package asterisk import ( "context" "encoding/binary" "strings" "sync" "testing" "time" ) func hepTestFrame(cid, sip string) []byte { packet := []byte("HEP3\x00\x00") for _, chunk := range []struct { kind uint16 data []byte }{{11, []byte{1}}, {17, []byte(cid)}, {15, []byte(sip)}} { part := make([]byte, 6+len(chunk.data)) binary.BigEndian.PutUint16(part[2:4], chunk.kind) binary.BigEndian.PutUint16(part[4:6], uint16(len(part))) copy(part[6:], chunk.data) packet = append(packet, part...) } binary.BigEndian.PutUint16(packet[4:6], uint16(len(packet))) return packet } func hepTestSIP(callID, method, branch, firstLine string) string { return firstLine + "\r\nVia: SIP/2.0/UDP 127.0.0.1:5060;branch=" + branch + "\r\nFrom: ;tag=a\r\nTo: \r\nCall-ID: " + callID + "\r\nCSeq: 1 " + method + "\r\nContent-Length: 0\r\n\r\n" } func TestHEPMirrorKeepsConcurrentSIPTransactionsSeparate(t *testing.T) { mirror := NewHEPMirror() if err := mirror.Bind("exec-a", "sip-id-a"); err != nil { t.Fatal(err) } if err := mirror.Bind("exec-b", "sip-id-b"); err != nil { t.Fatal(err) } if err := mirror.Bind("exec-duplicate", "sip-id-a"); err == nil { t.Fatal("one SIP Call-ID cannot belong to two executions") } var wg sync.WaitGroup for _, tc := range []struct { id, branch, code string }{{"sip-id-a", "z9hG4bK-a", "480 Temporarily Unavailable"}, {"sip-id-b", "z9hG4bK-b", "486 Busy Here"}} { wg.Add(1) go func() { defer wg.Done() invite := hepTestSIP(tc.id, "INVITE", tc.branch, "INVITE sip:callee@localhost SIP/2.0") response := hepTestSIP(tc.id, "INVITE", tc.branch, "SIP/2.0 "+tc.code) if err := mirror.Observe(hepTestFrame(tc.id, response)); err != nil { // UDP may reorder the response. t.Error(err) } if err := mirror.Observe(hepTestFrame(tc.id, invite)); err != nil { t.Error(err) } }() } wg.Wait() for _, tc := range []struct { execID, want string }{{"exec-a", "SIP/2.0 480 Temporarily Unavailable"}, {"exec-b", "SIP/2.0 486 Busy Here"}} { ctx, cancel := context.WithTimeout(context.Background(), time.Second) resp, err := mirror.Final(ctx, tc.execID) cancel() if err != nil || resp.StatusLine != tc.want || !strings.HasPrefix(resp.Raw, tc.want+"\r\n") { t.Fatalf("wrong execution response: %+v err=%v", resp, err) } } } func TestHEPMirrorRejectsUnmatchedAndMalformedPacketsWithoutGuessing(t *testing.T) { mirror := NewHEPMirror() if err := mirror.Bind("exec-a", "sip-id-a"); err != nil { t.Fatal(err) } for _, bad := range [][]byte{nil, []byte("HEP3"), []byte("HEP3\x00\x06"), []byte("HEP3\x00\x0c\x00\x00\x00\x0f\x00\x00")} { if err := mirror.Observe(bad); err == nil { t.Fatalf("malformed frame accepted: len=%d", len(bad)) } } unrelated := hepTestSIP("sip-id-b", "INVITE", "z9hG4bK-b", "SIP/2.0 480 Temporarily Unavailable") if err := mirror.Observe(hepTestFrame("sip-id-b", unrelated)); err != nil { t.Fatalf("unrelated traffic is not a parser error: %v", err) } incorrectCID := hepTestSIP("sip-id-b", "INVITE", "z9hG4bK-b", "SIP/2.0 480 Temporarily Unavailable") if err := mirror.Observe(hepTestFrame("sip-id-a", incorrectCID)); err == nil { t.Fatal("payload Call-ID and HEP CID disagreed") } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() if resp, err := mirror.Final(ctx, "exec-a"); err == nil || resp != nil { t.Fatalf("missing observed response was fabricated: %+v err=%v", resp, err) } } func TestHEPMirrorKeepsOriginalInviteWhenLaterReinviteArrives(t *testing.T) { mirror := NewHEPMirror() if err := mirror.Bind("exec", "sip-id"); err != nil { t.Fatal(err) } for _, sip := range []string{ hepTestSIP("sip-id", "INVITE", "z9hG4bK-first", "INVITE sip:callee@localhost SIP/2.0"), hepTestSIP("sip-id", "INVITE", "z9hG4bK-first", "SIP/2.0 200 OK"), hepTestSIP("sip-id", "INVITE", "z9hG4bK-later", "INVITE sip:callee@localhost SIP/2.0"), hepTestSIP("sip-id", "INVITE", "z9hG4bK-later", "SIP/2.0 488 Not Acceptable Here"), } { if err := mirror.Observe(hepTestFrame("sip-id", sip)); err != nil { t.Fatal(err) } } resp, err := mirror.Final(context.Background(), "exec") if err != nil || resp.Code != 200 { t.Fatalf("later reINVITE must not replace original INVITE outcome: %+v %v", resp, err) } } func TestHEPMirrorNeverTreatsProvisionalOrOtherMethodsAsFinalINVITE(t *testing.T) { mirror := NewHEPMirror() if err := mirror.Bind("exec-a", "sip-id-a"); err != nil { t.Fatal(err) } for _, sip := range []string{ hepTestSIP("sip-id-a", "INVITE", "z9hG4bK-a", "INVITE sip:callee@localhost SIP/2.0"), hepTestSIP("sip-id-a", "INVITE", "z9hG4bK-a", "SIP/2.0 180 Ringing"), hepTestSIP("sip-id-a", "BYE", "z9hG4bK-a", "SIP/2.0 200 OK"), } { if err := mirror.Observe(hepTestFrame("sip-id-a", sip)); err != nil { t.Fatal(err) } } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Millisecond) defer cancel() if resp, err := mirror.Final(ctx, "exec-a"); err == nil || resp != nil { t.Fatalf("non-final or non-INVITE response became an outcome: %+v err=%v", resp, err) } }