|
4 | 4 | package sdp |
5 | 5 |
|
6 | 6 | import ( |
| 7 | + "net" |
| 8 | + "strings" |
7 | 9 | "testing" |
| 10 | + "unicode" |
8 | 11 |
|
9 | 12 | "github.com/stretchr/testify/assert" |
10 | 13 | ) |
@@ -173,6 +176,11 @@ const ( |
173 | 176 | MediaTCPTLSMRCPv2 = TimingSDP + |
174 | 177 | "m=application 1544 TCP/TLS/MRCPv2 1\r\n" |
175 | 178 |
|
| 179 | + minimalBaseSDP = "v=0\r\n" + |
| 180 | + "o=- 1 1 IN IP4 0.0.0.0\r\n" + |
| 181 | + "s=-\r\n" + |
| 182 | + "t=0 0\r\n" |
| 183 | + |
176 | 184 | CanonicalUnmarshalSDP = "v=0\r\n" + |
177 | 185 | "o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5\r\n" + |
178 | 186 | "s=SDP Seminar\r\n" + |
@@ -579,3 +587,144 @@ func TestUnmarshalOriginEdgeCases(t *testing.T) { |
579 | 587 | }) |
580 | 588 | } |
581 | 589 | } |
| 590 | + |
| 591 | +func TestIgnoreMalformedSessionCandidate(t *testing.T) { |
| 592 | + in := minimalBaseSDP + |
| 593 | + "a=recvonly\r\n" + |
| 594 | + // invalid IPv4 address should be ignored |
| 595 | + "a=candidate:1 1 UDP 12345 999.999.999.999 1234 typ host\r\n" |
| 596 | + |
| 597 | + var sd SessionDescription |
| 598 | + err := sd.UnmarshalString(in) |
| 599 | + assert.NoError(t, err) |
| 600 | + |
| 601 | + out, err := sd.Marshal() |
| 602 | + assert.NoError(t, err) |
| 603 | + |
| 604 | + // candidate line should be dropped, everything else is preserved |
| 605 | + want := minimalBaseSDP + "a=recvonly\r\n" |
| 606 | + assert.Equal(t, want, string(out)) |
| 607 | +} |
| 608 | + |
| 609 | +func TestIgnoreMalformedMediaCandidate(t *testing.T) { |
| 610 | + in := minimalBaseSDP + |
| 611 | + "m=audio 9 RTP/AVP 0\r\n" + |
| 612 | + "a=rtpmap:0 PCMU/8000\r\n" + |
| 613 | + // malformed, should be ignored |
| 614 | + "a=candidate:1 1 UDP 12345 not_an_ip 1234 typ host\r\n" + |
| 615 | + // valid candidate remains |
| 616 | + "a=candidate:2 1 UDP 12345 203.0.113.1 2345 typ host\r\n" |
| 617 | + |
| 618 | + var sd SessionDescription |
| 619 | + err := sd.UnmarshalString(in) |
| 620 | + assert.NoError(t, err) |
| 621 | + |
| 622 | + out, err := sd.Marshal() |
| 623 | + assert.NoError(t, err) |
| 624 | + |
| 625 | + want := minimalBaseSDP + |
| 626 | + "m=audio 9 RTP/AVP 0\r\n" + |
| 627 | + "a=rtpmap:0 PCMU/8000\r\n" + |
| 628 | + "a=candidate:2 1 UDP 12345 203.0.113.1 2345 typ host\r\n" |
| 629 | + assert.Equal(t, want, string(out)) |
| 630 | +} |
| 631 | + |
| 632 | +func TestAllowMDNSCandidate(t *testing.T) { |
| 633 | + in := minimalBaseSDP + |
| 634 | + // mDNS hostnames are valid |
| 635 | + "a=candidate:1 1 UDP 12345 myhost.local 1234 typ host\r\n" |
| 636 | + |
| 637 | + var sd SessionDescription |
| 638 | + err := sd.UnmarshalString(in) |
| 639 | + assert.NoError(t, err) |
| 640 | + |
| 641 | + out, err := sd.Marshal() |
| 642 | + assert.NoError(t, err) |
| 643 | + assert.Equal(t, in, string(out)) |
| 644 | +} |
| 645 | + |
| 646 | +func TestAllowIPv6Candidate(t *testing.T) { |
| 647 | + in := minimalBaseSDP + |
| 648 | + // IPv6 literal is valid |
| 649 | + "a=candidate:1 1 UDP 12345 2001:db8::1 5555 typ host\r\n" |
| 650 | + |
| 651 | + var sd SessionDescription |
| 652 | + err := sd.UnmarshalString(in) |
| 653 | + assert.NoError(t, err) |
| 654 | + |
| 655 | + out, err := sd.Marshal() |
| 656 | + assert.NoError(t, err) |
| 657 | + assert.Equal(t, in, string(out)) |
| 658 | +} |
| 659 | + |
| 660 | +func TestIgnoreTruncatedCandidate(t *testing.T) { |
| 661 | + in := minimalBaseSDP + |
| 662 | + // missing port + fewer than 6 fields after split should be ignored |
| 663 | + "a=candidate:1 1 UDP 2113667327 203.0.113.1\r\n" |
| 664 | + |
| 665 | + var sd SessionDescription |
| 666 | + err := sd.UnmarshalString(in) |
| 667 | + assert.NoError(t, err) |
| 668 | + |
| 669 | + out, err := sd.Marshal() |
| 670 | + assert.NoError(t, err) |
| 671 | + // attribute should be dropped |
| 672 | + assert.Equal(t, minimalBaseSDP, string(out)) |
| 673 | +} |
| 674 | + |
| 675 | +func FuzzUnmarshalIgnoreMalformedCandidates(f *testing.F) { |
| 676 | + // Seeds: valid IPv4, valid IPv6, mDNS, and some bad cases |
| 677 | + for _, seed := range []string{ |
| 678 | + "203.0.113.1", |
| 679 | + "2001:db8::1", |
| 680 | + "router.local", |
| 681 | + "999.999.999.999", |
| 682 | + "bad space", // should be sanitized |
| 683 | + "", // should be coerced |
| 684 | + "::::", // invalid IPv6 |
| 685 | + "host_name", // underscore is invalid for DNS but we just treat as string |
| 686 | + "-.-", // sample punctuation |
| 687 | + } { |
| 688 | + f.Add(seed) |
| 689 | + } |
| 690 | + |
| 691 | + f.Fuzz(func(t *testing.T, addr string) { |
| 692 | + // sanitize fuzzed addr to avoid breaking SDP tokenization/lines |
| 693 | + addr = strings.Map(func(r rune) rune { |
| 694 | + switch { |
| 695 | + case unicode.IsLetter(r), unicode.IsDigit(r): |
| 696 | + return r |
| 697 | + } |
| 698 | + // allow a few safe punctuation characters |
| 699 | + switch r { |
| 700 | + case '.', ':', '-': |
| 701 | + return r |
| 702 | + default: |
| 703 | + return -1 |
| 704 | + } |
| 705 | + }, addr) |
| 706 | + if addr == "" { |
| 707 | + addr = "bad" |
| 708 | + } |
| 709 | + |
| 710 | + line := "a=candidate:1 1 UDP 12345 " + addr + " 1 typ host\r\n" |
| 711 | + in := minimalBaseSDP + line |
| 712 | + |
| 713 | + var sd SessionDescription |
| 714 | + err := sd.UnmarshalString(in) |
| 715 | + // unmarshal should never error if a candidate address is malformed. |
| 716 | + assert.NoError(t, err) |
| 717 | + |
| 718 | + outBytes, err := sd.Marshal() |
| 719 | + assert.NoError(t, err) |
| 720 | + out := string(outBytes) |
| 721 | + |
| 722 | + // Determine if candidate should be kept |
| 723 | + keep := strings.HasSuffix(addr, ".local") || net.ParseIP(addr) != nil |
| 724 | + if keep { |
| 725 | + assert.Contains(t, out, line) |
| 726 | + } else { |
| 727 | + assert.NotContains(t, out, line) |
| 728 | + } |
| 729 | + }) |
| 730 | +} |
0 commit comments