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