1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.services.telephony.rcs;
18 
19 import static com.android.internal.telephony.TelephonyStatsLog.SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING;
20 
21 import static junit.framework.Assert.assertEquals;
22 import static junit.framework.Assert.assertNotNull;
23 import static junit.framework.Assert.assertTrue;
24 
25 import static org.mockito.Mockito.eq;
26 import static org.mockito.Mockito.verify;
27 
28 import android.net.Uri;
29 import android.telephony.ims.SipMessage;
30 import android.util.Base64;
31 
32 import androidx.test.ext.junit.runners.AndroidJUnit4;
33 
34 import com.android.internal.telephony.metrics.RcsStats;
35 
36 import org.junit.Before;
37 import org.junit.Test;
38 import org.junit.runner.RunWith;
39 import org.mockito.Mock;
40 import org.mockito.MockitoAnnotations;
41 
42 import java.nio.ByteBuffer;
43 import java.util.Arrays;
44 import java.util.Collections;
45 import java.util.Set;
46 import java.util.stream.Collectors;
47 
48 @RunWith(AndroidJUnit4.class)
49 public class SipSessionTrackerTest {
50 
51     private class DialogAttributes {
52         public final String branchId;
53         public final String callId;
54         public final String fromHeader;
55         public final String fromTag;
56         public final String toUri;
57         public final String toHeader;
58         private final String mFromUri;
59         // This may be populated later.
60         public String toTag;
61 
DialogAttributes()62         DialogAttributes() {
63             branchId = getNextString();
64             callId = getNextString();
65             mFromUri = generateRandomSipUri();
66             fromHeader = generateContactUri(mFromUri);
67             fromTag = getNextString();
68             toUri = generateRandomSipUri();
69             toHeader = generateContactUri(toUri);
70         }
71 
DialogAttributes(String branchId, String callId, String fromUri, String fromTag, String toUri, String toTag)72         private DialogAttributes(String branchId, String callId, String fromUri,
73                 String fromTag, String toUri, String toTag) {
74             this.branchId = branchId;
75             this.callId = callId;
76             this.mFromUri = fromUri;
77             this.fromHeader = generateContactUri(fromUri);
78             this.fromTag = fromTag;
79             this.toUri = toUri;
80             this.toHeader = generateContactUri(toUri);
81             this.toTag = toTag;
82         }
83 
setToTag()84         public void setToTag() {
85             if (toTag == null) {
86                 toTag = getNextString();
87             }
88         }
89 
fromExisting()90         public DialogAttributes fromExisting() {
91             return new DialogAttributes(branchId, callId, mFromUri, fromTag, toUri, null);
92         }
93 
invertFromTo()94         public DialogAttributes invertFromTo() {
95             return new DialogAttributes(branchId, callId, toUri, fromTag, mFromUri, toTag);
96         }
97     }
98 
99     // Keep track of the string entry so we can generate unique strings.
100     private int mStringEntryCounter = 0;
101     private SipSessionTracker mTrackerUT;
102     private static final int TEST_SUB_ID = 1;
103     private static final String TEST_INVITE_SIP_METHOD = "INVITE";
104     private static final int TEST_SIP_RESPONSE_CODE = 200;
105     private static final int TEST_SIP_CLOSE_RESPONSE_CODE = 0;
106     @Mock
107     private RcsStats mRcsStats;
108 
109     @Before
setUp()110     public void setUp() {
111         mStringEntryCounter = 0;
112         MockitoAnnotations.initMocks(this);
113         mTrackerUT = new SipSessionTracker(TEST_SUB_ID, mRcsStats);
114     }
115 
116     @Test
testMetricsEndedGracefullyBye()117     public void testMetricsEndedGracefullyBye() {
118         DialogAttributes attr = new DialogAttributes();
119         // INVITE
120         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
121         filterMessage(inviteRequest, attr);
122         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
123         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
124 
125         // confirmed dialog
126         attr.setToTag();
127         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
128         filterMessage(inviteConfirm, attr);
129         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
130         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
131 
132         // Gracefully Ended
133         SipMessage inviteClose = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr);
134         filterMessage(inviteClose, attr);
135 
136         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
137         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
138         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr);
139 
140         // verify Metrics information
141         verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
142                 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(true));
143     }
144 
145     @Test
testMetricsCloseCleanupSession()146     public void testMetricsCloseCleanupSession() {
147         //mTrackerUT.setRcsStats(mRcsStats);
148         DialogAttributes attr = new DialogAttributes();
149         // INVITE A -> B
150         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
151         filterMessage(inviteRequest, attr);
152         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
153         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
154 
155         // confirmed dialog
156         attr.setToTag();
157         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
158         filterMessage(inviteConfirm, attr);
159         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
160         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
161 
162         //forcefully close session
163         mTrackerUT.cleanupSession(attr.callId);
164         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
165         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
166         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
167 
168         // verify Metrics information
169         verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
170                 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false));
171     }
172 
173     @Test
testMetricsCloseClearAllSessions()174     public void testMetricsCloseClearAllSessions() {
175         //mTrackerUT.setRcsStats(mRcsStats);
176         DialogAttributes attr = new DialogAttributes();
177 
178         // INVITE
179         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
180         filterMessage(inviteRequest, attr);
181         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
182         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
183 
184         // confirmed dialog
185         attr.setToTag();
186         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
187         filterMessage(inviteConfirm, attr);
188         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
189         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
190 
191         //forcefully close session
192         mTrackerUT.clearAllSessions();
193         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
194         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
195         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
196 
197         // verify Metrics information
198         verify(mRcsStats).onSipTransportSessionClosed(eq(TEST_SUB_ID), eq(attr.callId),
199                 eq(TEST_SIP_CLOSE_RESPONSE_CODE), eq(false));
200     }
201 
202     @Test
testEarlyDialogToConfirmed()203     public void testEarlyDialogToConfirmed() {
204         DialogAttributes attr = new DialogAttributes();
205         // INVITE A -> B
206         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
207         filterMessage(inviteRequest, attr);
208         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
209         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
210         // 100 TRYING A <- proxy
211         SipMessage inviteTrying = generateSipResponse("100", "Trying", attr);
212         filterMessage(inviteTrying, attr);
213         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
214         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
215         // INVITE proxy -> B
216         // (BOB generates To tag)
217         attr.setToTag();
218         // 180 RINGING proxy <- B
219         // 180 RINGING A <- proxy
220         SipMessage inviteRinging = generateSipResponse("180", "Ringing", attr);
221         filterMessage(inviteRinging, attr);
222         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
223         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attr);
224         // User answers phone
225         // 200 OK proxy <- B
226         // 200 OK A <- proxy
227         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
228         filterMessage(inviteConfirm, attr);
229         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
230         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
231     }
232 
233     @Test
testForkDialog()234     public void testForkDialog() {
235         DialogAttributes attrB1 = new DialogAttributes();
236         // INVITE A -> B
237         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attrB1);
238         filterMessage(inviteRequest, attrB1);
239         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
240         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1);
241         // INVITE proxy -> B
242         // (BOB generates To tag)
243         attrB1.setToTag();
244         // 180 RINGING proxy <- B1
245         // 180 RINGING A <- proxy
246         SipMessage inviteRingingB1 = generateSipResponse("180", "Ringing", attrB1);
247         filterMessage(inviteRingingB1, attrB1);
248         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
249         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1);
250         // Now get another RINGING indication from another device associated with the same user.
251         // 180 RINGING proxy <- B2
252         // 180 RINGING A <- proxy
253         DialogAttributes attrB2 = attrB1.fromExisting();
254         // set different To tag
255         attrB2.setToTag();
256         SipMessage inviteRingingB2 = generateSipResponse("180", "Ringing", attrB2);
257         filterMessage(inviteRingingB2, attrB2);
258         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
259         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB1, attrB2);
260         // User answers B1
261         // 200 OK proxy <- B1
262         // 200 OK A <- proxy
263         SipMessage inviteConfirm = generateSipResponse("200", "OK", attrB1);
264         filterMessage(inviteConfirm, attrB1);
265         verifyContainsCallIds(mTrackerUT.getEarlyDialogs(), attrB2);
266         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1);
267         // Receive indication that B2 is terminated because user answered on B1
268         // 487 A <- proxy
269         SipMessage terminatedResponse = generateSipResponse("487",
270                 "Request Terminated", attrB2);
271         filterMessage(terminatedResponse, attrB2);
272         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
273         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attrB1);
274         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB2);
275         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attrB1);
276         // Send BYE request for the open dialog.
277         filterMessage(byeRequest, attrB1);
278         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
279         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
280         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attrB1, attrB2);
281         // Clean up the session and ensure the close dialog is completely removed from the tracker.
282         mTrackerUT.cleanupSession(attrB1.callId);
283         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
284         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
285         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
286     }
287 
288     @Test
testCloseLocalDialog()289     public void testCloseLocalDialog() {
290         DialogAttributes attr = new DialogAttributes();
291         attr.setToTag();
292         createConfirmedDialog(attr);
293         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
294         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
295 
296         // Send BYE request for a dialog that was started locally and ensure that we see the call id
297         // move to the closed list.
298         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr);
299         filterMessage(byeRequest, attr);
300         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
301         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
302         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr);
303         // Clean up the session and ensure the close dialog is completely removed from the tracker.
304         mTrackerUT.cleanupSession(attr.callId);
305         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
306         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
307         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
308     }
309 
310     @Test
testAcceptContactFts()311     public void testAcceptContactFts() {
312         DialogAttributes attr = new DialogAttributes();
313         attr.setToTag();
314         SipMessage inviteRequest = generateSipRequest(
315                 SipMessageUtils.INVITE_SIP_METHOD,
316                 attr);
317         // add accept contact header
318         inviteRequest = new SipMessage(inviteRequest.getStartLine(),
319                 inviteRequest.getHeaderSection() + "\nAccept-Contact:*;+test",
320                 new byte[0]);
321         filterMessage(inviteRequest, attr);
322         assertTrue(mTrackerUT.getCallIdsAssociatedWithFeatureTag(Collections.singleton("+test"))
323                 .contains(attr.callId));
324     }
325 
326     @Test
testCloseRemoteDialog()327     public void testCloseRemoteDialog() {
328         DialogAttributes remoteAttr = new DialogAttributes();
329         remoteAttr.setToTag();
330         createConfirmedDialog(remoteAttr);
331         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
332         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), remoteAttr);
333 
334         // Send BYE request on a dialog that was started from the remote party.
335         DialogAttributes localAttr = remoteAttr.invertFromTo();
336         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, localAttr);
337         filterMessage(byeRequest, localAttr);
338         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
339         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
340         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), remoteAttr);
341         // Clean up the session and ensure the dialog is completely removed from the tracker.
342         mTrackerUT.cleanupSession(remoteAttr.callId);
343         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
344         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
345         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
346     }
347 
348     @Test
testCleanupConfirmedDialog()349     public void testCleanupConfirmedDialog() {
350         DialogAttributes attr = new DialogAttributes();
351         attr.setToTag();
352         createConfirmedDialog(attr);
353         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
354         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
355         // Clean up the session and ensure the dialog is completely removed from the tracker.
356         mTrackerUT.cleanupSession(attr.callId);
357         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
358         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
359         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
360     }
361 
362     @Test
testMultipleDialogs()363     public void testMultipleDialogs() {
364         DialogAttributes attr1 = new DialogAttributes();
365         createConfirmedDialog(attr1);
366         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
367         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1);
368         // add a second dialog
369         DialogAttributes attr2 = new DialogAttributes();
370         createConfirmedDialog(attr2);
371         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
372         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr1, attr2);
373         // Send BYE request on dialogs
374         SipMessage byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr1);
375         filterMessage(byeRequest, attr1);
376         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
377         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr2);
378         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr1);
379         mTrackerUT.cleanupSession(attr1.callId);
380         // Send BYE request on dialogs
381         byeRequest = generateSipRequest(SipMessageUtils.BYE_SIP_METHOD, attr2);
382         filterMessage(byeRequest, attr2);
383         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
384         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
385         verifyContainsCallIds(mTrackerUT.getClosedDialogs(), attr2);
386         mTrackerUT.cleanupSession(attr2.callId);
387         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
388         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
389         assertTrue(mTrackerUT.getClosedDialogs().isEmpty());
390     }
391 
392     @Test
testAcknowledgeMessageFailed()393     public void testAcknowledgeMessageFailed() {
394         DialogAttributes attr = new DialogAttributes();
395         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
396         mTrackerUT.filterSipMessage(
397                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest);
398         // Do not acknowledge the request and ensure that the operation has not been applied yet.
399         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
400         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
401         // send message ack failed event, the operation shouldn't have been applied
402         mTrackerUT.pendingMessageFailed(attr.branchId);
403         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
404         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
405     }
406 
407     @Test
testAcknowledgeBatchEvents()408     public void testAcknowledgeBatchEvents() {
409         DialogAttributes attr = new DialogAttributes();
410         SipMessage inviteRequest = generateSipRequest(SipMessageUtils.INVITE_SIP_METHOD, attr);
411         attr.setToTag();
412         SipMessage inviteConfirm = generateSipResponse("200", "OK", attr);
413         // We unexpectedly received two filter requests for the same branchId without
414         // acknowledgePendingMessage being called in between. Ensure that when it is called, it
415         // applies both operations.
416         mTrackerUT.filterSipMessage(
417                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteRequest);
418         mTrackerUT.filterSipMessage(
419                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, inviteConfirm);
420         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
421         assertTrue(mTrackerUT.getConfirmedDialogs().isEmpty());
422         // we should skip right to confirmed as both operations run back-to-back
423         mTrackerUT.acknowledgePendingMessage(attr.branchId);
424         assertTrue(mTrackerUT.getEarlyDialogs().isEmpty());
425         verifyContainsCallIds(mTrackerUT.getConfirmedDialogs(), attr);
426     }
427 
filterMessage(SipMessage m, DialogAttributes attr)428     private void filterMessage(SipMessage m, DialogAttributes attr) {
429         mTrackerUT.filterSipMessage(
430                 SIP_TRANSPORT_SESSION__SIP_MESSAGE_DIRECTION__OUTGOING, m);
431         mTrackerUT.acknowledgePendingMessage(attr.branchId);
432     }
verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs)433     private void verifyContainsCallIds(Set<SipDialog> callIdSet, DialogAttributes... attrs) {
434         Set<String> callIds = Arrays.stream(attrs).map(a -> a.callId).collect(
435                 Collectors.toSet());
436         assertTrue(callIdSet.stream().map(SipDialog::getCallId).collect(Collectors.toSet())
437                 .containsAll(callIds));
438     }
439 
generateSipRequest(String requestMethod, DialogAttributes attr)440     private SipMessage generateSipRequest(String requestMethod,
441             DialogAttributes attr) {
442         return SipMessageUtils.generateSipRequest(requestMethod, attr.fromHeader, attr.toHeader,
443                 attr.toUri, attr.branchId, attr.callId, attr.fromTag, attr.toTag);
444     }
generateSipResponse(String statusCode, String statusString, DialogAttributes attr)445     private SipMessage generateSipResponse(String statusCode, String statusString,
446             DialogAttributes attr) {
447         return SipMessageUtils.generateSipResponse(statusCode, statusString, attr.fromHeader,
448                 attr.toHeader, attr.branchId, attr.callId, attr.fromTag, attr.toTag);
449     }
450 
generateContactUri(String sipUri)451     private String generateContactUri(String sipUri) {
452         Uri uri = Uri.parse(sipUri);
453         assertNotNull(uri);
454         String[] user = uri.getSchemeSpecificPart().split("@", 2);
455         assertNotNull(user);
456         assertEquals(2, user.length);
457         return user[0] + " <" + sipUri + ">";
458     }
459 
generateRandomSipUri()460     private String generateRandomSipUri() {
461         return "sip:" + getNextString() + "@" + SipMessageUtils.BASE_ADDRESS;
462     }
463 
createConfirmedDialog(DialogAttributes attr)464     private void createConfirmedDialog(DialogAttributes attr) {
465         // INVITE ALICE -> BOB
466         SipMessage inviteRequest = generateSipRequest(
467                 SipMessageUtils.INVITE_SIP_METHOD,
468                 attr);
469         filterMessage(inviteRequest, attr);
470         attr.setToTag();
471         // skip to confirmed state for test.
472         SipMessage inviteConfirm = generateSipResponse("200", "OK",
473                 attr);
474         filterMessage(inviteConfirm, attr);
475     }
476 
getNextString()477     private String getNextString() {
478         // Get a string representation of the entry counter
479         byte[] idByteArray = ByteBuffer.allocate(4).putInt(mStringEntryCounter++).array();
480         return Base64.encodeToString(idByteArray,
481                 Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
482     }
483 }
484