1 /* 2 * Copyright (C) 2023 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 package com.android.server.wifi.entitlement; 17 18 import android.annotation.NonNull; 19 20 import com.android.internal.annotations.VisibleForTesting; 21 22 import java.time.Duration; 23 import java.time.Instant; 24 25 /** The Imsi Pseudonym information*/ 26 public class PseudonymInfo { 27 @VisibleForTesting 28 static final long DEFAULT_PSEUDONYM_TTL_IN_MILLIS = Duration.ofDays(2).toMillis(); 29 @VisibleForTesting 30 static final long REFRESH_AHEAD_TIME_IN_MILLIS = Duration.ofMinutes(30).toMillis(); 31 static final long MINIMUM_REFRESH_INTERVAL_IN_MILLIS = Duration.ofHours(12).toMillis(); 32 33 private final String mImsi; 34 private final String mPseudonym; 35 36 /* 37 * The number of milliseconds from the epoch of 1970-01-01T00:00:00Z when the pseudonym is 38 * received. 39 */ 40 private final long mTimeStamp; 41 42 /* 43 * Time To Live in milliseconds from the pseudonym is received. This is the maximum lifetime of 44 * a pseudonym. The pseudonym remains valid from the time it is received, until the mTtlInMillis 45 * has elapsed. 46 */ 47 private final long mTtlInMillis; 48 49 /* 50 * Refresh Ahead Time in milliseconds. When a pseudonym is expiring, we should refresh it ahead 51 * of time. For example, we refresh a pseudonym half an hour before it expires. If the TTL is 24 52 * hours, we should refresh this pseudonym when it is 23.5 hours old. 53 */ 54 private final long mRatInMillis; 55 56 /* 57 * Minimum Age To Refresh in milliseconds from the pseudonym is received. We should not 58 * refresh the pseudonym too frequently. 59 */ 60 private final long mMinAtrInMillis; 61 PseudonymInfo(@onNull String pseudonym, @NonNull String imsi)62 public PseudonymInfo(@NonNull String pseudonym, @NonNull String imsi) { 63 this(pseudonym, imsi, DEFAULT_PSEUDONYM_TTL_IN_MILLIS); 64 } 65 PseudonymInfo(@onNull String pseudonym, @NonNull String imsi, long ttlInMillis)66 public PseudonymInfo(@NonNull String pseudonym, @NonNull String imsi, long ttlInMillis) { 67 this(pseudonym, imsi, ttlInMillis, Instant.now().toEpochMilli()); 68 } 69 70 @VisibleForTesting PseudonymInfo(@onNull String pseudonym, @NonNull String imsi, long ttlInMillis, long timeStamp)71 public PseudonymInfo(@NonNull String pseudonym, @NonNull String imsi, long ttlInMillis, 72 long timeStamp) { 73 mPseudonym = pseudonym; 74 mImsi = imsi; 75 mTimeStamp = timeStamp; 76 mTtlInMillis = ttlInMillis; 77 mRatInMillis = Math.min(ttlInMillis / 2, REFRESH_AHEAD_TIME_IN_MILLIS); 78 mMinAtrInMillis = Math.min(ttlInMillis - mRatInMillis, MINIMUM_REFRESH_INTERVAL_IN_MILLIS); 79 } 80 getPseudonym()81 public String getPseudonym() { 82 return mPseudonym; 83 } 84 getImsi()85 public String getImsi() { 86 return mImsi; 87 } 88 89 /** 90 * Returns the Time To Live in milliseconds. 91 */ getTtlInMillis()92 public long getTtlInMillis() { 93 return mTtlInMillis; 94 } 95 96 /** Returns the Left Time To Refresh in milliseconds. */ getLttrInMillis()97 public long getLttrInMillis() { 98 long age = Instant.now().toEpochMilli() - mTimeStamp; 99 long leftTimeToLive = mTtlInMillis - age; 100 long leftTimeToRefresh = leftTimeToLive - mRatInMillis; 101 return leftTimeToRefresh > 0 ? leftTimeToRefresh : 0; 102 } 103 104 /** 105 * Returns whether the pseudonym has expired or not. 106 */ hasExpired()107 public boolean hasExpired() { 108 return Instant.now().toEpochMilli() - mTimeStamp >= mTtlInMillis; 109 } 110 111 /** 112 * Returns whether the pseudonym is old enough to refresh. To prevent DOS attack, the pseudonym 113 * should not be refreshed too frequently. 114 */ isOldEnoughToRefresh()115 public boolean isOldEnoughToRefresh() { 116 return Instant.now().toEpochMilli() - mTimeStamp >= mMinAtrInMillis; 117 } 118 119 @Override toString()120 public String toString() { 121 // Mask the pseudonym and IMSI which are two kinds of PII. 122 return " mPseudonym=" 123 + (mPseudonym.length() >= 7 ? (mPseudonym.substring(0, 7) + "***") : mPseudonym) 124 + " mImsi=***" 125 + (mImsi.length() >= 3 ? mImsi.substring(mImsi.length() - 3) : mImsi) 126 + " mTimeStamp=" 127 + mTimeStamp 128 + " mTtlInMillis=" 129 + mTtlInMillis 130 + " mRatInMillis=" 131 + mRatInMillis 132 + " mMinAtrInMillis=" 133 + mMinAtrInMillis; 134 } 135 } 136