sms_runtime_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. package ims
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "strings"
  9. "testing"
  10. "time"
  11. "vocat/internal/vowifi"
  12. )
  13. type smsTestAKA struct{ *recordingAKA }
  14. func (smsTestAKA) ReadSMSCenter(context.Context, string) (string, error) {
  15. return "+447785016005", nil
  16. }
  17. func TestSessionReceivesAndAcknowledgesSMSOverIMS(t *testing.T) {
  18. listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
  19. if err != nil {
  20. t.Fatal(err)
  21. }
  22. defer listener.Close()
  23. _ = listener.SetDeadline(time.Now().Add(10 * time.Second))
  24. received := make(chan ReceivedSMS, 1)
  25. serverDone := make(chan error, 1)
  26. nonce := base64.StdEncoding.EncodeToString(make([]byte, 32))
  27. go func() { serverDone <- serveInboundSMS(listener, nonce) }()
  28. provider, err := NewProvider(
  29. smsTestAKA{&recordingAKA{result: vowifi.AKAResult{RES: []byte{1, 2, 3, 4}}}},
  30. Config{
  31. PCSCF: listener.LocalAddr().String(), LocalAddress: "127.0.0.1",
  32. Transport: "udp", TransactionTimeout: 3 * time.Second, SecurityMode: SecurityDisabled,
  33. OnSMS: func(_ context.Context, message ReceivedSMS) error {
  34. received <- message
  35. return nil
  36. },
  37. },
  38. )
  39. if err != nil {
  40. t.Fatal(err)
  41. }
  42. session, err := provider.Start(context.Background(), vowifi.IMSRequest{
  43. DeviceID: "ec20",
  44. Identity: vowifi.SIMIdentity{IMSI: "001010123456789", HomeMCC: "001", HomeMNC: "01"},
  45. Tunnel: evidenceTunnel{evidence: vowifi.TunnelEvidence{
  46. Established: true, LocalIPv4: "127.0.0.1", PCSCF: []string{listener.LocalAddr().String()},
  47. }},
  48. })
  49. if err != nil {
  50. t.Fatal(err)
  51. }
  52. select {
  53. case message := <-received:
  54. if message.From != "+12345" || message.Text != "HELLO" ||
  55. message.MessageID != "ims:network-deliver-1:42" ||
  56. message.ServiceCenterTimestamp == nil || message.Timestamp.IsZero() {
  57. t.Fatalf("received = %#v", message)
  58. }
  59. case <-time.After(5 * time.Second):
  60. t.Fatal("timed out waiting for inbound SMS")
  61. }
  62. if err := session.Close(context.Background()); err != nil {
  63. t.Fatal(err)
  64. }
  65. if err := <-serverDone; err != nil {
  66. t.Fatal(err)
  67. }
  68. }
  69. func TestRuntimeSecurityHeaders(t *testing.T) {
  70. verify := "ipsec-3gpp;alg=hmac-sha-1-96;prot=esp;mod=trans"
  71. headers := runtimeSecurityHeaders(true, verify)
  72. want := []string{
  73. "Security-Verify: " + verify,
  74. "Require: sec-agree",
  75. "Proxy-Require: sec-agree",
  76. }
  77. if len(headers) != len(want) {
  78. t.Fatalf("security header count = %d, want %d", len(headers), len(want))
  79. }
  80. for index := range want {
  81. if headers[index] != want[index] {
  82. t.Fatalf("security header %d = %q, want %q", index, headers[index], want[index])
  83. }
  84. }
  85. if headers := runtimeSecurityHeaders(false, verify); len(headers) != 0 {
  86. t.Fatalf("disabled security headers = %#v", headers)
  87. }
  88. }
  89. func TestSessionSendsSMSOverIMS(t *testing.T) {
  90. listener, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.ParseIP("127.0.0.1")})
  91. if err != nil {
  92. t.Fatal(err)
  93. }
  94. defer listener.Close()
  95. _ = listener.SetDeadline(time.Now().Add(10 * time.Second))
  96. serverDone := make(chan error, 1)
  97. statusReceived := make(chan ReceivedSMSStatus, 1)
  98. nonce := base64.StdEncoding.EncodeToString(make([]byte, 32))
  99. go func() { serverDone <- serveOutboundSMS(listener, nonce) }()
  100. provider, err := NewProvider(
  101. smsTestAKA{&recordingAKA{result: vowifi.AKAResult{RES: []byte{1, 2, 3, 4}}}},
  102. Config{
  103. PCSCF: listener.LocalAddr().String(), LocalAddress: "127.0.0.1",
  104. Transport: "udp", TransactionTimeout: 3 * time.Second, SecurityMode: SecurityDisabled,
  105. OnSMSStatus: func(_ context.Context, status ReceivedSMSStatus) error {
  106. statusReceived <- status
  107. return nil
  108. },
  109. },
  110. )
  111. if err != nil {
  112. t.Fatal(err)
  113. }
  114. session, err := provider.Start(context.Background(), vowifi.IMSRequest{
  115. DeviceID: "ec20",
  116. Identity: vowifi.SIMIdentity{IMSI: "001010123456789", HomeMCC: "001", HomeMNC: "01"},
  117. Tunnel: evidenceTunnel{evidence: vowifi.TunnelEvidence{
  118. Established: true, LocalIPv4: "127.0.0.1", PCSCF: []string{listener.LocalAddr().String()},
  119. }},
  120. })
  121. if err != nil {
  122. t.Fatal(err)
  123. }
  124. result, err := session.(vowifi.SMSSender).SendSMS(context.Background(), vowifi.SMSSubmitRequest{
  125. Recipient: "+12345", Text: "HELLO",
  126. })
  127. if err != nil || !result.AllPartsAccepted || result.PartsAccepted != 1 || result.PartResults[0].SIPCode != 202 {
  128. t.Fatalf("SendSMS = (%#v, %v)", result, err)
  129. }
  130. select {
  131. case status := <-statusReceived:
  132. if status.To != "+12345" || status.MessageReference != result.PartResults[0].Reference ||
  133. status.StatusCode != 0 || status.DeliveryStatus != "delivered" ||
  134. status.ServiceCenterTimestamp == nil || status.DischargeTimestamp == nil {
  135. t.Fatalf("SMS status = %#v", status)
  136. }
  137. case <-time.After(5 * time.Second):
  138. t.Fatal("timed out waiting for SMS delivery status")
  139. }
  140. if err := session.Close(context.Background()); err != nil {
  141. t.Fatal(err)
  142. }
  143. if err := <-serverDone; err != nil {
  144. t.Fatal(err)
  145. }
  146. }
  147. func serveInboundSMS(listener *net.UDPConn, nonce string) error {
  148. packet := make([]byte, 65535)
  149. count, remote, err := listener.ReadFromUDP(packet)
  150. if err != nil {
  151. return err
  152. }
  153. _, headers, err := parseTestRequest(packet[:count])
  154. if err != nil {
  155. return err
  156. }
  157. callID := headers["call-id"]
  158. if _, err = listener.WriteToUDP(testResponse(401, "Unauthorized", callID, headers["cseq"], []string{
  159. `WWW-Authenticate: Digest realm="ims.mnc001.mcc001.3gppnetwork.org", nonce="` + nonce + `", algorithm=AKAv1-MD5, qop="auth"`,
  160. }), remote); err != nil {
  161. return err
  162. }
  163. count, remote, err = listener.ReadFromUDP(packet)
  164. if err != nil {
  165. return err
  166. }
  167. _, headers, err = parseTestRequest(packet[:count])
  168. if err != nil {
  169. return err
  170. }
  171. if _, err = listener.WriteToUDP(testResponse(200, "OK", callID, headers["cseq"], []string{
  172. "Contact: " + headers["contact"] + ";expires=600",
  173. }), remote); err != nil {
  174. return err
  175. }
  176. tpdu := []byte{
  177. 0x04, 0x05, 0x91, 0x21, 0x43, 0xf5, 0x00, 0x00,
  178. 0x42, 0x10, 0x20, 0x30, 0x40, 0x50, 0x00, 0x05,
  179. 0xc8, 0x22, 0x93, 0xf9, 0x04,
  180. }
  181. rpdu := []byte{0x01, 0x2a, 0x00, 0x00, byte(len(tpdu))}
  182. rpdu = append(rpdu, tpdu...)
  183. request := []byte(strings.Join([]string{
  184. "MESSAGE sip:[email protected] SIP/2.0",
  185. "Via: SIP/2.0/UDP " + listener.LocalAddr().String() + ";branch=z9hG4bKdeliver",
  186. "From: <sip:[email protected]>;tag=gw",
  187. "To: <sip:[email protected]>",
  188. "P-Asserted-Identity: <sip:[email protected]>",
  189. "Call-ID: network-deliver-1",
  190. "CSeq: 1 MESSAGE",
  191. "Content-Type: application/vnd.3gpp.sms",
  192. fmt.Sprintf("Content-Length: %d", len(rpdu)), "", "",
  193. }, "\r\n"))
  194. request = append(request, rpdu...)
  195. if _, err = listener.WriteToUDP(request, remote); err != nil {
  196. return err
  197. }
  198. count, remote, err = listener.ReadFromUDP(packet)
  199. if err != nil {
  200. return err
  201. }
  202. response, err := parseSIPResponse(packet[:count])
  203. if err != nil || response.StatusCode != 200 {
  204. return fmt.Errorf("delivery SIP response = (%#v, %v)", response, err)
  205. }
  206. count, remote, err = listener.ReadFromUDP(packet)
  207. if err != nil {
  208. return err
  209. }
  210. report, err := parseSIPPacket(packet[:count])
  211. if err != nil || report.Request == nil {
  212. return fmt.Errorf("delivery report parse: %v", err)
  213. }
  214. if report.Request.Method != "MESSAGE" || report.Request.value("In-Reply-To") != "network-deliver-1" ||
  215. len(report.Request.Body) != 2 || report.Request.Body[0] != 0x02 || report.Request.Body[1] != 0x2a {
  216. return fmt.Errorf("unexpected delivery report %#v", report.Request)
  217. }
  218. if _, err = listener.WriteToUDP(testResponse(200, "OK", report.Request.value("Call-ID"), report.Request.value("CSeq"), nil), remote); err != nil {
  219. return err
  220. }
  221. count, remote, err = listener.ReadFromUDP(packet)
  222. if err != nil {
  223. return err
  224. }
  225. _, headers, err = parseTestRequest(packet[:count])
  226. if err != nil {
  227. return err
  228. }
  229. if headers["expires"] != "0" {
  230. return errors.New("expected deregistration")
  231. }
  232. _, err = listener.WriteToUDP(testResponse(200, "OK", callID, headers["cseq"], nil), remote)
  233. return err
  234. }
  235. func serveOutboundSMS(listener *net.UDPConn, nonce string) error {
  236. packet := make([]byte, 65535)
  237. count, remote, err := listener.ReadFromUDP(packet)
  238. if err != nil {
  239. return err
  240. }
  241. _, headers, err := parseTestRequest(packet[:count])
  242. if err != nil {
  243. return err
  244. }
  245. registerCallID := headers["call-id"]
  246. if _, err = listener.WriteToUDP(testResponse(401, "Unauthorized", registerCallID, headers["cseq"], []string{
  247. `WWW-Authenticate: Digest realm="ims.mnc001.mcc001.3gppnetwork.org", nonce="` + nonce + `", algorithm=AKAv1-MD5, qop="auth"`,
  248. }), remote); err != nil {
  249. return err
  250. }
  251. count, remote, err = listener.ReadFromUDP(packet)
  252. if err != nil {
  253. return err
  254. }
  255. _, headers, err = parseTestRequest(packet[:count])
  256. if err != nil {
  257. return err
  258. }
  259. if _, err = listener.WriteToUDP(testResponse(200, "OK", registerCallID, headers["cseq"], []string{
  260. "Contact: " + headers["contact"] + ";expires=600",
  261. }), remote); err != nil {
  262. return err
  263. }
  264. count, remote, err = listener.ReadFromUDP(packet)
  265. if err != nil {
  266. return err
  267. }
  268. message, err := parseSIPPacket(packet[:count])
  269. if err != nil || message.Request == nil {
  270. return fmt.Errorf("outbound MESSAGE parse: %v", err)
  271. }
  272. if message.Request.Method != "MESSAGE" || message.Request.URI != "tel:+447785016005" ||
  273. strings.ToLower(message.Request.value("Content-Type")) != smsContentType {
  274. return fmt.Errorf("unexpected outbound MESSAGE %#v", message.Request)
  275. }
  276. rpdu, err := parseRPDU(message.Request.Body)
  277. if err != nil || rpdu.messageType != 0 || len(rpdu.tpdu) != 0 {
  278. // parseRPDU intentionally decodes only network-to-MS RP-DATA; inspect
  279. // the mandatory MO prefix and TPDU length directly below.
  280. if err != nil {
  281. return err
  282. }
  283. }
  284. body := message.Request.Body
  285. if len(body) < 8 || body[0] != 0x00 || body[2] != 0x00 {
  286. return fmt.Errorf("invalid MO RP-DATA %x", body)
  287. }
  288. destinationLength := int(body[3])
  289. userLengthIndex := 4 + destinationLength
  290. if userLengthIndex >= len(body) || int(body[userLengthIndex]) != len(body)-userLengthIndex-1 {
  291. return fmt.Errorf("invalid MO RP-DATA lengths %x", body)
  292. }
  293. tpdu := body[userLengthIndex+1:]
  294. if len(tpdu) < 2 || tpdu[0]&0x03 != 1 || tpdu[0]&0x20 == 0 || tpdu[1] != body[1] {
  295. return fmt.Errorf("SMS-SUBMIT did not request a trackable status report: %x", tpdu)
  296. }
  297. if _, err = listener.WriteToUDP(testResponse(202, "Accepted", message.Request.value("Call-ID"), message.Request.value("CSeq"), nil), remote); err != nil {
  298. return err
  299. }
  300. statusTPDU := []byte{
  301. 0x02, tpdu[1], 0x05, 0x91, 0x21, 0x43, 0xf5,
  302. 0x42, 0x10, 0x20, 0x30, 0x40, 0x50, 0x00,
  303. 0x42, 0x10, 0x20, 0x30, 0x50, 0x50, 0x00,
  304. 0x00,
  305. }
  306. statusRPDU := []byte{0x01, 0x2b, 0x00, 0x00, byte(len(statusTPDU))}
  307. statusRPDU = append(statusRPDU, statusTPDU...)
  308. statusRequest := []byte(strings.Join([]string{
  309. "MESSAGE sip:[email protected] SIP/2.0",
  310. "Via: SIP/2.0/UDP " + listener.LocalAddr().String() + ";branch=z9hG4bKstatus",
  311. "From: <sip:[email protected]>;tag=gw",
  312. "To: <sip:[email protected]>",
  313. "P-Asserted-Identity: <sip:[email protected]>",
  314. "Call-ID: network-status-1",
  315. "CSeq: 2 MESSAGE",
  316. "Content-Type: application/vnd.3gpp.sms",
  317. fmt.Sprintf("Content-Length: %d", len(statusRPDU)), "", "",
  318. }, "\r\n"))
  319. statusRequest = append(statusRequest, statusRPDU...)
  320. if _, err = listener.WriteToUDP(statusRequest, remote); err != nil {
  321. return err
  322. }
  323. count, remote, err = listener.ReadFromUDP(packet)
  324. if err != nil {
  325. return err
  326. }
  327. statusResponse, err := parseSIPResponse(packet[:count])
  328. if err != nil || statusResponse.StatusCode != 200 {
  329. return fmt.Errorf("status SIP response = (%#v, %v)", statusResponse, err)
  330. }
  331. count, remote, err = listener.ReadFromUDP(packet)
  332. if err != nil {
  333. return err
  334. }
  335. statusACK, err := parseSIPPacket(packet[:count])
  336. if err != nil || statusACK.Request == nil || statusACK.Request.value("In-Reply-To") != "network-status-1" ||
  337. len(statusACK.Request.Body) != 2 || statusACK.Request.Body[0] != 0x02 || statusACK.Request.Body[1] != 0x2b {
  338. return fmt.Errorf("unexpected status RP-ACK %#v (%v)", statusACK.Request, err)
  339. }
  340. if _, err = listener.WriteToUDP(testResponse(200, "OK", statusACK.Request.value("Call-ID"), statusACK.Request.value("CSeq"), nil), remote); err != nil {
  341. return err
  342. }
  343. count, remote, err = listener.ReadFromUDP(packet)
  344. if err != nil {
  345. return err
  346. }
  347. _, headers, err = parseTestRequest(packet[:count])
  348. if err != nil {
  349. return err
  350. }
  351. if headers["expires"] != "0" {
  352. return errors.New("expected deregistration")
  353. }
  354. _, err = listener.WriteToUDP(testResponse(200, "OK", registerCallID, headers["cseq"], nil), remote)
  355. return err
  356. }