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