Skip to main content

netget/server/h323/
h245.rs

1//! The H.245 NetGet needs for a call to count as connected, and nothing past it.
2//!
3//! Two ways in, both carried inside H.225.0 call signalling (NetGet never opens a separate
4//! H.245 TCP channel):
5//!
6//! - **Fast connect** (H.323 8.1.7): the caller's Setup carries `fastStart` — PER-encoded
7//!   OpenLogicalChannel proposals. [`answer_fast_start`] picks a G.711 channel each way and
8//!   returns them with this side's RTP/RTCP addresses filled in; [`offer_fast_start`] writes
9//!   the proposals when NetGet calls.
10//! - **Tunnelled H.245** (H.323 8.2.1): messages ride in `h245Control` of any H.225.0
11//!   message. [`H245Session`] runs the procedures a peer needs before it calls the session
12//!   established — capability exchange (TerminalCapabilitySet / Ack) and master-slave
13//!   determination — answers OpenLogicalChannel with this side's media address, CloseLogical
14//!   Channel, RoundTripDelay and EndSession, and opens one G.711 channel towards the peer.
15//!
16//! Only G.711 (µ-law and A-law, 64 kbit/s) is offered or accepted. NetGet binds the RTP/RTCP
17//! sockets it advertises and counts what arrives on them; it sends no media.
18
19use super::{per, schema};
20use anyhow::{anyhow, bail, Result};
21use serde_json::{json, Value};
22use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
23
24/// H.245 version 7.
25pub const PROTOCOL_ID: &str = "0.0.8.245.0.7";
26/// Most H.245 messages one call may exchange before NetGet stops answering them.
27pub const MAX_H245_MESSAGES: u32 = 256;
28/// The terminal type a terminal declares in master-slave determination (H.323 Table 1).
29pub const TERMINAL_TYPE: u64 = 50;
30/// G.711 frames per packet NetGet offers when the model does not choose: 20 ms of audio.
31pub const DEFAULT_FRAMES: u64 = 20;
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum Codec {
35    G711Ulaw,
36    G711Alaw,
37}
38
39impl Codec {
40    pub fn asn1(self) -> &'static str {
41        match self {
42            Codec::G711Ulaw => "g711Ulaw64k",
43            Codec::G711Alaw => "g711Alaw64k",
44        }
45    }
46
47    pub fn name(self) -> &'static str {
48        match self {
49            Codec::G711Ulaw => "g711u",
50            Codec::G711Alaw => "g711a",
51        }
52    }
53
54    pub fn parse(s: &str) -> Result<Self> {
55        match s.to_ascii_lowercase().as_str() {
56            "g711u" | "g711ulaw" | "g711ulaw64k" | "pcmu" | "ulaw" => Ok(Codec::G711Ulaw),
57            "g711a" | "g711alaw" | "g711alaw64k" | "pcma" | "alaw" => Ok(Codec::G711Alaw),
58            other => bail!("codec {other:?}: NetGet speaks g711u and g711a"),
59        }
60    }
61
62    /// The codec an `AudioCapability` value names, if it is one of these.
63    pub fn of_audio(v: &Value) -> Option<(Codec, u64)> {
64        for c in [Codec::G711Ulaw, Codec::G711Alaw] {
65            if let Some(frames) = v.get(c.asn1()).and_then(|f| f.as_u64()) {
66                return Some((c, frames));
67            }
68        }
69        None
70    }
71}
72
73/// Every codec NetGet will negotiate, in its default order of preference.
74pub const ALL_CODECS: [Codec; 2] = [Codec::G711Ulaw, Codec::G711Alaw];
75
76/// The audio codec an `AudioCapability` names, for the model (`g711Ulaw64k`, `g729`, ...).
77pub fn audio_name(v: &Value) -> String {
78    v.as_object()
79        .and_then(|o| o.keys().next().cloned())
80        .unwrap_or_else(|| "unknown".into())
81}
82
83pub fn decode(bytes: &[u8]) -> Result<Value> {
84    per::decode(schema::H245_MULTIMEDIASYSTEMCONTROLMESSAGE, bytes).map_err(|e| anyhow!("{e}"))
85}
86
87pub fn encode(msg: &Value) -> Result<Vec<u8>> {
88    per::encode(schema::H245_MULTIMEDIASYSTEMCONTROLMESSAGE, msg).map_err(|e| anyhow!("{e}"))
89}
90
91pub fn decode_olc(bytes: &[u8]) -> Result<Value> {
92    per::decode(schema::H245_OPENLOGICALCHANNEL, bytes).map_err(|e| anyhow!("{e}"))
93}
94
95pub fn encode_olc(olc: &Value) -> Result<Vec<u8>> {
96    per::encode(schema::H245_OPENLOGICALCHANNEL, olc).map_err(|e| anyhow!("{e}"))
97}
98
99/// An H.245 unicast `TransportAddress`.
100pub fn unicast(addr: SocketAddr) -> Value {
101    match addr.ip() {
102        IpAddr::V4(ip) => json!({"unicastAddress": {"iPAddress": {
103            "network": hex::encode(ip.octets()), "tsapIdentifier": addr.port()}}}),
104        IpAddr::V6(ip) => json!({"unicastAddress": {"iP6Address": {
105            "network": hex::encode(ip.octets()), "tsapIdentifier": addr.port()}}}),
106    }
107}
108
109pub fn unicast_addr(v: &Value) -> Option<SocketAddr> {
110    let u = v.get("unicastAddress")?;
111    if let Some(a) = u.get("iPAddress") {
112        let ip: [u8; 4] = hex::decode(a.get("network")?.as_str()?)
113            .ok()?
114            .try_into()
115            .ok()?;
116        let port = u16::try_from(a.get("tsapIdentifier")?.as_u64()?).ok()?;
117        return Some(SocketAddr::new(IpAddr::V4(Ipv4Addr::from(ip)), port));
118    }
119    if let Some(a) = u.get("iP6Address") {
120        let ip: [u8; 16] = hex::decode(a.get("network")?.as_str()?)
121            .ok()?
122            .try_into()
123            .ok()?;
124        let port = u16::try_from(a.get("tsapIdentifier")?.as_u64()?).ok()?;
125        return Some(SocketAddr::new(IpAddr::V6(Ipv6Addr::from(ip)), port));
126    }
127    None
128}
129
130/// Where media goes and how, once a channel each way is agreed.
131#[derive(Debug, Clone, Default, PartialEq, Eq)]
132pub struct Media {
133    pub codec: Option<&'static str>,
134    pub frames: Option<u64>,
135    /// Where the peer wants this side's RTP.
136    pub remote_rtp: Option<SocketAddr>,
137    pub remote_rtcp: Option<SocketAddr>,
138}
139
140impl Media {
141    pub fn to_json(&self) -> Value {
142        json!({
143            "codec": self.codec,
144            "frames_per_packet": self.frames,
145            "remote_rtp": self.remote_rtp.map(|a| a.to_string()),
146            "remote_rtcp": self.remote_rtcp.map(|a| a.to_string()),
147        })
148    }
149}
150
151fn h2250(session: u64, media: Option<SocketAddr>, control: Option<SocketAddr>) -> Value {
152    let mut p = json!({"sessionID": session});
153    if let Some(m) = media {
154        p["mediaChannel"] = unicast(m);
155    }
156    if let Some(c) = control {
157        p["mediaControlChannel"] = unicast(c);
158    }
159    json!({ "h2250LogicalChannelParameters": p })
160}
161
162fn audio(codec: Codec, frames: u64) -> Value {
163    json!({"audioData": { codec.asn1(): frames }})
164}
165
166/// The fastStart proposals NetGet puts in a Setup: for each codec, a channel NetGet transmits
167/// on (forward, RTCP given) and one it receives on (reverse, RTP and RTCP given).
168pub fn offer_fast_start(
169    codecs: &[Codec],
170    rtp: SocketAddr,
171    rtcp: SocketAddr,
172) -> Result<Vec<Vec<u8>>> {
173    let mut out = Vec::new();
174    for (i, c) in codecs.iter().enumerate() {
175        let lcn = 101 + i as u64;
176        out.push(encode_olc(&json!({
177            "forwardLogicalChannelNumber": lcn,
178            "forwardLogicalChannelParameters": {
179                "dataType": audio(*c, DEFAULT_FRAMES),
180                "multiplexParameters": h2250(1, None, Some(rtcp)),
181            },
182        }))?);
183        out.push(encode_olc(&json!({
184            "forwardLogicalChannelNumber": lcn + 100,
185            "forwardLogicalChannelParameters": {
186                "dataType": {"nullData": null},
187                "multiplexParameters": {"none": null},
188            },
189            "reverseLogicalChannelParameters": {
190                "dataType": audio(*c, DEFAULT_FRAMES),
191                "multiplexParameters": h2250(1, Some(rtp), Some(rtcp)),
192            },
193        }))?);
194    }
195    Ok(out)
196}
197
198/// One fastStart proposal, decoded, for the model.
199pub fn describe_proposal(olc: &Value) -> Value {
200    let fwd = olc.get("forwardLogicalChannelParameters");
201    let rev = olc.get("reverseLogicalChannelParameters");
202    let fwd_audio = fwd
203        .and_then(|f| f.get("dataType"))
204        .and_then(|d| d.get("audioData"));
205    let rev_audio = rev
206        .and_then(|r| r.get("dataType"))
207        .and_then(|d| d.get("audioData"));
208    match (fwd_audio, rev_audio) {
209        (Some(a), _) => json!({
210            "direction": "caller_sends",
211            "codec": audio_name(a),
212        }),
213        (None, Some(a)) => {
214            let media = rev
215                .and_then(|r| r.get("multiplexParameters"))
216                .and_then(|m| m.get("h2250LogicalChannelParameters"))
217                .and_then(|p| p.get("mediaChannel"))
218                .and_then(unicast_addr);
219            json!({
220                "direction": "caller_receives",
221                "codec": audio_name(a),
222                "caller_rtp": media.map(|m| m.to_string()),
223            })
224        }
225        _ => json!({"direction": "other"}),
226    }
227}
228
229/// Choose from a caller's fastStart proposals: the first channel the caller transmits on and
230/// the first it receives on, in a codec from `prefer`, both in the same codec. Returns the
231/// OpenLogicalChannel encodings to send back and what was agreed, or `None` when no G.711
232/// pair is on offer.
233pub fn answer_fast_start(
234    proposals: &[Vec<u8>],
235    prefer: &[Codec],
236    rtp: SocketAddr,
237    rtcp: SocketAddr,
238) -> Result<Option<(Vec<Vec<u8>>, Media)>> {
239    let decoded: Vec<Value> = proposals
240        .iter()
241        .filter_map(|p| decode_olc(p).ok())
242        .collect();
243    for codec in prefer {
244        let forward = decoded.iter().find(|o| {
245            o.get("reverseLogicalChannelParameters").is_none()
246                && o.get("forwardLogicalChannelParameters")
247                    .and_then(|f| f.get("dataType"))
248                    .and_then(|d| d.get("audioData"))
249                    .and_then(Codec::of_audio)
250                    .is_some_and(|(c, _)| c == *codec)
251        });
252        let reverse = decoded.iter().find(|o| {
253            o.get("reverseLogicalChannelParameters")
254                .and_then(|r| r.get("dataType"))
255                .and_then(|d| d.get("audioData"))
256                .and_then(Codec::of_audio)
257                .is_some_and(|(c, _)| c == *codec)
258        });
259        let (Some(forward), Some(reverse)) = (forward, reverse) else {
260            continue;
261        };
262        // The caller's transmit channel: tell it where to send (our RTP) and our RTCP.
263        let mut fwd = forward.clone();
264        let params = &mut fwd["forwardLogicalChannelParameters"]["multiplexParameters"]
265            ["h2250LogicalChannelParameters"];
266        if !params.is_object() {
267            continue;
268        }
269        params["mediaChannel"] = unicast(rtp);
270        params["mediaControlChannel"] = unicast(rtcp);
271        // The caller's receive channel: accepted as proposed, with this side's RTCP so the
272        // caller's receiver reports have somewhere to go.
273        let mut rev = reverse.clone();
274        let rparams = &mut rev["reverseLogicalChannelParameters"]["multiplexParameters"]
275            ["h2250LogicalChannelParameters"];
276        let remote_rtp = rparams.get("mediaChannel").and_then(unicast_addr);
277        let remote_rtcp = rparams.get("mediaControlChannel").and_then(unicast_addr);
278        if rparams.is_object() {
279            rparams["mediaControlChannel"] = unicast(rtcp);
280        }
281        let frames = reverse["reverseLogicalChannelParameters"]["dataType"]["audioData"]
282            .get(codec.asn1())
283            .and_then(|f| f.as_u64());
284        let answer = vec![encode_olc(&fwd)?, encode_olc(&rev)?];
285        return Ok(Some((
286            answer,
287            Media {
288                codec: Some(codec.name()),
289                frames,
290                remote_rtp,
291                remote_rtcp,
292            },
293        )));
294    }
295    Ok(None)
296}
297
298/// What a callee's fastStart answer says, for the side that offered: the agreed codec and
299/// where to send media.
300pub fn read_fast_start_answer(answer: &[Vec<u8>]) -> Media {
301    let mut media = Media::default();
302    for raw in answer {
303        let Ok(olc) = decode_olc(raw) else { continue };
304        if let Some(fwd) = olc.get("forwardLogicalChannelParameters") {
305            if let Some((c, frames)) = fwd
306                .get("dataType")
307                .and_then(|d| d.get("audioData"))
308                .and_then(Codec::of_audio)
309            {
310                let p = &fwd["multiplexParameters"]["h2250LogicalChannelParameters"];
311                media.codec = Some(c.name());
312                media.frames = Some(frames);
313                media.remote_rtp = p.get("mediaChannel").and_then(unicast_addr);
314                media.remote_rtcp = p.get("mediaControlChannel").and_then(unicast_addr);
315            }
316        }
317    }
318    media
319}
320
321fn our_capability_set(seq: u64, codecs: &[Codec]) -> Value {
322    let multipoint = json!({
323        "multicastCapability": false,
324        "multiUniCastConference": false,
325        "mediaDistributionCapability": [{
326            "centralizedControl": false, "distributedControl": false,
327            "centralizedAudio": false, "distributedAudio": false,
328            "centralizedVideo": false, "distributedVideo": false,
329        }],
330    });
331    let table: Vec<Value> = codecs
332        .iter()
333        .enumerate()
334        .map(|(i, c)| {
335            json!({
336                "capabilityTableEntryNumber": i + 1,
337                "capability": {"receiveAudioCapability": { c.asn1(): DEFAULT_FRAMES }},
338            })
339        })
340        .collect();
341    let alternatives: Vec<Value> = (1..=codecs.len()).map(|n| json!(n)).collect();
342    json!({"request": {"terminalCapabilitySet": {
343        "sequenceNumber": seq,
344        "protocolIdentifier": PROTOCOL_ID,
345        "multiplexCapability": {"h2250Capability": {
346            "maximumAudioDelayJitter": 60,
347            "receiveMultipointCapability": multipoint,
348            "transmitMultipointCapability": multipoint,
349            "receiveAndTransmitMultipointCapability": multipoint,
350            "mcCapability": {"centralizedConferenceMC": false, "decentralizedConferenceMC": false},
351            "rtcpVideoControlCapability": false,
352            "mediaPacketizationCapability": {"h261aVideoPacketization": false},
353            "logicalChannelSwitchingCapability": false,
354            "t120DynamicPortCapability": false,
355        }},
356        "capabilityTable": table,
357        "capabilityDescriptors": [{
358            "capabilityDescriptorNumber": 0,
359            "simultaneousCapabilities": [alternatives],
360        }],
361    }}})
362}
363
364/// The tunnelled H.245 procedures for one call.
365pub struct H245Session {
366    codecs: Vec<Codec>,
367    rtp: SocketAddr,
368    rtcp: SocketAddr,
369    msd_number: u64,
370    started: bool,
371    our_tcs_acked: bool,
372    peer_tcs_seen: bool,
373    peer_codecs: Vec<Codec>,
374    /// `Some(true)` when this side is master.
375    master: Option<bool>,
376    our_channel: Option<u64>,
377    our_channel_acked: bool,
378    peer_channels: Vec<u64>,
379    messages: u32,
380    pub media: Media,
381    pub ended: bool,
382    /// DTMF and user input the peer sent, oldest first, for the call's events.
383    pub user_input: Vec<String>,
384}
385
386impl H245Session {
387    pub fn new(codecs: Vec<Codec>, rtp: SocketAddr, rtcp: SocketAddr) -> Self {
388        Self {
389            codecs,
390            rtp,
391            rtcp,
392            msd_number: rand::random::<u64>() & 0xFF_FFFF,
393            started: false,
394            our_tcs_acked: false,
395            peer_tcs_seen: false,
396            peer_codecs: Vec::new(),
397            master: None,
398            our_channel: None,
399            our_channel_acked: false,
400            peer_channels: Vec::new(),
401            messages: 0,
402            media: Media::default(),
403            ended: false,
404            user_input: Vec::new(),
405        }
406    }
407
408    pub fn started(&self) -> bool {
409        self.started
410    }
411
412    /// Capability exchange done both ways and master/slave decided.
413    pub fn established(&self) -> bool {
414        self.our_tcs_acked && self.peer_tcs_seen && self.master.is_some()
415    }
416
417    pub fn summary(&self) -> Value {
418        json!({
419            "capability_exchange": self.our_tcs_acked && self.peer_tcs_seen,
420            "master": self.master,
421            "peer_codecs": self.peer_codecs.iter().map(|c| c.name()).collect::<Vec<_>>(),
422            "channel_to_peer": self.our_channel_acked,
423            "channels_from_peer": self.peer_channels.len(),
424        })
425    }
426
427    /// This side's opening: TerminalCapabilitySet and MasterSlaveDetermination.
428    pub fn start(&mut self) -> Result<Vec<Vec<u8>>> {
429        if self.started {
430            return Ok(Vec::new());
431        }
432        self.started = true;
433        Ok(vec![
434            encode(&our_capability_set(1, &self.codecs))?,
435            encode(&json!({"request": {"masterSlaveDetermination": {
436                "terminalType": TERMINAL_TYPE,
437                "statusDeterminationNumber": self.msd_number,
438            }}}))?,
439        ])
440    }
441
442    /// Handle one tunnelled message; returns the messages to send back.
443    pub fn on_message(&mut self, raw: &[u8]) -> Result<Vec<Vec<u8>>> {
444        self.messages += 1;
445        if self.messages > MAX_H245_MESSAGES {
446            bail!("more than {MAX_H245_MESSAGES} H.245 messages on one call");
447        }
448        let msg = decode(raw)?;
449        let mut out: Vec<Value> = Vec::new();
450        let mut start_first = false;
451        if let Some(req) = msg.get("request") {
452            if !self.started {
453                start_first = true;
454            }
455            if let Some(tcs) = req.get("terminalCapabilitySet") {
456                let seq = tcs
457                    .get("sequenceNumber")
458                    .and_then(|s| s.as_u64())
459                    .unwrap_or(0);
460                self.peer_tcs_seen = true;
461                self.peer_codecs = tcs
462                    .get("capabilityTable")
463                    .and_then(|t| t.as_array())
464                    .map(|t| {
465                        t.iter()
466                            .filter_map(|e| {
467                                let cap = e.get("capability")?;
468                                let a = cap
469                                    .get("receiveAudioCapability")
470                                    .or_else(|| cap.get("receiveAndTransmitAudioCapability"))?;
471                                Codec::of_audio(a).map(|(c, _)| c)
472                            })
473                            .collect()
474                    })
475                    .unwrap_or_default();
476                out.push(
477                    json!({"response": {"terminalCapabilitySetAck": {"sequenceNumber": seq}}}),
478                );
479            } else if let Some(msd) = req.get("masterSlaveDetermination") {
480                let their_type = msd
481                    .get("terminalType")
482                    .and_then(|t| t.as_u64())
483                    .unwrap_or(0);
484                let their_num = msd
485                    .get("statusDeterminationNumber")
486                    .and_then(|t| t.as_u64())
487                    .unwrap_or(0);
488                let we_are_master = if their_type != TERMINAL_TYPE {
489                    TERMINAL_TYPE > their_type
490                } else {
491                    let diff = their_num.wrapping_sub(self.msd_number) & 0xFF_FFFF;
492                    if diff == 0 || diff == 0x80_0000 {
493                        out.push(json!({"response": {"masterSlaveDeterminationReject": {
494                            "cause": {"identicalNumbers": null}}}}));
495                        return self.finish(out, false);
496                    }
497                    diff < 0x80_0000
498                };
499                self.master = Some(we_are_master);
500                // The Ack tells the *receiver* what it is.
501                let decision = if we_are_master { "slave" } else { "master" };
502                out.push(json!({"response": {"masterSlaveDeterminationAck": {
503                    "decision": { decision: null }}}}));
504            } else if let Some(olc) = req.get("openLogicalChannel") {
505                let lcn = olc
506                    .get("forwardLogicalChannelNumber")
507                    .and_then(|n| n.as_u64())
508                    .unwrap_or(1);
509                let codec = olc
510                    .get("forwardLogicalChannelParameters")
511                    .and_then(|f| f.get("dataType"))
512                    .and_then(|d| d.get("audioData"))
513                    .and_then(Codec::of_audio);
514                match codec {
515                    Some((c, frames)) if self.codecs.contains(&c) => {
516                        if self.peer_channels.len() < 8 {
517                            self.peer_channels.push(lcn);
518                        }
519                        if self.media.codec.is_none() {
520                            self.media.codec = Some(c.name());
521                            self.media.frames = Some(frames);
522                        }
523                        out.push(json!({"response": {"openLogicalChannelAck": {
524                            "forwardLogicalChannelNumber": lcn,
525                            "forwardMultiplexAckParameters": {"h2250LogicalChannelAckParameters": {
526                                "sessionID": 1,
527                                "mediaChannel": unicast(self.rtp),
528                                "mediaControlChannel": unicast(self.rtcp),
529                                "flowControlToZero": false,
530                            }},
531                        }}}));
532                    }
533                    _ => out.push(json!({"response": {"openLogicalChannelReject": {
534                        "forwardLogicalChannelNumber": lcn,
535                        "cause": {"dataTypeNotSupported": null},
536                    }}})),
537                }
538            } else if let Some(clc) = req.get("closeLogicalChannel") {
539                let lcn = clc
540                    .get("forwardLogicalChannelNumber")
541                    .and_then(|n| n.as_u64())
542                    .unwrap_or(1);
543                self.peer_channels.retain(|c| *c != lcn);
544                out.push(json!({"response": {"closeLogicalChannelAck": {
545                    "forwardLogicalChannelNumber": lcn}}}));
546            } else if let Some(rtd) = req.get("roundTripDelayRequest") {
547                let seq = rtd
548                    .get("sequenceNumber")
549                    .and_then(|s| s.as_u64())
550                    .unwrap_or(0);
551                out.push(json!({"response": {"roundTripDelayResponse": {"sequenceNumber": seq}}}));
552            } else if let Some(rcc) = req.get("requestChannelClose") {
553                let lcn = rcc
554                    .get("forwardLogicalChannelNumber")
555                    .and_then(|n| n.as_u64())
556                    .unwrap_or(1);
557                out.push(json!({"response": {"requestChannelCloseAck": {
558                    "forwardLogicalChannelNumber": lcn}}}));
559            }
560        } else if let Some(resp) = msg.get("response") {
561            if resp.get("terminalCapabilitySetAck").is_some() {
562                self.our_tcs_acked = true;
563            } else if let Some(ack) = resp.get("masterSlaveDeterminationAck") {
564                // The peer tells this side what it is.
565                if let Some(d) = ack.get("decision").and_then(|d| d.as_object()) {
566                    let we_are_master = d.contains_key("master");
567                    if self.master.is_none() {
568                        self.master = Some(we_are_master);
569                        let theirs = if we_are_master { "slave" } else { "master" };
570                        out.push(json!({"response": {"masterSlaveDeterminationAck": {
571                            "decision": { theirs: null }}}}));
572                    }
573                }
574            } else if let Some(ack) = resp.get("openLogicalChannelAck") {
575                let p = ack
576                    .get("forwardMultiplexAckParameters")
577                    .and_then(|m| m.get("h2250LogicalChannelAckParameters"));
578                if let Some(p) = p {
579                    self.media.remote_rtp = p.get("mediaChannel").and_then(unicast_addr);
580                    self.media.remote_rtcp = p.get("mediaControlChannel").and_then(unicast_addr);
581                }
582                self.our_channel_acked = true;
583            }
584        } else if let Some(cmd) = msg.get("command") {
585            if cmd.get("endSessionCommand").is_some() {
586                self.ended = true;
587                out.push(json!({"command": {"endSessionCommand": {"disconnect": null}}}));
588            }
589        } else if let Some(ind) = msg.get("indication") {
590            if let Some(ui) = ind.get("userInput") {
591                let text = ui
592                    .get("signal")
593                    .and_then(|s| s.get("signalType"))
594                    .or_else(|| ui.get("alphanumeric"))
595                    .and_then(|s| s.as_str())
596                    .map(crate::utils::sanitize::strip_controls);
597                if let Some(t) = text {
598                    if self.user_input.len() < 64 {
599                        self.user_input.push(t);
600                    }
601                }
602            }
603        }
604        self.finish(out, start_first)
605    }
606
607    fn finish(&mut self, out: Vec<Value>, start_first: bool) -> Result<Vec<Vec<u8>>> {
608        let mut encoded = if start_first {
609            self.start()?
610        } else {
611            Vec::new()
612        };
613        for m in &out {
614            encoded.push(encode(m)?);
615        }
616        // Once both capability sets are known and roles decided, open a G.711 channel to the
617        // peer in the first codec both sides have.
618        if self.established() && self.our_channel.is_none() {
619            if let Some(codec) = self
620                .codecs
621                .iter()
622                .find(|c| self.peer_codecs.contains(c))
623                .copied()
624            {
625                self.our_channel = Some(1);
626                encoded.push(encode(&json!({"request": {"openLogicalChannel": {
627                    "forwardLogicalChannelNumber": 1,
628                    "forwardLogicalChannelParameters": {
629                        "dataType": audio(codec, DEFAULT_FRAMES),
630                        "multiplexParameters": h2250(1, None, Some(self.rtcp)),
631                    },
632                }}}))?);
633                if self.media.codec.is_none() {
634                    self.media.codec = Some(codec.name());
635                    self.media.frames = Some(DEFAULT_FRAMES);
636                }
637            }
638        }
639        Ok(encoded)
640    }
641
642    /// EndSessionCommand, for hanging up a call that has H.245 running.
643    pub fn end_session(&mut self) -> Result<Vec<u8>> {
644        self.ended = true;
645        encode(&json!({"command": {"endSessionCommand": {"disconnect": null}}}))
646    }
647}