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 
17 package com.android.server.connectivity.mdns;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 
22 /**
23  * The query scheduler class for calculating next query tasks parameters.
24  * <p>
25  * The class is not thread-safe and needs to be used on a consistent thread.
26  */
27 public class MdnsQueryScheduler {
28 
29     /**
30      * The argument for tracking the query tasks status.
31      */
32     public static class ScheduledQueryTaskArgs {
33         public final QueryTaskConfig config;
34         public final long timeToRun;
35         public final long minTtlExpirationTimeWhenScheduled;
36         public final long sessionId;
37 
ScheduledQueryTaskArgs(@onNull QueryTaskConfig config, long timeToRun, long minTtlExpirationTimeWhenScheduled, long sessionId)38         ScheduledQueryTaskArgs(@NonNull QueryTaskConfig config, long timeToRun,
39                 long minTtlExpirationTimeWhenScheduled, long sessionId) {
40             this.config = config;
41             this.timeToRun = timeToRun;
42             this.minTtlExpirationTimeWhenScheduled = minTtlExpirationTimeWhenScheduled;
43             this.sessionId = sessionId;
44         }
45     }
46 
47     @Nullable
48     private ScheduledQueryTaskArgs mLastScheduledQueryTaskArgs;
49 
MdnsQueryScheduler()50     public MdnsQueryScheduler() {
51     }
52 
53     /**
54      * Cancel the scheduled run. The method needed to be called when the scheduled task need to
55      * be canceled and rescheduling is not need.
56      */
cancelScheduledRun()57     public void cancelScheduledRun() {
58         mLastScheduledQueryTaskArgs = null;
59     }
60 
61     /**
62      * Calculates ScheduledQueryTaskArgs for rescheduling the current task. Returns null if the
63      * rescheduling is not necessary.
64      */
65     @Nullable
maybeRescheduleCurrentRun(long now, long minRemainingTtl, long lastSentTime, long sessionId)66     public ScheduledQueryTaskArgs maybeRescheduleCurrentRun(long now,
67             long minRemainingTtl, long lastSentTime, long sessionId) {
68         if (mLastScheduledQueryTaskArgs == null) {
69             return null;
70         }
71         if (!mLastScheduledQueryTaskArgs.config.shouldUseQueryBackoff()) {
72             return null;
73         }
74 
75         final long timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
76                 mLastScheduledQueryTaskArgs.config, now, minRemainingTtl, lastSentTime);
77 
78         if (timeToRun <= mLastScheduledQueryTaskArgs.timeToRun) {
79             return null;
80         }
81 
82         mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(mLastScheduledQueryTaskArgs.config,
83                 timeToRun,
84                 minRemainingTtl + now,
85                 sessionId);
86         return mLastScheduledQueryTaskArgs;
87     }
88 
89     /**
90      *  Calculates the ScheduledQueryTaskArgs for the next run.
91      */
92     @NonNull
scheduleNextRun( @onNull QueryTaskConfig currentConfig, long minRemainingTtl, long now, long lastSentTime, long sessionId)93     public ScheduledQueryTaskArgs scheduleNextRun(
94             @NonNull QueryTaskConfig currentConfig,
95             long minRemainingTtl,
96             long now,
97             long lastSentTime,
98             long sessionId) {
99         final QueryTaskConfig nextRunConfig = currentConfig.getConfigForNextRun();
100         final long timeToRun;
101         if (mLastScheduledQueryTaskArgs == null) {
102             timeToRun = now + nextRunConfig.delayUntilNextTaskWithoutBackoffMs;
103         } else {
104             timeToRun = calculateTimeToRun(mLastScheduledQueryTaskArgs,
105                     nextRunConfig, now, minRemainingTtl, lastSentTime);
106         }
107         mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(nextRunConfig, timeToRun,
108                 minRemainingTtl + now,
109                 sessionId);
110         return mLastScheduledQueryTaskArgs;
111     }
112 
113     /**
114      *  Calculates the ScheduledQueryTaskArgs for the initial run.
115      */
scheduleFirstRun(@onNull QueryTaskConfig taskConfig, long now, long minRemainingTtl, long currentSessionId)116     public ScheduledQueryTaskArgs scheduleFirstRun(@NonNull QueryTaskConfig taskConfig,
117             long now, long minRemainingTtl, long currentSessionId) {
118         mLastScheduledQueryTaskArgs = new ScheduledQueryTaskArgs(taskConfig, now /* timeToRun */,
119                 now + minRemainingTtl/* minTtlExpirationTimeWhenScheduled */,
120                 currentSessionId);
121         return mLastScheduledQueryTaskArgs;
122     }
123 
calculateTimeToRun(@onNull ScheduledQueryTaskArgs taskArgs, QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime)124     private static long calculateTimeToRun(@NonNull ScheduledQueryTaskArgs taskArgs,
125             QueryTaskConfig queryTaskConfig, long now, long minRemainingTtl, long lastSentTime) {
126         final long baseDelayInMs = queryTaskConfig.delayUntilNextTaskWithoutBackoffMs;
127         if (!queryTaskConfig.shouldUseQueryBackoff()) {
128             return lastSentTime + baseDelayInMs;
129         }
130         if (minRemainingTtl <= 0) {
131             // There's no service, or there is an expired service. In any case, schedule for the
132             // minimum time, which is the base delay.
133             return lastSentTime + baseDelayInMs;
134         }
135         // If the next TTL expiration time hasn't changed, then use previous calculated timeToRun.
136         if (lastSentTime < now
137                 && taskArgs.minTtlExpirationTimeWhenScheduled == now + minRemainingTtl) {
138             // Use the original scheduling time if the TTL has not changed, to avoid continuously
139             // rescheduling to 80% of the remaining TTL as time passes
140             return taskArgs.timeToRun;
141         }
142         return Math.max(now + (long) (0.8 * minRemainingTtl), lastSentTime + baseDelayInMs);
143     }
144 }
145