1use super::{per, schema};
20use anyhow::{anyhow, bail, Result};
21use serde_json::{json, Value};
22use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
23
24pub const PROTOCOL_ID: &str = "0.0.8.245.0.7";
26pub const MAX_H245_MESSAGES: u32 = 256;
28pub const TERMINAL_TYPE: u64 = 50;
30pub 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 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
73pub const ALL_CODECS: [Codec; 2] = [Codec::G711Ulaw, Codec::G711Alaw];
75
76pub 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
99pub 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#[derive(Debug, Clone, Default, PartialEq, Eq)]
132pub struct Media {
133 pub codec: Option<&'static str>,
134 pub frames: Option<u64>,
135 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
166pub 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
198pub 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
229pub 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 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 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
298pub 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
364pub 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 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 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 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 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 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 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 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 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 pub fn end_session(&mut self) -> Result<Vec<u8>> {
644 self.ended = true;
645 encode(&json!({"command": {"endSessionCommand": {"disconnect": null}}}))
646 }
647}