engine/sdp: CODECS override for the codecs we offer and accept
KPN's interconnect gateway transcodes every caller up: a G.711-only fixed-line INVITE reached the phone offering PCMA, PCMU, AMR and AMR-WB (bench call 2026-09-08), so the AMR-WB-only build rang and the narrowband path — the one the Telia field report hit with 488 — cannot be reached on the air through KPN by any caller. CODECS=<list> (comma-separated over AMR-WB, AMR/AMR-NB, PCMA, PCMU) makes the list both the codecs offered on an outgoing call and the acceptance preference for an inbound offer, in that order: CODECS=PCMA takes G.711 A-law out of the mixed offer above, CODECS=PCMA,PCMU sends a G.711-only offer toward the network. Unset, nothing changes — the default offer bytes and the AMR-WB > AMR > PCMA > PCMU preference stay pinned. The daemon logs an active override at startup.
This commit is contained in:
parent
452634683c
commit
37ec8660b7
6 changed files with 270 additions and 67 deletions
|
|
@ -772,6 +772,62 @@ int main() {
|
|||
Check(m.Terminated(), "abandoned machine is terminated");
|
||||
}
|
||||
|
||||
// ---- CODECS override, terminating side: the transcoding gateway's
|
||||
// mixed offer (KPN 2026-09-08) answered with G.711 A-law because the
|
||||
// list says so — the on-air narrowband bench path
|
||||
{
|
||||
imsd::util::Rng rng(51);
|
||||
std::string sdp =
|
||||
"v=0\r\no=LucentPCSF 1 1 IN IP6 2001:db8::309\r\ns=-\r\n"
|
||||
"c=IN IP6 2001:db8::309\r\nt=0 0\r\n"
|
||||
"m=audio 41166 RTP/AVP 8 0 100 96 101 13 106\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\na=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:100 AMR/8000\r\na=fmtp:100 mode-change-period=2\r\n"
|
||||
"a=rtpmap:96 AMR-WB/16000\r\na=fmtp:96 mode-change-period=2\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15\r\n"
|
||||
"a=rtpmap:106 telephone-event/16000\r\na=fmtp:106 0-15\r\n"
|
||||
"a=sendrecv\r\na=ptime:20\r\na=maxptime:60\r\n";
|
||||
CallMachine m(c, rng, "ims-call-20", IncomingInvite{MtInviteSdp("mt-kpn-pcma", sdp)}, 50004, {"PCMA"});
|
||||
auto ai = m.OnInvite();
|
||||
Check(Responded(ai, "SIP/2.0 180 Ringing") != nullptr, "CODECS=PCMA: mixed offer rings");
|
||||
auto aa = m.OnAccept();
|
||||
const Action* ok = Responded(aa, "SIP/2.0 200 OK");
|
||||
Check(ok && ok->text.contains("m=audio 50004 RTP/AVP 8 101\r\n"), "CODECS=PCMA: answer is PCMA + narrowband DTMF at the offer's pts");
|
||||
Check(ok && ok->text.contains("a=rtpmap:8 PCMA/8000/1\r\n") && !ok->text.contains("AMR"), "CODECS=PCMA: no AMR in the answer");
|
||||
const Action* med = Find(aa, Action::Type::StartMedia);
|
||||
Check(med && med->media.codec == "PCMA" && med->media.payloadType == 8 && !med->media.octetAlign, "CODECS=PCMA: media leg starts G.711 A-law");
|
||||
|
||||
// same offer, CODECS=AMR: narrowband AMR, bandwidth-efficient as offered
|
||||
imsd::util::Rng rng2(52);
|
||||
CallMachine m2(c, rng2, "ims-call-21", IncomingInvite{MtInviteSdp("mt-kpn-amr", sdp)}, 50004, {"AMR"});
|
||||
m2.OnInvite();
|
||||
auto aa2 = m2.OnAccept();
|
||||
const Action* ok2 = Responded(aa2, "SIP/2.0 200 OK");
|
||||
Check(ok2 && ok2->text.contains("m=audio 50004 RTP/AVP 100 101\r\n") && !ok2->text.contains("octet-align"), "CODECS=AMR: answer is AMR-NB BE + narrowband DTMF");
|
||||
const Action* med2 = Find(aa2, Action::Type::StartMedia);
|
||||
Check(med2 && med2->media.codec == "AMR" && med2->media.payloadType == 100 && !med2->media.octetAlign, "CODECS=AMR: media leg starts AMR-NB BE");
|
||||
|
||||
// same offer, no override: unchanged — AMR-WB
|
||||
imsd::util::Rng rng3(53);
|
||||
CallMachine m3(c, rng3, "ims-call-22", IncomingInvite{MtInviteSdp("mt-kpn-default", sdp)}, 50004);
|
||||
m3.OnInvite();
|
||||
auto aa3 = m3.OnAccept();
|
||||
const Action* med3 = Find(aa3, Action::Type::StartMedia);
|
||||
Check(med3 && med3->media.codec == "AMR-WB" && med3->media.payloadType == 96, "no override: the same offer still lands on AMR-WB");
|
||||
}
|
||||
|
||||
// ---- CODECS override, originating side: the INVITE offers only what the
|
||||
// list names (G.711 = a landline-shaped offer toward the network)
|
||||
{
|
||||
imsd::util::Rng rng(54);
|
||||
CallMachine m(c, rng, "ims-call-23", "0783690354", 50004, false, false, {"PCMA", "PCMU"});
|
||||
auto start = m.Start();
|
||||
const Action* inv = Sent(start, "INVITE ");
|
||||
Check(inv && inv->text.contains("m=audio 50004 RTP/AVP 8 0 100\r\n"), "CODECS=PCMA,PCMU: INVITE offers G.711 only");
|
||||
Check(inv && !inv->text.contains("AMR"), "CODECS=PCMA,PCMU: no AMR in the offer");
|
||||
Check(inv && inv->text.contains("a=rtpmap:100 telephone-event/8000\r\n") && !inv->text.contains("telephone-event/16000"), "CODECS=PCMA,PCMU: narrowband DTMF only");
|
||||
}
|
||||
|
||||
if (Failures == 0) std::println("Engine: all tests passed");
|
||||
return Failures;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -294,6 +294,93 @@ int main() {
|
|||
Check(sdp.contains("a=rtpmap:0 PCMU/8000/1\r\n") && !sdp.contains("fmtp:0"), "PCMU answer has no fmtp");
|
||||
}
|
||||
|
||||
// ---- CODECS override: the offer of a real interconnect gateway that
|
||||
// transcodes up (KPN, 2026-09-08: a G.711-only caller arrives with
|
||||
// AMR-WB BE added). Default picks AMR-WB; a preference list picks
|
||||
// what it names, in its order, and nothing outside it.
|
||||
{
|
||||
std::string kpn =
|
||||
"c=IN IP6 2001:db8::309\r\n"
|
||||
"m=audio 41166 RTP/AVP 8 0 100 96 101 13 106\r\n"
|
||||
"a=rtpmap:8 PCMA/8000\r\na=rtpmap:0 PCMU/8000\r\n"
|
||||
"a=rtpmap:100 AMR/8000\r\na=fmtp:100 mode-change-period=2\r\n"
|
||||
"a=rtpmap:96 AMR-WB/16000\r\na=fmtp:96 mode-change-period=2\r\n"
|
||||
"a=rtpmap:101 telephone-event/8000\r\na=fmtp:101 0-15\r\n"
|
||||
"a=rtpmap:106 telephone-event/16000\r\na=fmtp:106 0-15\r\n"
|
||||
"a=sendrecv\r\na=ptime:20\r\na=maxptime:60\r\n";
|
||||
Answer d = ParseAnswer(kpn);
|
||||
Check(d.payloadType == 96 && d.codec == "AMR-WB" && !d.octetAlign, "transcoding gateway: default takes AMR-WB BE");
|
||||
std::vector<std::string> pcma{"PCMA"};
|
||||
Answer a = ParseAnswer(kpn, pcma);
|
||||
Check(a.payloadType == 8 && a.codec == "PCMA" && !a.octetAlign, "CODECS=PCMA takes G.711 A-law out of a mixed offer");
|
||||
std::vector<std::string> ulawFirst{"PCMU", "PCMA"};
|
||||
Answer u = ParseAnswer(kpn, ulawFirst);
|
||||
Check(u.payloadType == 0 && u.codec == "PCMU", "CODECS order wins over the offer's order");
|
||||
std::vector<std::string> nb{"AMR"};
|
||||
Answer n = ParseAnswer(kpn, nb);
|
||||
Check(n.payloadType == 100 && n.codec == "AMR" && !n.octetAlign, "CODECS=AMR takes narrowband AMR, BE as offered");
|
||||
std::vector<std::string> wb{"AMR-WB"};
|
||||
Answer w = ParseAnswer(kpn, wb);
|
||||
Check(w.payloadType == 96 && w.codec == "AMR-WB", "CODECS=AMR-WB matches the default here");
|
||||
Answer g = ParseAnswer("c=IN IP4 192.0.2.1\r\n" "m=audio 5000 RTP/AVP 8 0 101\r\n" "a=rtpmap:101 telephone-event/8000\r\n", wb);
|
||||
Check(g.codec.empty() && g.payloadType == 8, "CODECS=AMR-WB refuses a G.711-only offer (codec empty, first pt)");
|
||||
Check(g.port == 5000 && g.ip == "192.0.2.1", "address/port still parsed when no codec is acceptable");
|
||||
}
|
||||
|
||||
// ---- ParseCodecList: case, spaces, AMR-NB alias, unknowns, repeats
|
||||
{
|
||||
auto l = ParseCodecList("pcma, amr-nb ,AMR-WB,g729,PCMA,,PCMU");
|
||||
Check(l.size() == 4 && l[0] == "PCMA" && l[1] == "AMR" && l[2] == "AMR-WB" && l[3] == "PCMU", "codec list normalised: PCMA,AMR,AMR-WB,PCMU");
|
||||
Check(ParseCodecList("").empty(), "empty spec = no override");
|
||||
Check(ParseCodecList("opus, g722").empty(), "only unknown names = no override");
|
||||
}
|
||||
|
||||
// ---- BuildOffer with an explicit codec list: G.711 only (landline
|
||||
// shape, for MO tests against a transcoding network)
|
||||
{
|
||||
std::string sdp = BuildOffer({
|
||||
.local = "192.0.2.1",
|
||||
.rtpPort = 22222,
|
||||
.sessionId = 1234567,
|
||||
.codecs = {"PCMA", "PCMU"},
|
||||
});
|
||||
std::string expected =
|
||||
"v=0\r\n"
|
||||
"o=- 1234567 1234567 IN IP4 192.0.2.1\r\n"
|
||||
"s=-\r\n"
|
||||
"c=IN IP4 192.0.2.1\r\n"
|
||||
"t=0 0\r\n"
|
||||
"m=audio 22222 RTP/AVP 8 0 100\r\n"
|
||||
"b=AS:80\r\n"
|
||||
"b=RS:512\r\n"
|
||||
"b=RR:1536\r\n"
|
||||
"a=rtpmap:8 PCMA/8000/1\r\n"
|
||||
"a=rtpmap:0 PCMU/8000/1\r\n"
|
||||
"a=rtpmap:100 telephone-event/8000\r\n"
|
||||
"a=fmtp:100 0-15\r\n"
|
||||
"a=ptime:20\r\n"
|
||||
"a=maxptime:240\r\n"
|
||||
"a=sendrecv\r\n";
|
||||
Check(sdp == expected, "G.711-only offer byte-exact");
|
||||
}
|
||||
|
||||
// ---- BuildOffer, AMR narrowband only: pt 99, narrowband DTMF only, AS 30
|
||||
{
|
||||
std::string sdp = BuildOffer({.local = "2001:db8::1", .rtpPort = 50004, .sessionId = 7, .codecs = {"AMR"}});
|
||||
Check(sdp.contains("m=audio 50004 RTP/AVP 99 100\r\n"), "AMR-only offer m-line");
|
||||
Check(sdp.contains("b=AS:30\r\n"), "AMR-only offer bandwidth");
|
||||
Check(sdp.contains("a=rtpmap:99 AMR/8000/1\r\na=fmtp:99 octet-align=1;mode-change-capability=2;max-red=0\r\n"), "AMR-only offer rtpmap+fmtp");
|
||||
Check(!sdp.contains("16000"), "no wideband telephone-event without AMR-WB");
|
||||
}
|
||||
|
||||
// ---- BuildOffer, explicit default order == the pinned default bytes
|
||||
{
|
||||
Offer o{.local = "2001:db8::1", .rtpPort = 50004, .sessionId = 7};
|
||||
Offer e = o;
|
||||
e.codecs = {"AMR-WB", "AMR"};
|
||||
Check(BuildOffer(o) == BuildOffer(e), "explicit AMR-WB,AMR list is byte-identical to the default offer");
|
||||
}
|
||||
|
||||
if (Failures == 0) std::println("Sdp: all tests passed");
|
||||
return Failures;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue