mirror of
https://github.com/Telecominfraproject/wlan-cloud-rrm.git
synced 2025-10-30 02:02:28 +00:00
Compare commits
21 Commits
v2.7.0
...
token_refr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb30ce7e4 | ||
|
|
350a45b616 | ||
|
|
52dae760d8 | ||
|
|
343fc7b6ee | ||
|
|
2a952f56a9 | ||
|
|
52a2258c2d | ||
|
|
0b4fd49627 | ||
|
|
d81df03637 | ||
|
|
594fd9fa91 | ||
|
|
8c48a8901b | ||
|
|
0ac189f493 | ||
|
|
df21d07ec9 | ||
|
|
01a070c9b7 | ||
|
|
5211eae7c6 | ||
|
|
fafbda0bd8 | ||
|
|
43c9aaafb2 | ||
|
|
89e637cfeb | ||
|
|
0a64fb4963 | ||
|
|
4191bc1a70 | ||
|
|
3b6e83d103 | ||
|
|
27c36ff444 |
@@ -9,7 +9,7 @@ fullnameOverride: ""
|
||||
images:
|
||||
owrrm:
|
||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owrrm
|
||||
tag: v2.7.0
|
||||
tag: main
|
||||
pullPolicy: Always
|
||||
# regcred:
|
||||
# registry: tip-tip-wlan-cloud-ucentral.jfrog.io
|
||||
|
||||
@@ -478,8 +478,10 @@ components:
|
||||
RRMSchedule:
|
||||
type: object
|
||||
properties:
|
||||
cron:
|
||||
type: string
|
||||
crons:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
algorithms:
|
||||
type: array
|
||||
items:
|
||||
|
||||
@@ -143,6 +143,9 @@ public class RRMAlgorithm {
|
||||
* @param dryRun if set, do not apply changes
|
||||
* @param allowDefaultMode if false, "mode" argument must be present and
|
||||
* valid (returns error if invalid)
|
||||
* @param updateImmediately true if the method should queue the zone for
|
||||
* update and interrupt the config manager thread
|
||||
* to trigger immediate update
|
||||
*
|
||||
* @return the algorithm result, with exactly one field set ("error" upon
|
||||
* failure, any others upon success)
|
||||
@@ -153,7 +156,8 @@ public class RRMAlgorithm {
|
||||
Modeler modeler,
|
||||
String zone,
|
||||
boolean dryRun,
|
||||
boolean allowDefaultMode
|
||||
boolean allowDefaultMode,
|
||||
boolean updateImmediately
|
||||
) {
|
||||
AlgorithmResult result = new AlgorithmResult();
|
||||
if (name == null || args == null) {
|
||||
@@ -212,11 +216,14 @@ public class RRMAlgorithm {
|
||||
}
|
||||
result.channelMap = optimizer.computeChannelMap();
|
||||
if (!dryRun) {
|
||||
optimizer.applyConfig(
|
||||
optimizer.updateDeviceApConfig(
|
||||
deviceDataManager,
|
||||
configManager,
|
||||
result.channelMap
|
||||
);
|
||||
if (updateImmediately) {
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
|
||||
@@ -270,11 +277,14 @@ public class RRMAlgorithm {
|
||||
}
|
||||
result.txPowerMap = optimizer.computeTxPowerMap();
|
||||
if (!dryRun) {
|
||||
optimizer.applyConfig(
|
||||
optimizer.updateDeviceApConfig(
|
||||
deviceDataManager,
|
||||
configManager,
|
||||
result.txPowerMap
|
||||
);
|
||||
if (updateImmediately) {
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.error = String.format("Unknown algorithm: '%s'", name);
|
||||
|
||||
@@ -232,7 +232,7 @@ public class RRMConfig {
|
||||
* The main logic loop interval (i.e. sleep time), in ms
|
||||
* ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS})
|
||||
*/
|
||||
public int updateIntervalMs = 5000;
|
||||
public int updateIntervalMs = 30000; // 30sec
|
||||
|
||||
/**
|
||||
* The expected device statistics interval, in seconds (or -1 to
|
||||
@@ -246,13 +246,13 @@ public class RRMConfig {
|
||||
* automatic scans)
|
||||
* ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC})
|
||||
*/
|
||||
public int wifiScanIntervalSec = 900;
|
||||
public int wifiScanIntervalSec = 900; // 15min
|
||||
|
||||
/**
|
||||
* The capabilities request interval (per device), in seconds
|
||||
* ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC})
|
||||
*/
|
||||
public int capabilitiesIntervalSec = 3600;
|
||||
public int capabilitiesIntervalSec = 3600; // 1hr
|
||||
|
||||
/**
|
||||
* Number of executor threads for async tasks (ex. wifi scans)
|
||||
@@ -273,7 +273,7 @@ public class RRMConfig {
|
||||
* The main logic loop interval (i.e. sleep time), in ms
|
||||
* ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS})
|
||||
*/
|
||||
public int updateIntervalMs = 60000;
|
||||
public int updateIntervalMs = 120000; // 2min
|
||||
|
||||
/**
|
||||
* Enable pushing device config changes?
|
||||
@@ -363,7 +363,7 @@ public class RRMConfig {
|
||||
* Sync interval, in ms, for owprov venue information etc.
|
||||
* ({@code PROVMONITORPARAMS_SYNCINTERVALMS})
|
||||
*/
|
||||
public int syncIntervalMs = 300000;
|
||||
public int syncIntervalMs = 300000; // 5min
|
||||
}
|
||||
|
||||
/** ProvMonitor parameters. */
|
||||
|
||||
@@ -19,9 +19,9 @@ public class RRMSchedule {
|
||||
*
|
||||
* This field expects a cron-like format as defined by the Quartz Job
|
||||
* Scheduler (CronTrigger):
|
||||
* https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html
|
||||
* https://www.quartz-scheduler.org/documentation/quartz-2.4.0/tutorials/crontrigger.html
|
||||
*/
|
||||
public String cron;
|
||||
public List<String> crons;
|
||||
|
||||
/**
|
||||
* The list of RRM algorithms to run.
|
||||
|
||||
@@ -302,12 +302,13 @@ public class ApiServer implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an OpenWiFi token (external), caching successful lookups.
|
||||
* Validate an OpenWiFi token (external), caching successful lookups. This will
|
||||
* validate a USER token - subscriber token won't work and will fail (plus only
|
||||
* users should be dealing with RRM).
|
||||
* @return true if token is valid
|
||||
*/
|
||||
private boolean validateOpenWifiToken(String token) {
|
||||
// The below only checks /api/v1/validateToken and caches it as necessary.
|
||||
// TODO - /api/v1/validateSubToken still has to be implemented.
|
||||
Long expiry = tokenCache.get(token);
|
||||
if (expiry == null) {
|
||||
TokenValidationResult result = client.validateToken(token);
|
||||
@@ -711,7 +712,8 @@ public class ApiServer implements Runnable {
|
||||
modeler,
|
||||
venue,
|
||||
mock,
|
||||
true /* allowDefaultMode */
|
||||
true, /* allowDefaultMode */
|
||||
true /* updateImmediately */
|
||||
);
|
||||
if (result.error != null) {
|
||||
response.status(400);
|
||||
@@ -917,7 +919,7 @@ public class ApiServer implements Runnable {
|
||||
DeviceConfig networkConfig =
|
||||
gson.fromJson(request.body(), DeviceConfig.class);
|
||||
deviceDataManager.setDeviceNetworkConfig(networkConfig);
|
||||
configManager.wakeUp();
|
||||
configManager.queueAllZonesAndWakeUp();
|
||||
|
||||
// Revalidate data model
|
||||
modeler.revalidate();
|
||||
@@ -981,7 +983,7 @@ public class ApiServer implements Runnable {
|
||||
DeviceConfig zoneConfig =
|
||||
gson.fromJson(request.body(), DeviceConfig.class);
|
||||
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
|
||||
configManager.wakeUp();
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
|
||||
// Revalidate data model
|
||||
modeler.revalidate();
|
||||
@@ -1044,7 +1046,10 @@ public class ApiServer implements Runnable {
|
||||
DeviceConfig apConfig =
|
||||
gson.fromJson(request.body(), DeviceConfig.class);
|
||||
deviceDataManager.setDeviceApConfig(serialNumber, apConfig);
|
||||
configManager.wakeUp();
|
||||
// TODO enable updates to device(s), not just the entire zone
|
||||
final String zone =
|
||||
deviceDataManager.getDeviceZone(serialNumber);
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
|
||||
// Revalidate data model
|
||||
modeler.revalidate();
|
||||
@@ -1117,7 +1122,10 @@ public class ApiServer implements Runnable {
|
||||
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
|
||||
.apply(apConfig);
|
||||
});
|
||||
configManager.wakeUp();
|
||||
final String zone =
|
||||
deviceDataManager.getDeviceZone(serialNumber);
|
||||
// TODO enable updates to device(s), not just the entire zone
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
|
||||
// Revalidate data model
|
||||
modeler.revalidate();
|
||||
@@ -1260,7 +1268,8 @@ public class ApiServer implements Runnable {
|
||||
modeler,
|
||||
zone,
|
||||
dryRun,
|
||||
false /* allowDefaultMode */
|
||||
false, /* allowDefaultMode */
|
||||
true /* updateImmediately */
|
||||
);
|
||||
if (result.error != null) {
|
||||
response.status(400);
|
||||
@@ -1371,7 +1380,8 @@ public class ApiServer implements Runnable {
|
||||
modeler,
|
||||
zone,
|
||||
dryRun,
|
||||
false /* allowDefaultMode */
|
||||
false, /* allowDefaultMode */
|
||||
true /* updateImmediately */
|
||||
);
|
||||
if (result.error != null) {
|
||||
response.status(400);
|
||||
|
||||
@@ -10,9 +10,12 @@ package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
@@ -63,8 +66,11 @@ public class ConfigManager implements Runnable {
|
||||
/** Is the main thread sleeping? */
|
||||
private final AtomicBoolean sleepingFlag = new AtomicBoolean(false);
|
||||
|
||||
/** Was a manual config update requested? */
|
||||
private final AtomicBoolean eventFlag = new AtomicBoolean(false);
|
||||
/**
|
||||
* Thread-safe set of zones for which manual config updates have been
|
||||
* requested.
|
||||
*/
|
||||
private Set<String> zonesToUpdate = ConcurrentHashMap.newKeySet();
|
||||
|
||||
/** Config listener interface. */
|
||||
public interface ConfigListener {
|
||||
@@ -165,6 +171,7 @@ public class ConfigManager implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// Fetch device list
|
||||
List<DeviceWithStatus> devices = client.getDevices();
|
||||
@@ -180,7 +187,10 @@ public class ConfigManager implements Runnable {
|
||||
List<String> devicesNeedingUpdate = new ArrayList<>();
|
||||
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
||||
params.configDebounceIntervalSec * 1_000_000_000L;
|
||||
final boolean isEvent = eventFlag.getAndSet(false);
|
||||
Set<String> zonesToUpdateCopy = new HashSet<>(zonesToUpdate);
|
||||
// use removeAll() instead of clear() in case items are added between
|
||||
// the previous line and the following line
|
||||
zonesToUpdate.removeAll(zonesToUpdateCopy);
|
||||
for (DeviceWithStatus device : devices) {
|
||||
// Update config structure
|
||||
DeviceData data = deviceDataMap.computeIfAbsent(
|
||||
@@ -201,11 +211,13 @@ public class ConfigManager implements Runnable {
|
||||
for (ConfigListener listener : configListeners.values()) {
|
||||
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
||||
}
|
||||
|
||||
// Check event flag
|
||||
// Check if there are requested updates for this zone
|
||||
String deviceZone =
|
||||
deviceDataManager.getDeviceZone(device.serialNumber);
|
||||
boolean isEvent = zonesToUpdateCopy.contains(deviceZone);
|
||||
if (params.configOnEventOnly && !isEvent) {
|
||||
logger.debug(
|
||||
"Skipping config for {} (event flag not set)",
|
||||
"Skipping config for {} (zone not marked for updates)",
|
||||
device.serialNumber
|
||||
);
|
||||
continue;
|
||||
@@ -251,15 +263,16 @@ public class ConfigManager implements Runnable {
|
||||
}
|
||||
}
|
||||
|
||||
final boolean shouldUpdate = !zonesToUpdateCopy.isEmpty();
|
||||
// Send config changes to devices
|
||||
if (!params.configEnabled) {
|
||||
logger.trace("Config changes are disabled.");
|
||||
} else if (devicesNeedingUpdate.isEmpty()) {
|
||||
logger.debug("No device configs to send.");
|
||||
} else if (params.configOnEventOnly && !isEvent) {
|
||||
} else if (params.configOnEventOnly && !shouldUpdate) {
|
||||
// shouldn't happen
|
||||
logger.error(
|
||||
"ERROR!! {} device(s) queued for config update, but event flag not set",
|
||||
"ERROR!! {} device(s) queued for config update, but no zones queued for update.",
|
||||
devicesNeedingUpdate.size()
|
||||
);
|
||||
} else {
|
||||
@@ -364,9 +377,38 @@ public class ConfigManager implements Runnable {
|
||||
return (configListeners.remove(id) != null);
|
||||
}
|
||||
|
||||
/** Interrupt the main thread, possibly triggering an update immediately. */
|
||||
public void wakeUp() {
|
||||
eventFlag.set(true);
|
||||
/**
|
||||
* Mark the zone to be updated, then interrupt the main thread to possibly
|
||||
* trigger an update immediately.
|
||||
*
|
||||
* @param zone non-null zone (i.e., venue)
|
||||
*/
|
||||
public void queueZoneAndWakeUp(String zone) {
|
||||
if (zone == null) {
|
||||
logger.debug("Zone to queue must be a non-null String.");
|
||||
return;
|
||||
}
|
||||
zonesToUpdate.add(zone);
|
||||
wakeUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Track all zones to be updated, then interrupt the main thread to possibly
|
||||
* trigger an update immediately.
|
||||
*/
|
||||
public void queueAllZonesAndWakeUp() {
|
||||
/*
|
||||
* Note, addAll is not atomic, but that is ok. This just means that it
|
||||
* is possible that some zones may get updated now by the main thread
|
||||
* while others get updated either when the main thread is woken up or
|
||||
* the next time the main thread does its periodic update.
|
||||
*/
|
||||
zonesToUpdate.addAll(deviceDataManager.getZones());
|
||||
wakeUp();
|
||||
}
|
||||
|
||||
/** Interrupt the main thread to possibly trigger an update immediately. */
|
||||
private void wakeUp() {
|
||||
if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) {
|
||||
wakeupFlag.set(true);
|
||||
mainThread.interrupt();
|
||||
|
||||
@@ -218,6 +218,7 @@ public class DataCollector implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// Fetch device list
|
||||
List<DeviceWithStatus> devices = client.getDevices();
|
||||
|
||||
@@ -238,6 +238,7 @@ public class Modeler implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// TODO: backfill data from database?
|
||||
|
||||
|
||||
@@ -22,8 +22,8 @@ import com.facebook.openwifirrm.aggregators.Aggregator;
|
||||
import com.facebook.openwifirrm.aggregators.MeanAggregator;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
|
||||
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.HTOperation;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.VHTOperation;
|
||||
|
||||
/**
|
||||
* Modeler utilities.
|
||||
@@ -239,9 +239,9 @@ public class ModelerUtils {
|
||||
return Objects.equals(entry1.bssid, entry2.bssid) &&
|
||||
entry1.frequency == entry2.frequency &&
|
||||
entry1.channel == entry2.channel &&
|
||||
HTOperationElement
|
||||
HTOperation
|
||||
.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) &&
|
||||
VHTOperationElement
|
||||
VHTOperation
|
||||
.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -102,6 +103,7 @@ public class ProvMonitor implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// Fetch data from owprov
|
||||
// TODO: this may change later - for now, we only fetch inventory and
|
||||
@@ -159,12 +161,21 @@ public class ProvMonitor implements Runnable {
|
||||
return null;
|
||||
}
|
||||
|
||||
RRMSchedule schedule = new RRMSchedule();
|
||||
schedule.cron = RRMScheduler
|
||||
String[] crons = RRMScheduler
|
||||
.parseIntoQuartzCron(details.rrm.schedule);
|
||||
if (schedule.cron == null || schedule.cron.isEmpty()) {
|
||||
if (crons == null || crons.length == 0) {
|
||||
return null;
|
||||
}
|
||||
// if ANY crons are invalid throw it out since it doesn't make sense to
|
||||
// schedule partial jobs
|
||||
for (String cron : crons) {
|
||||
if (cron == null || cron.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
RRMSchedule schedule = new RRMSchedule();
|
||||
schedule.crons = Arrays.asList(crons);
|
||||
|
||||
if (details.rrm.algorithms != null) {
|
||||
schedule.algorithms =
|
||||
@@ -175,6 +186,7 @@ public class ProvMonitor implements Runnable {
|
||||
)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
return schedule;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,15 +8,15 @@
|
||||
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.text.ParseException;
|
||||
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.CronExpression;
|
||||
import org.quartz.CronScheduleBuilder;
|
||||
import org.quartz.Job;
|
||||
import org.quartz.JobBuilder;
|
||||
import org.quartz.JobDetail;
|
||||
@@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
||||
import com.facebook.openwifirrm.RRMSchedule;
|
||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
@@ -74,15 +75,21 @@ public class RRMScheduler {
|
||||
/** The scheduler instance. */
|
||||
private Scheduler scheduler;
|
||||
|
||||
/** The zones with active triggers scheduled. */
|
||||
private Set<String> scheduledZones;
|
||||
/**
|
||||
* The job keys with active triggers scheduled. Job keys take the format of
|
||||
* {@code <zone>:<index>}
|
||||
*
|
||||
* @see #parseIntoQuartzCron(String)
|
||||
* */
|
||||
private Set<String> scheduledJobKeys;
|
||||
|
||||
/** RRM job. */
|
||||
public static class RRMJob implements Job {
|
||||
@Override
|
||||
public void execute(JobExecutionContext context)
|
||||
throws JobExecutionException {
|
||||
String zone = context.getTrigger().getKey().getName();
|
||||
String jobKey = context.getTrigger().getKey().getName();
|
||||
String zone = jobKey.split(":")[0];
|
||||
logger.debug("Executing job for zone: {}", zone);
|
||||
try {
|
||||
SchedulerContext schedulerContext =
|
||||
@@ -107,13 +114,14 @@ public class RRMScheduler {
|
||||
* @param linuxCron Linux cron with seconds
|
||||
* (seconds minutes hours day_of_month month day_of_week [year])
|
||||
*
|
||||
* @throws IllegalArgumentException when a linux cron cannot be parsed
|
||||
* into a valid Quartz spec
|
||||
* @return String a Quartz supported cron
|
||||
* @throws IllegalArgumentException when a linux cron cannot be parsed into a
|
||||
* valid Quartz spec
|
||||
* @return String[] an array of length 1 or 2 of Quartz supported cron that's
|
||||
* equivalent to the original linux cron
|
||||
*/
|
||||
public static String parseIntoQuartzCron(String linuxCron) {
|
||||
public static String[] parseIntoQuartzCron(String linuxCron) {
|
||||
if (CronExpression.isValidExpression(linuxCron)) {
|
||||
return linuxCron;
|
||||
return new String[] { linuxCron };
|
||||
}
|
||||
|
||||
String[] split = linuxCron.split(" ");
|
||||
@@ -144,15 +152,36 @@ public class RRMScheduler {
|
||||
// if first case failed and only day of week is *, set to ?
|
||||
split[DAY_OF_WEEK_INDEX] = "?";
|
||||
} else {
|
||||
// Quartz does not support both values being set, so return null
|
||||
return null;
|
||||
// Quartz does not support both values being set but the standard says that
|
||||
// if both are specified then it becomes OR of the two fields. Which means
|
||||
// that we can split it into two separate crons and have it work the same way
|
||||
split[DAY_OF_MONTH_INDEX] = "?";
|
||||
String dayOfWeekCron = String.join(" ", split);
|
||||
|
||||
split[DAY_OF_MONTH_INDEX] = dayOfMonth;
|
||||
split[DAY_OF_WEEK_INDEX] = "?";
|
||||
String dayOfMonthCron = String.join(" ", split);
|
||||
|
||||
if (
|
||||
!CronExpression.isValidExpression(dayOfWeekCron) ||
|
||||
!CronExpression.isValidExpression(dayOfMonthCron)
|
||||
) {
|
||||
logger.error(
|
||||
"Unable to parse cron {} into valid crons",
|
||||
linuxCron
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return new String[] { dayOfWeekCron, dayOfMonthCron };
|
||||
}
|
||||
|
||||
String quartzCron = String.join(" ", split);
|
||||
if (!CronExpression.isValidExpression(quartzCron)) {
|
||||
return null;
|
||||
}
|
||||
return quartzCron;
|
||||
|
||||
return new String[] { quartzCron };
|
||||
}
|
||||
|
||||
/** Constructor. */
|
||||
@@ -194,7 +223,7 @@ public class RRMScheduler {
|
||||
// Schedule job and triggers
|
||||
scheduler.addJob(job, false);
|
||||
syncTriggers();
|
||||
logger.info("Scheduled {} RRM trigger(s)", scheduledZones.size());
|
||||
logger.info("Scheduled {} RRM trigger(s)", scheduledJobKeys.size());
|
||||
|
||||
// Start scheduler
|
||||
scheduler.start();
|
||||
@@ -218,85 +247,98 @@ public class RRMScheduler {
|
||||
|
||||
/**
|
||||
* Synchronize triggers to the current topology, adding/updating/deleting
|
||||
* them as necessary. This updates {@link #scheduledZones}.
|
||||
* them as necessary. This updates {@link #scheduledJobKeys}.
|
||||
*/
|
||||
public void syncTriggers() {
|
||||
Set<String> scheduled = ConcurrentHashMap.newKeySet();
|
||||
Set<String> prevScheduled = new HashSet<>();
|
||||
if (scheduledZones != null) {
|
||||
prevScheduled.addAll(scheduledZones);
|
||||
if (scheduledJobKeys != null) {
|
||||
prevScheduled.addAll(scheduledJobKeys);
|
||||
}
|
||||
|
||||
// Add new triggers
|
||||
for (String zone : deviceDataManager.getZones()) {
|
||||
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
||||
RRMSchedule schedule = config.schedule;
|
||||
if (
|
||||
config.schedule == null ||
|
||||
config.schedule.cron == null ||
|
||||
config.schedule.cron.isEmpty()
|
||||
schedule == null || schedule.crons == null ||
|
||||
schedule.crons.isEmpty()
|
||||
) {
|
||||
continue; // RRM not scheduled
|
||||
}
|
||||
|
||||
try {
|
||||
CronExpression.validateExpression(config.schedule.cron);
|
||||
} catch (ParseException e) {
|
||||
logger.error(
|
||||
String.format(
|
||||
"Invalid cron expression (%s) for zone %s",
|
||||
config.schedule.cron,
|
||||
zone
|
||||
),
|
||||
e
|
||||
for (int i = 0; i < schedule.crons.size(); i++) {
|
||||
String cron = schedule.crons.get(i);
|
||||
// if even one schedule has invalid cron, the whole thing is probably wrong
|
||||
if (cron == null || cron.isEmpty()) {
|
||||
logger.error("There was an invalid cron in the schedule");
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
CronExpression.validateExpression(cron);
|
||||
} catch (ParseException e) {
|
||||
logger.error(
|
||||
String.format(
|
||||
"Invalid cron expression (%s) for zone %s",
|
||||
cron,
|
||||
zone
|
||||
),
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create trigger
|
||||
String jobKey = String.format("%s:%d", zone, i);
|
||||
Trigger trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(jobKey)
|
||||
.forJob(job)
|
||||
.withSchedule(
|
||||
CronScheduleBuilder.cronSchedule(cron)
|
||||
)
|
||||
.build();
|
||||
|
||||
try {
|
||||
if (!prevScheduled.contains(jobKey)) {
|
||||
scheduler.scheduleJob(trigger);
|
||||
} else {
|
||||
scheduler.rescheduleJob(trigger.getKey(), trigger);
|
||||
}
|
||||
} catch (SchedulerException e) {
|
||||
logger.error(
|
||||
"Failed to schedule RRM trigger for job key: " + jobKey,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
scheduled.add(jobKey);
|
||||
logger.debug(
|
||||
"Scheduled/updated RRM for job key '{}' at: < {} >",
|
||||
jobKey,
|
||||
cron
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create trigger
|
||||
Trigger trigger = TriggerBuilder.newTrigger()
|
||||
.withIdentity(zone)
|
||||
.forJob(job)
|
||||
.withSchedule(
|
||||
CronScheduleBuilder.cronSchedule(config.schedule.cron)
|
||||
)
|
||||
.build();
|
||||
try {
|
||||
if (!prevScheduled.contains(zone)) {
|
||||
scheduler.scheduleJob(trigger);
|
||||
} else {
|
||||
scheduler.rescheduleJob(trigger.getKey(), trigger);
|
||||
}
|
||||
} catch (SchedulerException e) {
|
||||
logger.error(
|
||||
"Failed to schedule RRM trigger for zone: " + zone,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
scheduled.add(zone);
|
||||
logger.debug(
|
||||
"Scheduled/updated RRM for zone '{}' at: < {} >",
|
||||
zone,
|
||||
config.schedule.cron
|
||||
);
|
||||
}
|
||||
|
||||
// Remove old triggers
|
||||
prevScheduled.removeAll(scheduled);
|
||||
for (String zone : prevScheduled) {
|
||||
for (String jobKey : prevScheduled) {
|
||||
try {
|
||||
scheduler.unscheduleJob(TriggerKey.triggerKey(zone));
|
||||
scheduler.unscheduleJob(TriggerKey.triggerKey(jobKey));
|
||||
} catch (SchedulerException e) {
|
||||
logger.error(
|
||||
"Failed to remove RRM trigger for zone: " + zone,
|
||||
"Failed to remove RRM trigger for jobKey: " + jobKey,
|
||||
e
|
||||
);
|
||||
continue;
|
||||
}
|
||||
logger.debug("Removed RRM trigger for zone '{}'", zone);
|
||||
logger.debug("Removed RRM trigger for jobKey '{}'", jobKey);
|
||||
}
|
||||
|
||||
this.scheduledZones = scheduled;
|
||||
this.scheduledJobKeys = scheduled;
|
||||
}
|
||||
|
||||
/** Run RRM algorithms for the given zone. */
|
||||
@@ -305,16 +347,19 @@ public class RRMScheduler {
|
||||
|
||||
// Get algorithms from zone config
|
||||
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
||||
if (config.schedule == null) {
|
||||
RRMSchedule schedule = config.schedule;
|
||||
if (schedule == null) {
|
||||
logger.error("RRM schedule missing for zone '{}', aborting!", zone);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
config.schedule.algorithms == null ||
|
||||
config.schedule.algorithms.isEmpty()
|
||||
schedule.algorithms == null ||
|
||||
schedule.algorithms.isEmpty()
|
||||
) {
|
||||
logger.debug("Using default RRM algorithms for zone '{}'", zone);
|
||||
config.schedule.algorithms = Arrays.asList(
|
||||
logger
|
||||
.debug("Using default RRM algorithms for zone '{}'", zone);
|
||||
schedule.algorithms = Arrays.asList(
|
||||
new RRMAlgorithm(
|
||||
RRMAlgorithm.AlgorithmType.OptimizeChannel.name()
|
||||
),
|
||||
@@ -325,14 +370,15 @@ public class RRMScheduler {
|
||||
}
|
||||
|
||||
// Execute algorithms
|
||||
for (RRMAlgorithm algo : config.schedule.algorithms) {
|
||||
for (RRMAlgorithm algo : schedule.algorithms) {
|
||||
RRMAlgorithm.AlgorithmResult result = algo.run(
|
||||
deviceDataManager,
|
||||
configManager,
|
||||
modeler,
|
||||
zone,
|
||||
params.dryRun,
|
||||
true /* allowDefaultMode */
|
||||
true, /* allowDefaultMode */
|
||||
false /* updateImmediately */
|
||||
);
|
||||
logger.info(
|
||||
"'{}' result for zone '{}': {}",
|
||||
@@ -341,5 +387,6 @@ public class RRMScheduler {
|
||||
gson.toJson(result)
|
||||
);
|
||||
}
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ public class DatabaseManager {
|
||||
/** Convert a list of state records to a State object. */
|
||||
private State toState(List<StateRecord> records, long ts) {
|
||||
State state = new State();
|
||||
state.unit = state.new Unit();
|
||||
state.unit = new State.Unit();
|
||||
state.unit.localtime = ts;
|
||||
|
||||
// Parse each record
|
||||
|
||||
@@ -25,9 +25,9 @@ import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.HTOperation;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.VHTOperation;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
|
||||
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
|
||||
|
||||
/**
|
||||
* Channel optimizer base class.
|
||||
@@ -208,13 +208,13 @@ public abstract class ChannelOptimizer {
|
||||
return MIN_CHANNEL_WIDTH;
|
||||
}
|
||||
|
||||
HTOperationElement htOperObj = new HTOperationElement(htOper);
|
||||
HTOperation htOperObj = new HTOperation(htOper);
|
||||
if (vhtOper == null) {
|
||||
// HT mode only supports 20/40 MHz
|
||||
return htOperObj.staChannelWidth ? 40 : 20;
|
||||
} else {
|
||||
// VHT/HE mode supports 20/40/160/80+80 MHz
|
||||
VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper);
|
||||
VHTOperation vhtOperObj = new VHTOperation(vhtOper);
|
||||
if (!htOperObj.staChannelWidth && vhtOperObj.channelWidth == 0) {
|
||||
return 20;
|
||||
} else if (
|
||||
@@ -639,14 +639,13 @@ public abstract class ChannelOptimizer {
|
||||
public abstract Map<String, Map<String, Integer>> computeChannelMap();
|
||||
|
||||
/**
|
||||
* Program the given channel map into the AP config and notify the config
|
||||
* manager.
|
||||
* Program the given channel map into the AP config.
|
||||
*
|
||||
* @param deviceDataManager the DeviceDataManager instance
|
||||
* @param configManager the ConfigManager instance
|
||||
* @param channelMap the map of devices (by serial number) to radio to channel
|
||||
*/
|
||||
public void applyConfig(
|
||||
public void updateDeviceApConfig(
|
||||
DeviceDataManager deviceDataManager,
|
||||
ConfigManager configManager,
|
||||
Map<String, Map<String, Integer>> channelMap
|
||||
@@ -664,8 +663,5 @@ public abstract class ChannelOptimizer {
|
||||
deviceConfig.autoChannels = entry.getValue();
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger config update now
|
||||
configManager.wakeUp();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,19 +13,18 @@ import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* TPC (Transmit Power Control) base class.
|
||||
@@ -83,19 +82,19 @@ public abstract class TPC {
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tx power choices based on user and allowed channels from deviceConfig
|
||||
* Determine the new tx power choices based on user and allowed channels from deviceConfig.
|
||||
*
|
||||
* @param band the operational band
|
||||
* @param serialNumber the device
|
||||
* @param txPowerChoices the available tx powers of the device
|
||||
* @return the updated tx powers of the device
|
||||
* @param serialNumber the device's serial number
|
||||
* @param txPowerChoices the device's available tx powers
|
||||
* @return the device's updated tx powers
|
||||
*/
|
||||
protected List<Integer> updateTxPowerChoices(
|
||||
String band,
|
||||
String serialNumber,
|
||||
List<Integer> txPowerChoices
|
||||
) {
|
||||
List<Integer> newTxPowerChoices =
|
||||
new ArrayList<>(txPowerChoices);
|
||||
List<Integer> newTxPowerChoices = new ArrayList<>(txPowerChoices);
|
||||
|
||||
// Update the available tx powers based on user tx powers or allowed tx powers
|
||||
DeviceConfig deviceCfg = deviceConfigs.get(serialNumber);
|
||||
@@ -127,8 +126,7 @@ public abstract class TPC {
|
||||
newTxPowerChoices.retainAll(allowedTxPowers);
|
||||
}
|
||||
|
||||
// If the intersection of the above steps gives an empty list,
|
||||
// turn back to use the default available tx powers list
|
||||
// If newTxPowerChoices is empty, use default available tx powers list
|
||||
if (newTxPowerChoices.isEmpty()) {
|
||||
logger.debug(
|
||||
"Device {}: the updated availableTxPowersList is empty!!! " +
|
||||
@@ -153,14 +151,13 @@ public abstract class TPC {
|
||||
public abstract Map<String, Map<String, Integer>> computeTxPowerMap();
|
||||
|
||||
/**
|
||||
* Program the given tx power map into the AP config and notify the config
|
||||
* manager.
|
||||
* Program the given tx power map into the AP config.
|
||||
*
|
||||
* @param deviceDataManager the DeviceDataManager instance
|
||||
* @param configManager the ConfigManager instance
|
||||
* @param txPowerMap the map of devices (by serial number) to radio to tx power
|
||||
*/
|
||||
public void applyConfig(
|
||||
public void updateDeviceApConfig(
|
||||
DeviceDataManager deviceDataManager,
|
||||
ConfigManager configManager,
|
||||
Map<String, Map<String, Integer>> txPowerMap
|
||||
@@ -178,15 +175,12 @@ public abstract class TPC {
|
||||
deviceConfig.autoTxPowers = entry.getValue();
|
||||
}
|
||||
});
|
||||
|
||||
// Trigger config update now
|
||||
configManager.wakeUp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get AP serial numbers per channel.
|
||||
*
|
||||
* @return the map of channel to the list of serial numbers
|
||||
* @return the map from channel to the list of AP serial numbers
|
||||
*/
|
||||
protected Map<Integer, List<String>> getApsPerChannel() {
|
||||
Map<Integer, List<String>> apsPerChannel = new TreeMap<>();
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.Country;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.LocalPowerConstraint;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.QbssLoad;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.TxPwrInfo;
|
||||
|
||||
/** Wrapper class containing information elements */
|
||||
public final class InformationElements {
|
||||
|
||||
public Country country;
|
||||
public QbssLoad qbssLoad;
|
||||
public LocalPowerConstraint localPowerConstraint;
|
||||
public TxPwrInfo txPwrInfo;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(country, localPowerConstraint, qbssLoad, txPwrInfo);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
InformationElements other = (InformationElements) obj;
|
||||
return Objects.equals(country, other.country) && Objects.equals(
|
||||
localPowerConstraint,
|
||||
other.localPowerConstraint
|
||||
) && Objects.equals(qbssLoad, other.qbssLoad) &&
|
||||
Objects.equals(txPwrInfo, other.txPwrInfo);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
package com.facebook.openwifirrm.ucentral;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -30,6 +31,8 @@ import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.WebTokenRefreshRequest;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.WebTokenResult;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.WifiScanRequest;
|
||||
import com.facebook.openwifirrm.ucentral.prov.models.EntityList;
|
||||
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
|
||||
@@ -137,7 +140,18 @@ public class UCentralClient {
|
||||
* The access token obtained from uCentralSec, needed only when using public
|
||||
* endpoints.
|
||||
*/
|
||||
private String accessToken;
|
||||
private WebTokenResult accessToken;
|
||||
|
||||
/**
|
||||
* The unix timestamp (in seconds) keeps track of when the accessToken is created.
|
||||
*/
|
||||
private long created;
|
||||
|
||||
/**
|
||||
* The unix timestamp (in seconds) keeps track of last time when the accessToken
|
||||
* is accessed.
|
||||
*/
|
||||
private long lastAccess;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -184,7 +198,8 @@ public class UCentralClient {
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("userId", username);
|
||||
body.put("password", password);
|
||||
HttpResponse<String> response = httpPost("oauth2", OWSEC_SERVICE, body);
|
||||
HttpResponse<String> response =
|
||||
httpPost("oauth2", OWSEC_SERVICE, body, null);
|
||||
if (!response.isSuccess()) {
|
||||
logger.error(
|
||||
"Login failed: Response code {}, body: {}",
|
||||
@@ -195,27 +210,146 @@ public class UCentralClient {
|
||||
}
|
||||
|
||||
// Parse access token from response
|
||||
JSONObject respBody;
|
||||
WebTokenResult token;
|
||||
try {
|
||||
respBody = new JSONObject(response.getBody());
|
||||
} catch (JSONException e) {
|
||||
token = gson.fromJson(response.getBody(), WebTokenResult.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error("Login failed: Unexpected response", e);
|
||||
logger.debug("Response body: {}", response.getBody());
|
||||
return false;
|
||||
}
|
||||
if (!respBody.has("access_token")) {
|
||||
if (
|
||||
token == null || token.access_token == null ||
|
||||
token.access_token.isEmpty()
|
||||
) {
|
||||
logger.error("Login failed: Missing access token");
|
||||
logger.debug("Response body: {}", respBody.toString());
|
||||
logger.debug("Response body: {}", response.getBody());
|
||||
return false;
|
||||
}
|
||||
this.accessToken = respBody.getString("access_token");
|
||||
this.accessToken = token;
|
||||
this.created = accessToken.created;
|
||||
this.lastAccess = accessToken.created;
|
||||
logger.info("Login successful as user: {}", username);
|
||||
logger.debug("Access token: {}", accessToken);
|
||||
logger.debug("Access token: {}", accessToken.access_token);
|
||||
logger.debug("Refresh token: {}", accessToken.refresh_token);
|
||||
|
||||
// Load system endpoints
|
||||
return loadSystemEndpoints();
|
||||
}
|
||||
|
||||
/**
|
||||
* when using public endpoints, refresh the access token if it's expired.
|
||||
*/
|
||||
public synchronized void refreshAccessToken() {
|
||||
if (usePublicEndpoints) {
|
||||
refreshAccessTokenImpl();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token is completely expired even if
|
||||
* for a token refresh request
|
||||
*
|
||||
* @return true if the refresh token is expired
|
||||
*/
|
||||
private boolean isAccessTokenExpired() {
|
||||
if (accessToken == null) {
|
||||
return true;
|
||||
}
|
||||
return created + accessToken.expires_in <
|
||||
Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an access token is expired.
|
||||
*
|
||||
* @return true if the access token is expired
|
||||
*/
|
||||
private boolean isAccessTokenTimedOut() {
|
||||
if (accessToken == null) {
|
||||
return true;
|
||||
}
|
||||
return lastAccess + accessToken.idle_timeout <
|
||||
Instant.now().getEpochSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the access toke when time out. If the refresh token is expired, login again.
|
||||
* If the access token is expired, POST a WebTokenRefreshRequest to refresh token.
|
||||
*/
|
||||
private void refreshAccessTokenImpl() {
|
||||
if (!usePublicEndpoints) {
|
||||
return;
|
||||
}
|
||||
if (isAccessTokenExpired()) {
|
||||
synchronized (this) {
|
||||
if (isAccessTokenExpired()) {
|
||||
logger.info("Token is expired, login again");
|
||||
login();
|
||||
}
|
||||
}
|
||||
} else if (isAccessTokenTimedOut()) {
|
||||
synchronized (this) {
|
||||
if (isAccessTokenTimedOut()) {
|
||||
logger.debug("Access token timed out, refreshing the token");
|
||||
accessToken = refreshToken();
|
||||
created = Instant.now().getEpochSecond();
|
||||
lastAccess = created;
|
||||
if (accessToken != null) {
|
||||
logger.debug("Successfully refresh token.");
|
||||
}else{
|
||||
logger.error(
|
||||
"Fail to refresh token with access token: {}",
|
||||
accessToken.access_token
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST a WebTokenRefreshRequest to refresh the access token.
|
||||
*
|
||||
* @return valid access token if success, otherwise return null.
|
||||
*/
|
||||
private WebTokenResult refreshToken() {
|
||||
if (accessToken == null) {
|
||||
return null;
|
||||
}
|
||||
WebTokenRefreshRequest refreshRequest = new WebTokenRefreshRequest();
|
||||
refreshRequest.userId = username;
|
||||
refreshRequest.refreshToken = accessToken.refresh_token;
|
||||
logger.debug("refresh token: {}", accessToken.refresh_token);
|
||||
Map<String, Object> parameters =
|
||||
Collections.singletonMap("grant_type", "refresh_token");
|
||||
HttpResponse<String> response =
|
||||
httpPost(
|
||||
"oauth2",
|
||||
OWSEC_SERVICE,
|
||||
refreshRequest,
|
||||
parameters
|
||||
);
|
||||
if (!response.isSuccess()) {
|
||||
logger.error(
|
||||
"Failed to refresh token: Response code {}, body: {}",
|
||||
response.getStatus(),
|
||||
response.getBody()
|
||||
);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return gson.fromJson(response.getBody(), WebTokenResult.class);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error(
|
||||
"Failed to serialize WebTokenResult: Unexpected response:",
|
||||
e
|
||||
);
|
||||
logger.debug("Response body: {}", response.getBody());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Read system endpoint URLs from uCentralSec. */
|
||||
private boolean loadSystemEndpoints() {
|
||||
// Make request
|
||||
@@ -324,8 +458,12 @@ public class UCentralClient {
|
||||
.connectTimeout(connectTimeoutMs)
|
||||
.socketTimeout(socketTimeoutMs);
|
||||
if (usePublicEndpoints) {
|
||||
if (accessToken != null) {
|
||||
req.header("Authorization", "Bearer " + accessToken);
|
||||
if (!isAccessTokenExpired()) {
|
||||
req.header(
|
||||
"Authorization",
|
||||
"Bearer " + accessToken.access_token
|
||||
);
|
||||
lastAccess = Instant.now().getEpochSecond();
|
||||
}
|
||||
} else {
|
||||
req
|
||||
@@ -339,26 +477,29 @@ public class UCentralClient {
|
||||
}
|
||||
}
|
||||
|
||||
/** Send a POST request with a JSON body. */
|
||||
/** Send a POST request with a JSON body and query params. */
|
||||
private HttpResponse<String> httpPost(
|
||||
String endpoint,
|
||||
String service,
|
||||
Object body
|
||||
Object body,
|
||||
Map<String, Object> parameters
|
||||
) {
|
||||
return httpPost(
|
||||
endpoint,
|
||||
service,
|
||||
body,
|
||||
parameters,
|
||||
socketParams.connectTimeoutMs,
|
||||
socketParams.socketTimeoutMs
|
||||
);
|
||||
}
|
||||
|
||||
/** Send a POST request with a JSON body using given timeout values. */
|
||||
/** Send a POST request with a JSON body and query params using given timeout values. */
|
||||
private HttpResponse<String> httpPost(
|
||||
String endpoint,
|
||||
String service,
|
||||
Object body,
|
||||
Map<String, Object> parameters,
|
||||
int connectTimeoutMs,
|
||||
int socketTimeoutMs
|
||||
) {
|
||||
@@ -367,9 +508,16 @@ public class UCentralClient {
|
||||
.header("accept", "application/json")
|
||||
.connectTimeout(connectTimeoutMs)
|
||||
.socketTimeout(socketTimeoutMs);
|
||||
if (parameters != null && !parameters.isEmpty()) {
|
||||
req.queryString(parameters);
|
||||
}
|
||||
if (usePublicEndpoints) {
|
||||
if (accessToken != null) {
|
||||
req.header("Authorization", "Bearer " + accessToken);
|
||||
if (!isAccessTokenExpired()) {
|
||||
req.header(
|
||||
"Authorization",
|
||||
"Bearer " + accessToken.access_token
|
||||
);
|
||||
lastAccess = Instant.now().getEpochSecond();
|
||||
}
|
||||
} else {
|
||||
req
|
||||
@@ -454,6 +602,7 @@ public class UCentralClient {
|
||||
String.format("device/%s/wifiscan", serialNumber),
|
||||
OWGW_SERVICE,
|
||||
req,
|
||||
null,
|
||||
socketParams.connectTimeoutMs,
|
||||
socketParams.wifiScanTimeoutMs
|
||||
);
|
||||
@@ -482,7 +631,8 @@ public class UCentralClient {
|
||||
HttpResponse<String> response = httpPost(
|
||||
String.format("device/%s/configure", serialNumber),
|
||||
OWGW_SERVICE,
|
||||
req
|
||||
req,
|
||||
null
|
||||
);
|
||||
if (!response.isSuccess()) {
|
||||
logger.error("Error: {}", response.getBody());
|
||||
|
||||
@@ -24,6 +24,10 @@ import org.slf4j.LoggerFactory;
|
||||
import com.facebook.openwifirrm.RRMConfig;
|
||||
import com.facebook.openwifirrm.Utils;
|
||||
import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.Country;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.LocalPowerConstraint;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.QbssLoad;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.TxPwrInfo;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
@@ -37,6 +41,9 @@ public class UCentralUtils {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(UCentralUtils.class);
|
||||
|
||||
/** Information Element (IE) content field key */
|
||||
private static final String IE_CONTENT_FIELD_KEY = "content";
|
||||
|
||||
/** The Gson instance. */
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
@@ -79,15 +86,76 @@ public class UCentralUtils {
|
||||
for (JsonElement e : scanInfo) {
|
||||
WifiScanEntry entry = gson.fromJson(e, WifiScanEntry.class);
|
||||
entry.unixTimeMs = timestampMs;
|
||||
extractIEs(e, entry);
|
||||
entries.add(entry);
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Exception when parsing wifiscan entries", e);
|
||||
return null;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract desired information elements (IEs) from the wifiscan entry.
|
||||
* Modifies {@code entry} argument. Skips invalid IEs (IEs with missing
|
||||
* fields).
|
||||
*/
|
||||
private static void extractIEs(
|
||||
JsonElement entryJsonElement,
|
||||
WifiScanEntry entry
|
||||
) {
|
||||
JsonElement iesJsonElement =
|
||||
entryJsonElement.getAsJsonObject().get("ies");
|
||||
if (iesJsonElement == null) {
|
||||
logger.debug("Wifiscan entry does not contain 'ies' field.");
|
||||
return;
|
||||
}
|
||||
JsonArray iesJsonArray = iesJsonElement.getAsJsonArray();
|
||||
InformationElements ieContainer = new InformationElements();
|
||||
for (JsonElement ieJsonElement : iesJsonArray) {
|
||||
JsonElement typeElement =
|
||||
ieJsonElement.getAsJsonObject().get("type");
|
||||
if (typeElement == null) { // shouldn't happen
|
||||
continue;
|
||||
}
|
||||
if (!ieJsonElement.isJsonObject()) {
|
||||
// the IEs we are interested in are Json objects
|
||||
continue;
|
||||
}
|
||||
JsonObject ie = ieJsonElement.getAsJsonObject();
|
||||
JsonElement contentsJsonElement = ie.get(IE_CONTENT_FIELD_KEY);
|
||||
if (
|
||||
contentsJsonElement == null ||
|
||||
!contentsJsonElement.isJsonObject()
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
JsonObject contents = contentsJsonElement.getAsJsonObject();
|
||||
try {
|
||||
switch (typeElement.getAsInt()) {
|
||||
case Country.TYPE:
|
||||
ieContainer.country = Country.parse(contents);
|
||||
break;
|
||||
case QbssLoad.TYPE:
|
||||
ieContainer.qbssLoad = QbssLoad.parse(contents);
|
||||
break;
|
||||
case LocalPowerConstraint.TYPE:
|
||||
ieContainer.localPowerConstraint =
|
||||
LocalPowerConstraint.parse(contents);
|
||||
break;
|
||||
case TxPwrInfo.TYPE:
|
||||
ieContainer.txPwrInfo = TxPwrInfo.parse(contents);
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("Skipping invalid IE {}", ie);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
entry.ieContainer = ieContainer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set all radios config of an AP to a given value.
|
||||
*
|
||||
|
||||
@@ -22,6 +22,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
||||
* time reference.
|
||||
*/
|
||||
public long unixTimeMs;
|
||||
/** Stores Information Elements (IEs) from the wifiscan entry. */
|
||||
public InformationElements ieContainer;
|
||||
|
||||
/** Default Constructor. */
|
||||
public WifiScanEntry() {}
|
||||
@@ -30,13 +32,14 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
||||
public WifiScanEntry(WifiScanEntry o) {
|
||||
super(o);
|
||||
this.unixTimeMs = o.unixTimeMs;
|
||||
this.ieContainer = o.ieContainer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + Objects.hash(unixTimeMs);
|
||||
result = prime * result + Objects.hash(ieContainer, unixTimeMs);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -52,7 +55,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
||||
return false;
|
||||
}
|
||||
WifiScanEntry other = (WifiScanEntry) obj;
|
||||
return unixTimeMs == other.unixTimeMs;
|
||||
return Objects.equals(ieContainer, other.ieContainer) &&
|
||||
unixTimeMs == other.unixTimeMs;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.gw.models;
|
||||
|
||||
public class WebTokenRefreshRequest {
|
||||
public String userId;
|
||||
public String refreshToken;
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* This information element (IE) appears in wifiscan entries.
|
||||
* Refer to the 802.11 specification for more details. Language in
|
||||
* javadocs is taken from the specification.
|
||||
*/
|
||||
public class Country {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Country.class);
|
||||
|
||||
/** Defined in 802.11 */
|
||||
public static final int TYPE = 7;
|
||||
|
||||
/** Constraints for a subset of channels in the AP's country */
|
||||
public static class CountryInfo {
|
||||
/**
|
||||
* The lowest channel number in the CountryInfo.
|
||||
*/
|
||||
public final int firstChannelNumber;
|
||||
/**
|
||||
* The maximum power, in dBm, allowed to be transmitted.
|
||||
*/
|
||||
public final int maximumTransmitPowerLevel;
|
||||
/**
|
||||
* Number of channels this CountryInfo applies to. E.g., if First
|
||||
* Channel Number is 2 and Number of Channels is 4, this CountryInfo
|
||||
* describes channels 2, 3, 4, and 5.
|
||||
*/
|
||||
public final int numberOfChannels;
|
||||
|
||||
/** Constructor. */
|
||||
public CountryInfo(
|
||||
int firstChannelNumber,
|
||||
int maximumTransmitPowerLevel,
|
||||
int numberOfChannels
|
||||
) {
|
||||
this.firstChannelNumber = firstChannelNumber;
|
||||
this.maximumTransmitPowerLevel = maximumTransmitPowerLevel;
|
||||
this.numberOfChannels = numberOfChannels;
|
||||
}
|
||||
|
||||
/** Parse CountryInfo from the appropriate Json object. */
|
||||
public static CountryInfo parse(JsonObject contents) {
|
||||
final int firstChannelNumber =
|
||||
contents.get("First Channel Number").getAsInt();
|
||||
final int maximumTransmitPowerLevel = contents
|
||||
.get("Maximum Transmit Power Level (in dBm)")
|
||||
.getAsInt();
|
||||
final int numberOfChannels =
|
||||
contents.get("Number of Channels").getAsInt();
|
||||
return new CountryInfo(
|
||||
firstChannelNumber,
|
||||
maximumTransmitPowerLevel,
|
||||
numberOfChannels
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
firstChannelNumber,
|
||||
maximumTransmitPowerLevel,
|
||||
numberOfChannels
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
CountryInfo other = (CountryInfo) obj;
|
||||
return firstChannelNumber == other.firstChannelNumber &&
|
||||
maximumTransmitPowerLevel == other.maximumTransmitPowerLevel &&
|
||||
numberOfChannels == other.numberOfChannels;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CountryInfo [firstChannelNumber=" + firstChannelNumber +
|
||||
", maximumTransmitPowerLevel=" + maximumTransmitPowerLevel +
|
||||
", numberOfChannels=" + numberOfChannels + "]";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Each constraint is a CountryInfo describing tx power constraints on
|
||||
* one or more channels, for the current country.
|
||||
*/
|
||||
public final List<CountryInfo> constraints;
|
||||
|
||||
/** Constructor */
|
||||
public Country(List<CountryInfo> countryInfos) {
|
||||
this.constraints = Collections.unmodifiableList(countryInfos);
|
||||
}
|
||||
|
||||
/** Parse Country IE from the appropriate Json object. */
|
||||
public static Country parse(JsonObject contents) {
|
||||
List<CountryInfo> constraints = new ArrayList<>();
|
||||
JsonElement constraintsObject = contents.get("constraints");
|
||||
if (constraintsObject != null) {
|
||||
for (JsonElement jsonElement : constraintsObject.getAsJsonArray()) {
|
||||
CountryInfo countryInfo =
|
||||
CountryInfo.parse(jsonElement.getAsJsonObject());
|
||||
constraints.add(countryInfo);
|
||||
}
|
||||
}
|
||||
return new Country(constraints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(constraints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Country other = (Country) obj;
|
||||
return Objects.equals(constraints, other.constraints);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Country [constraints=" + constraints + "]";
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
@@ -17,7 +17,7 @@ import org.apache.commons.codec.binary.Base64;
|
||||
* High Throughput (HT) Operation Element, which is potentially present in
|
||||
* wifiscan entries. Introduced in 802.11n (2009).
|
||||
*/
|
||||
public class HTOperationElement {
|
||||
public class HTOperation {
|
||||
|
||||
/** Channel number of the primary channel. */
|
||||
public final byte primaryChannel;
|
||||
@@ -78,7 +78,7 @@ public class HTOperationElement {
|
||||
* For details about the parameters, see the javadocs for the corresponding
|
||||
* member variables.
|
||||
*/
|
||||
public HTOperationElement(
|
||||
public HTOperation(
|
||||
byte primaryChannel,
|
||||
byte secondaryChannelOffset,
|
||||
boolean staChannelWidth,
|
||||
@@ -114,7 +114,7 @@ public class HTOperationElement {
|
||||
}
|
||||
|
||||
/** Constructor with the most used parameters. */
|
||||
public HTOperationElement(
|
||||
public HTOperation(
|
||||
byte primaryChannel,
|
||||
byte secondaryChannelOffset,
|
||||
boolean staChannelWidth,
|
||||
@@ -141,7 +141,7 @@ public class HTOperationElement {
|
||||
* @param htOper a base64 encoded properly formatted HT operation element (see
|
||||
* 802.11)
|
||||
*/
|
||||
public HTOperationElement(String htOper) {
|
||||
public HTOperation(String htOper) {
|
||||
byte[] bytes = Base64.decodeBase64(htOper);
|
||||
/*
|
||||
* Note that the code here may seem to read "reversed" compared to 802.11. This
|
||||
@@ -182,7 +182,7 @@ public class HTOperationElement {
|
||||
* @return true if the the operation elements "match" for the purpose of
|
||||
* aggregating statistics; false otherwise.
|
||||
*/
|
||||
public boolean matchesForAggregation(HTOperationElement other) {
|
||||
public boolean matchesForAggregation(HTOperation other) {
|
||||
return other != null && primaryChannel == other.primaryChannel &&
|
||||
secondaryChannelOffset == other.secondaryChannelOffset &&
|
||||
staChannelWidth == other.staChannelWidth &&
|
||||
@@ -211,8 +211,8 @@ public class HTOperationElement {
|
||||
if (htOper1 == null || htOper2 == null) {
|
||||
return false; // false if exactly one is null
|
||||
}
|
||||
HTOperationElement htOperObj1 = new HTOperationElement(htOper1);
|
||||
HTOperationElement htOperObj2 = new HTOperationElement(htOper2);
|
||||
HTOperation htOperObj1 = new HTOperation(htOper1);
|
||||
HTOperation htOperObj2 = new HTOperation(htOper2);
|
||||
return htOperObj1.matchesForAggregation(htOperObj2);
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ public class HTOperationElement {
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
HTOperationElement other = (HTOperationElement) obj;
|
||||
HTOperation other = (HTOperation) obj;
|
||||
return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) &&
|
||||
channelCenterFrequencySegment2 ==
|
||||
other.channelCenterFrequencySegment2 &&
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* This information element (IE) appears in wifiscan entries. It is called
|
||||
* "Local Power Constraint" in these entries, and just "Power Constraint" in
|
||||
* the 802.11 specification. Refer to the specification for more details.
|
||||
* Language in javadocs is taken from the specification.
|
||||
*/
|
||||
public class LocalPowerConstraint {
|
||||
|
||||
/** Defined in 802.11 */
|
||||
public static final int TYPE = 32;
|
||||
|
||||
/**
|
||||
* Units are dB.
|
||||
* <p>
|
||||
* The local maximum transmit power for a channel is defined as the maximum
|
||||
* transmit power level specified for the channel in the Country IE minus
|
||||
* this variable for the given channel.
|
||||
*/
|
||||
public final int localPowerConstraint;
|
||||
|
||||
/** Constructor */
|
||||
public LocalPowerConstraint(int localPowerConstraint) {
|
||||
this.localPowerConstraint = localPowerConstraint;
|
||||
}
|
||||
|
||||
/** Parse LocalPowerConstraint IE from appropriate Json object. */
|
||||
public static LocalPowerConstraint parse(JsonObject contents) {
|
||||
final int localPowerConstraint =
|
||||
contents.get("Local Power Constraint").getAsInt();
|
||||
return new LocalPowerConstraint(localPowerConstraint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(localPowerConstraint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
LocalPowerConstraint other = (LocalPowerConstraint) obj;
|
||||
return localPowerConstraint == other.localPowerConstraint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "LocalPowerConstraint [localPowerConstraint=" +
|
||||
localPowerConstraint + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* This information element (IE) appears in wifiscan entries. It is called
|
||||
* "QBSS Load" in these entries, and just "BSS Load" in the 802.11
|
||||
* specification. Refer to the specification for more details. Language in
|
||||
* javadocs is taken from the specification.
|
||||
*/
|
||||
public class QbssLoad {
|
||||
|
||||
/** Defined in 802.11 */
|
||||
public static final int TYPE = 11;
|
||||
|
||||
/**
|
||||
* The total number of STAs currently associated with the BSS.
|
||||
*/
|
||||
public final int stationCount;
|
||||
/**
|
||||
* The Channel Utilization field is defined as the percentage of time,
|
||||
* linearly scaled with 255 representing 100%, that the AP sensed the
|
||||
* medium was busy, as indicated by either the physical or virtual carrier
|
||||
* sense (CS) mechanism. When more than one channel is in use for the BSS,
|
||||
* the Channel Utilization field value is calculated only for the primary
|
||||
* channel. This percentage is computed using the following formula:
|
||||
* <p>
|
||||
* floor(255 * channelBusyTime /
|
||||
* (dot11ChannelUtilizationBeaconIntervals * dot11BeaconPeriod * 1024)
|
||||
* )
|
||||
*/
|
||||
public final int channelUtilization;
|
||||
/**
|
||||
* The Available Admission Capacity field contains an unsigned integer that
|
||||
* specifies the remaining amount of medium time available via explicit
|
||||
* admission control, in units of 32 miscrosecond/second. The field is
|
||||
* helpful for roaming STAs to select an AP that is likely to accept future
|
||||
* admission control requests, but it does not represent an assurance that
|
||||
* the HC admits these requests.
|
||||
*/
|
||||
public final int availableAdmissionCapacity;
|
||||
|
||||
/** Constructor */
|
||||
public QbssLoad(
|
||||
int stationCount,
|
||||
int channelUtilization,
|
||||
int availableAdmissionCapacity
|
||||
) {
|
||||
this.stationCount = stationCount;
|
||||
this.channelUtilization = channelUtilization;
|
||||
this.availableAdmissionCapacity = availableAdmissionCapacity;
|
||||
}
|
||||
|
||||
/** Parse QbssLoad IE from appropriate Json object; return null if invalid. */
|
||||
public static QbssLoad parse(JsonObject contents) {
|
||||
// unclear why there is this additional nested layer
|
||||
JsonElement ccaContentJsonElement = contents.get("802.11e CCA Version");
|
||||
if (ccaContentJsonElement == null) {
|
||||
return null;
|
||||
}
|
||||
contents = ccaContentJsonElement.getAsJsonObject();
|
||||
final int stationCount = contents.get("Station Count").getAsInt();
|
||||
final int channelUtilization =
|
||||
contents.get("Channel Utilization").getAsInt();
|
||||
final int availableAdmissionCapacity =
|
||||
contents.get("Available Admission Capabilities").getAsInt();
|
||||
return new QbssLoad(
|
||||
stationCount,
|
||||
channelUtilization,
|
||||
availableAdmissionCapacity
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
availableAdmissionCapacity,
|
||||
channelUtilization,
|
||||
stationCount
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
QbssLoad other = (QbssLoad) obj;
|
||||
return availableAdmissionCapacity == other.availableAdmissionCapacity &&
|
||||
channelUtilization == other.channelUtilization &&
|
||||
stationCount == other.stationCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "QbssLoad [stationCount=" + stationCount +
|
||||
", channelUtilization=" + channelUtilization +
|
||||
", availableAdmissionCapacity=" + availableAdmissionCapacity + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* This information element (IE) appears in wifiscan entries. It is called
|
||||
* "Tx Pwr Info" in these entries, and "Transmit Power Envelope" in the 802.11
|
||||
* specification. Refer to the specification for more details. Language in
|
||||
* javadocs is taken from the specification.
|
||||
*/
|
||||
public class TxPwrInfo {
|
||||
|
||||
/** Defined in 802.11 */
|
||||
public static final int TYPE = 195;
|
||||
|
||||
/** Local maximum transmit power for 20 MHz. Required field. */
|
||||
public final Integer localMaxTxPwrConstraint20MHz;
|
||||
/** Local maximum transmit power for 40 MHz. Optional field. */
|
||||
public final Integer localMaxTxPwrConstraint40MHz;
|
||||
/** Local maximum transmit power for 80 MHz. Optional field. */
|
||||
public final Integer localMaxTxPwrConstraint80MHz;
|
||||
/** Local maximum transmit power for both 160 MHz and 80+80 MHz. Optional field. */
|
||||
public final Integer localMaxTxPwrConstraint160MHz;
|
||||
|
||||
/** Constructor */
|
||||
public TxPwrInfo(
|
||||
int localMaxTxPwrConstraint20MHz,
|
||||
int localMaxTxPwrConstraint40MHz,
|
||||
int localMaxTxPwrConstraint80MHz,
|
||||
int localMaxTxPwrConstraint160MHz
|
||||
) {
|
||||
this.localMaxTxPwrConstraint20MHz = localMaxTxPwrConstraint20MHz;
|
||||
this.localMaxTxPwrConstraint40MHz = localMaxTxPwrConstraint40MHz;
|
||||
this.localMaxTxPwrConstraint80MHz = localMaxTxPwrConstraint80MHz;
|
||||
this.localMaxTxPwrConstraint160MHz = localMaxTxPwrConstraint160MHz;
|
||||
}
|
||||
|
||||
/** Parse TxPwrInfo IE from appropriate Json object. */
|
||||
public static TxPwrInfo parse(JsonObject contents) {
|
||||
// required field
|
||||
int localMaxTxPwrConstraint20MHz =
|
||||
contents.get("Local Max Tx Pwr Constraint 20MHz").getAsInt();
|
||||
// optional field
|
||||
Integer localMaxTxPwrConstraint40MHz =
|
||||
parseOptionalField(contents, "Local Max Tx Pwr Constraint 40MHz");
|
||||
Integer localMaxTxPwrConstraint80MHz =
|
||||
parseOptionalField(contents, "Local Max Tx Pwr Constraint 40MHz");
|
||||
Integer localMaxTxPwrConstraint160MHz =
|
||||
parseOptionalField(contents, "Local Max Tx Pwr Constraint 40MHz");
|
||||
return new TxPwrInfo(
|
||||
localMaxTxPwrConstraint20MHz,
|
||||
localMaxTxPwrConstraint40MHz,
|
||||
localMaxTxPwrConstraint80MHz,
|
||||
localMaxTxPwrConstraint160MHz
|
||||
);
|
||||
}
|
||||
|
||||
private static Integer parseOptionalField(
|
||||
JsonObject contents,
|
||||
String fieldName
|
||||
) {
|
||||
JsonElement element = contents.get(fieldName);
|
||||
if (element == null) {
|
||||
return null;
|
||||
}
|
||||
return element.getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
localMaxTxPwrConstraint160MHz,
|
||||
localMaxTxPwrConstraint20MHz,
|
||||
localMaxTxPwrConstraint40MHz,
|
||||
localMaxTxPwrConstraint80MHz
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TxPwrInfo other = (TxPwrInfo) obj;
|
||||
return localMaxTxPwrConstraint160MHz ==
|
||||
other.localMaxTxPwrConstraint160MHz &&
|
||||
localMaxTxPwrConstraint20MHz ==
|
||||
other.localMaxTxPwrConstraint20MHz &&
|
||||
localMaxTxPwrConstraint40MHz ==
|
||||
other.localMaxTxPwrConstraint40MHz &&
|
||||
localMaxTxPwrConstraint80MHz == other.localMaxTxPwrConstraint80MHz;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TxPwrInfo [localMaxTxPwrConstraint20MHz=" +
|
||||
localMaxTxPwrConstraint20MHz + ", localMaxTxPwrConstraint40MHz=" +
|
||||
localMaxTxPwrConstraint40MHz + ", localMaxTxPwrConstraint80MHz=" +
|
||||
localMaxTxPwrConstraint80MHz + ", localMaxTxPwrConstraint160MHz=" +
|
||||
localMaxTxPwrConstraint160MHz + "]";
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
@@ -17,7 +17,7 @@ import org.apache.commons.codec.binary.Base64;
|
||||
* Very High Throughput (VHT) Operation Element, which is potentially present in
|
||||
* wifiscan entries. Introduced in 802.11ac (2013).
|
||||
*/
|
||||
public class VHTOperationElement {
|
||||
public class VHTOperation {
|
||||
|
||||
/**
|
||||
* This field is 0 if the channel width is 20 MHz or 40 MHz, and 1 otherwise.
|
||||
@@ -65,7 +65,7 @@ public class VHTOperationElement {
|
||||
* @param vhtOper a base64 encoded properly formatted VHT operation element (see
|
||||
* 802.11 standard)
|
||||
*/
|
||||
public VHTOperationElement(String vhtOper) {
|
||||
public VHTOperation(String vhtOper) {
|
||||
byte[] bytes = Base64.decodeBase64(vhtOper);
|
||||
this.channelWidth = bytes[0];
|
||||
this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value
|
||||
@@ -89,7 +89,7 @@ public class VHTOperationElement {
|
||||
* For details about the parameters, see the javadocs for the corresponding
|
||||
* member variables.
|
||||
*/
|
||||
public VHTOperationElement(
|
||||
public VHTOperation(
|
||||
byte channelWidth,
|
||||
short channel1,
|
||||
short channel2,
|
||||
@@ -114,7 +114,7 @@ public class VHTOperationElement {
|
||||
* @return true if the the operation elements "match" for the purpose of
|
||||
* aggregating statistics; false otherwise.
|
||||
*/
|
||||
public boolean matchesForAggregation(VHTOperationElement other) {
|
||||
public boolean matchesForAggregation(VHTOperation other) {
|
||||
// check everything except vhtMcsForNss
|
||||
return other != null && channel1 == other.channel1 &&
|
||||
channel2 == other.channel2 && channelWidth == other.channelWidth;
|
||||
@@ -142,8 +142,8 @@ public class VHTOperationElement {
|
||||
if (vhtOper1 == null || vhtOper2 == null) {
|
||||
return false; // false if exactly one is null
|
||||
}
|
||||
VHTOperationElement vhtOperObj1 = new VHTOperationElement(vhtOper1);
|
||||
VHTOperationElement vhtOperObj2 = new VHTOperationElement(vhtOper2);
|
||||
VHTOperation vhtOperObj1 = new VHTOperation(vhtOper1);
|
||||
VHTOperation vhtOperObj2 = new VHTOperation(vhtOper2);
|
||||
return vhtOperObj1.matchesForAggregation(vhtOperObj2);
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ public class VHTOperationElement {
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
VHTOperationElement other = (VHTOperationElement) obj;
|
||||
VHTOperation other = (VHTOperation) obj;
|
||||
return channel1 == other.channel1 && channel2 == other.channel2 &&
|
||||
channelWidth == other.channelWidth &&
|
||||
Arrays.equals(vhtMcsForNss, other.vhtMcsForNss);
|
||||
@@ -12,8 +12,8 @@ import com.google.gson.JsonObject;
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
public class State {
|
||||
public class Interface {
|
||||
public class Client {
|
||||
public static class Interface {
|
||||
public static class Client {
|
||||
public String mac;
|
||||
public String[] ipv4_addresses;
|
||||
public String[] ipv6_addresses;
|
||||
@@ -21,9 +21,9 @@ public class State {
|
||||
// TODO last_seen
|
||||
}
|
||||
|
||||
public class SSID {
|
||||
public class Association {
|
||||
public class Rate {
|
||||
public static class SSID {
|
||||
public static class Association {
|
||||
public static class Rate {
|
||||
public long bitrate;
|
||||
public int chwidth;
|
||||
public boolean sgi;
|
||||
@@ -66,7 +66,7 @@ public class State {
|
||||
public JsonObject radio;
|
||||
}
|
||||
|
||||
public class Counters {
|
||||
public static class Counters {
|
||||
public long collisions;
|
||||
public long multicast;
|
||||
public long rx_bytes;
|
||||
@@ -96,8 +96,8 @@ public class State {
|
||||
|
||||
public Interface[] interfaces;
|
||||
|
||||
public class Unit {
|
||||
public class Memory {
|
||||
public static class Unit {
|
||||
public static class Memory {
|
||||
public long buffered;
|
||||
public long cached;
|
||||
public long free;
|
||||
|
||||
@@ -10,9 +10,11 @@ package com.facebook.openwifirrm.ucentral.models;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
/** Represents a single entry in wifi scan results. */
|
||||
/**
|
||||
* Represents a single entry in wifi scan results.
|
||||
* ies[] array is not stored directly, but parsed into WifiScanEntry fields
|
||||
*
|
||||
*/
|
||||
public class WifiScanEntryResult {
|
||||
public int channel;
|
||||
public long last_seen;
|
||||
@@ -50,8 +52,6 @@ public class WifiScanEntryResult {
|
||||
public String vht_oper;
|
||||
public int capability;
|
||||
public int frequency;
|
||||
/** IE = information element */
|
||||
public JsonArray ies;
|
||||
|
||||
/** Default Constructor. */
|
||||
public WifiScanEntryResult() {}
|
||||
@@ -68,7 +68,6 @@ public class WifiScanEntryResult {
|
||||
this.vht_oper = o.vht_oper;
|
||||
this.capability = o.capability;
|
||||
this.frequency = o.frequency;
|
||||
this.ies = o.ies;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -79,7 +78,6 @@ public class WifiScanEntryResult {
|
||||
channel,
|
||||
frequency,
|
||||
ht_oper,
|
||||
ies,
|
||||
last_seen,
|
||||
signal,
|
||||
ssid,
|
||||
@@ -104,7 +102,9 @@ public class WifiScanEntryResult {
|
||||
capability == other.capability && channel == other.channel &&
|
||||
frequency == other.frequency && Objects
|
||||
.equals(ht_oper, other.ht_oper) &&
|
||||
Objects.equals(ies, other.ies) && last_seen == other.last_seen && signal == other.signal && Objects.equals(ssid, other.ssid) && tsf == other.tsf && Objects.equals(vht_oper, other.vht_oper);
|
||||
last_seen == other.last_seen &&
|
||||
Objects.equals(ssid, other.ssid) && tsf == other.tsf &&
|
||||
Objects.equals(vht_oper, other.vht_oper);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,7 +13,7 @@ import java.util.List;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
|
||||
|
||||
public class DeviceConfiguration {
|
||||
public class DeviceConfigurationElement {
|
||||
public static class DeviceConfigurationElement {
|
||||
public String name;
|
||||
public String description;
|
||||
public Integer weight;
|
||||
|
||||
@@ -11,7 +11,7 @@ package com.facebook.openwifirrm.ucentral.prov.models;
|
||||
import java.util.List;
|
||||
|
||||
public class RRMDetails {
|
||||
public class RRMDetailsImpl {
|
||||
public static class RRMDetailsImpl {
|
||||
public String vendor;
|
||||
public String schedule;
|
||||
public List<RRMAlgorithmDetails> algorithms;
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -23,48 +23,48 @@ public class RRMSchedulerTest {
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * * * *"));
|
||||
|
||||
// correct (6 fields)
|
||||
assertEquals(
|
||||
"* * * ? * *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * *")
|
||||
);
|
||||
|
||||
// correct (7 fields)
|
||||
assertEquals(
|
||||
"* * * ? * * *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * * *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * * *")
|
||||
);
|
||||
|
||||
// correct value other than * for day of month
|
||||
assertEquals(
|
||||
"* * * 1 * ?",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * 1 * ? *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * * *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * 1/2 * ?",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1/2 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * 1/2 * ? *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1/2 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * * *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * 1-2 * ?",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1-2 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * 1-2 * ? *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1-2 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * * *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * 1,2 * ?",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1,2 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * 1,2 * ? *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * 1,2 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * * *")
|
||||
);
|
||||
|
||||
@@ -79,70 +79,70 @@ public class RRMSchedulerTest {
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * * *"));
|
||||
|
||||
// correct value other than * for day of month
|
||||
assertEquals(
|
||||
"* * * ? * 1",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1 *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1/3",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/3" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1/3 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/3 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3 *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1-3",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-3" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1-3 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-3 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3 *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1,3",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1,3" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1,3 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1,3 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3 *")
|
||||
);
|
||||
|
||||
// correct value other than * for day of month, make sure 0 turns into 7
|
||||
assertEquals(
|
||||
"* * * ? * 7",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 7" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 0")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 7 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 7 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 0 *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1/7",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1/7 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0 *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1-7",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1-7 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0 *")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1,7",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1,7" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,0")
|
||||
);
|
||||
assertEquals(
|
||||
"* * * ? * 1,7 *",
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1,7 *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,0 *")
|
||||
);
|
||||
|
||||
@@ -155,5 +155,119 @@ public class RRMSchedulerTest {
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7-8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7,8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * 7,8 *"));
|
||||
|
||||
// correct value for both day of week and day of month
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 7", "* * * 1 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * 0")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 7 *", "* * * 1 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * 0 *")
|
||||
);
|
||||
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7", "* * * 1 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1/0")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1", "* * * 1/2 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7", "* * * 1/2 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1/0")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7 *", "* * * 1 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1/0 *")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1 *", "* * * 1/2 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1 *")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7 *", "* * * 1/2 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * 1/0 *")
|
||||
);
|
||||
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7", "* * * 1 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1-0")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1", "* * * 1-3 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7", "* * * 1-3 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1-0")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7 *", "* * * 1 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * 1-0 *")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1 *", "* * * 1-3 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1 *")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7 *", "* * * 1-3 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1-0 *")
|
||||
);
|
||||
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7", "* * * 1/3 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/3 * 1-0")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7", "* * * 1-3 * ?" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1/0")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1-7 *", "* * * 1/3 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1/3 * 1-0 *")
|
||||
);
|
||||
assertArrayEquals(
|
||||
new String[] { "* * * ? * 1/7 *", "* * * 1-3 * ? *" },
|
||||
RRMScheduler.parseIntoQuartzCron("* * * 1-3 * 1/0 *")
|
||||
);
|
||||
|
||||
// wrong value for either day of week or day of month
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7/8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7/8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7-8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7-8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7,8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0 * 7,8 *"));
|
||||
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7/8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7/8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7-8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7-8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7,8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0/1 * 7,8 *"));
|
||||
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7/8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7/8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7-8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7-8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7,8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0-1 * 7,8 *"));
|
||||
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7/8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7/8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7-8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7-8 *"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7,8"));
|
||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * 7,8 *"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,6 +140,26 @@ public class TestUtils {
|
||||
return jsonList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array with one radio info entry with the given tx power and
|
||||
* channel.
|
||||
*/
|
||||
public static JsonArray createDeviceStatusSingleBand(
|
||||
int channel,
|
||||
int txPower2G
|
||||
) {
|
||||
JsonArray jsonList = new JsonArray();
|
||||
jsonList.add(
|
||||
createDeviceStatusRadioObject(
|
||||
UCentralUtils.getBandFromChannel(channel),
|
||||
channel,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
txPower2G
|
||||
)
|
||||
);
|
||||
return jsonList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an array with two radio info entries (2G and 5G), with the given
|
||||
* tx powers and channels.
|
||||
@@ -480,7 +500,7 @@ public class TestUtils {
|
||||
new State.Interface.SSID.Association[clientRssis[i].length];
|
||||
for (int j = 0; j < clientRssis[i].length; j++) {
|
||||
state.interfaces[i].ssids[0].associations[j] =
|
||||
state.interfaces[i].ssids[0].new Association();
|
||||
new State.Interface.SSID.Association();
|
||||
state.interfaces[i].ssids[0].associations[j].rssi =
|
||||
clientRssis[i][j];
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.facebook.openwifirrm.optimizers.TestUtils;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
|
||||
@TestMethodOrder(OrderAnnotation.class)
|
||||
public class MeasurementBasedApApTPCTest {
|
||||
@@ -77,65 +76,84 @@ public class MeasurementBasedApApTPCTest {
|
||||
return deviceDataManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a data model with 3 devices in only the given band.
|
||||
* All are at max_tx_power, which represents the first step in greedy TPC.
|
||||
*
|
||||
* @param band band (e.g., "2G")
|
||||
*/
|
||||
private static DataModel createModelSingleBand(String band) {
|
||||
DataModel model = new DataModel();
|
||||
|
||||
final int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
||||
|
||||
List<String> bssids = Arrays.asList(BSSID_A, BSSID_B, BSSID_C);
|
||||
List<String> devices = Arrays.asList(DEVICE_A, DEVICE_B, DEVICE_C);
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
String device = devices.get(i);
|
||||
String bssid = bssids.get(i);
|
||||
model.latestState.put(
|
||||
device,
|
||||
TestUtils.createState(
|
||||
channel,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
bssid
|
||||
)
|
||||
);
|
||||
model.latestDeviceStatusRadios.put(
|
||||
device,
|
||||
TestUtils
|
||||
.createDeviceStatusSingleBand(channel, MAX_TX_POWER)
|
||||
);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a data model with 3 devices. All are at max_tx_power, which
|
||||
* represents the first step in greedy TPC.
|
||||
*
|
||||
* @return a data model
|
||||
*/
|
||||
private static DataModel createModel() {
|
||||
private static DataModel createModelDualBand() {
|
||||
DataModel model = new DataModel();
|
||||
|
||||
State stateA = TestUtils.createState(
|
||||
1,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_A,
|
||||
36,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_A
|
||||
);
|
||||
State stateB = TestUtils.createState(
|
||||
1,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_B,
|
||||
36,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_B
|
||||
);
|
||||
State stateC = TestUtils.createState(
|
||||
1,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_C,
|
||||
36,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_C
|
||||
);
|
||||
final int channel2G =
|
||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
||||
final int channel5G =
|
||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_5G);
|
||||
|
||||
model.latestState.put(DEVICE_A, stateA);
|
||||
model.latestState.put(DEVICE_B, stateB);
|
||||
model.latestState.put(DEVICE_C, stateC);
|
||||
|
||||
model.latestDeviceStatusRadios.put(
|
||||
DEVICE_A,
|
||||
TestUtils
|
||||
.createDeviceStatusDualBand(1, MAX_TX_POWER, 36, MAX_TX_POWER)
|
||||
);
|
||||
model.latestDeviceStatusRadios.put(
|
||||
DEVICE_B,
|
||||
TestUtils
|
||||
.createDeviceStatusDualBand(1, MAX_TX_POWER, 36, MAX_TX_POWER)
|
||||
);
|
||||
model.latestDeviceStatusRadios.put(
|
||||
DEVICE_C,
|
||||
TestUtils
|
||||
.createDeviceStatusDualBand(1, MAX_TX_POWER, 36, MAX_TX_POWER)
|
||||
);
|
||||
List<String> bssids = Arrays.asList(BSSID_A, BSSID_B, BSSID_C);
|
||||
List<String> devices = Arrays.asList(DEVICE_A, DEVICE_B, DEVICE_C);
|
||||
for (int i = 0; i < devices.size(); i++) {
|
||||
String device = devices.get(i);
|
||||
String bssid = bssids.get(i);
|
||||
model.latestState.put(
|
||||
device,
|
||||
TestUtils.createState(
|
||||
channel2G,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
bssid,
|
||||
channel5G,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
bssid
|
||||
)
|
||||
);
|
||||
model.latestDeviceStatusRadios.put(
|
||||
device,
|
||||
TestUtils
|
||||
.createDeviceStatusDualBand(
|
||||
channel2G,
|
||||
MAX_TX_POWER,
|
||||
channel5G,
|
||||
MAX_TX_POWER
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return model;
|
||||
}
|
||||
@@ -286,7 +304,7 @@ public class MeasurementBasedApApTPCTest {
|
||||
@Test
|
||||
@Order(1)
|
||||
void testGetManagedBSSIDs() throws Exception {
|
||||
DataModel dataModel = createModel();
|
||||
DataModel dataModel = createModelDualBand();
|
||||
Set<String> managedBSSIDs =
|
||||
MeasurementBasedApApTPC.getManagedBSSIDs(dataModel);
|
||||
assertEquals(3, managedBSSIDs.size());
|
||||
@@ -393,7 +411,7 @@ public class MeasurementBasedApApTPCTest {
|
||||
*/
|
||||
private static void testComputeTxPowerMapSimpleInOneBand(String band) {
|
||||
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
||||
DataModel dataModel = createModel();
|
||||
DataModel dataModel = createModelSingleBand(band);
|
||||
dataModel.latestWifiScans = createLatestWifiScansB(channel);
|
||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||
|
||||
@@ -418,7 +436,7 @@ public class MeasurementBasedApApTPCTest {
|
||||
String band
|
||||
) {
|
||||
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
||||
DataModel dataModel = createModel();
|
||||
DataModel dataModel = createModelSingleBand(band);
|
||||
dataModel.latestWifiScans = createLatestWifiScansC(channel);
|
||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||
|
||||
@@ -445,7 +463,7 @@ public class MeasurementBasedApApTPCTest {
|
||||
*/
|
||||
private static void testComputeTxPowerMapMissingDataInOneBand(String band) {
|
||||
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
||||
DataModel dataModel = createModel();
|
||||
DataModel dataModel = createModelSingleBand(band);
|
||||
dataModel.latestWifiScans =
|
||||
createLatestWifiScansWithMissingEntries(channel);
|
||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||
@@ -481,35 +499,17 @@ public class MeasurementBasedApApTPCTest {
|
||||
@Order(6)
|
||||
void testComputeTxPowerMapMultiBand() {
|
||||
// test both bands simultaneously with different setups on each band
|
||||
DataModel dataModel = createModel();
|
||||
dataModel.latestState.remove(DEVICE_B);
|
||||
dataModel.latestState.put(
|
||||
DEVICE_B,
|
||||
TestUtils.createState(
|
||||
1,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_B
|
||||
)
|
||||
);
|
||||
// make device C not operate in the 5G band instead of dual band
|
||||
dataModel.latestState.put(
|
||||
DEVICE_C,
|
||||
TestUtils.createState(
|
||||
1,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_C
|
||||
)
|
||||
);
|
||||
DataModel dataModel = createModelDualBand();
|
||||
|
||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||
// 2G setup
|
||||
// 2G: use testComputeTxPowerMapSimpleInOneBand setup
|
||||
final int channel2G =
|
||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
||||
dataModel.latestWifiScans = createLatestWifiScansB(channel2G);
|
||||
// 5G setup
|
||||
// 5G: use testComputeTxPowerMapMissingDataInOneBand setup
|
||||
final int channel5G =
|
||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_5G);
|
||||
// add 5G wifiscan results to dataModel.latestWifiScans
|
||||
Map<String, List<List<WifiScanEntry>>> toMerge =
|
||||
createLatestWifiScansWithMissingEntries(channel5G);
|
||||
for (
|
||||
@@ -537,8 +537,10 @@ public class MeasurementBasedApApTPCTest {
|
||||
Map<String, Map<String, Integer>> txPowerMap =
|
||||
optimizer.computeTxPowerMap();
|
||||
|
||||
// test 2G band
|
||||
// every AP operates in at least one band
|
||||
assertEquals(3, txPowerMap.size());
|
||||
|
||||
// test 2G band
|
||||
assertEquals(
|
||||
2,
|
||||
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_2G)
|
||||
@@ -557,11 +559,69 @@ public class MeasurementBasedApApTPCTest {
|
||||
0,
|
||||
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_5G)
|
||||
);
|
||||
// deivce B does not have 5G radio
|
||||
assertFalse(
|
||||
txPowerMap.get(DEVICE_B).containsKey(UCentralConstants.BAND_5G)
|
||||
assertEquals(
|
||||
0,
|
||||
txPowerMap.get(DEVICE_B).get(UCentralConstants.BAND_5G)
|
||||
);
|
||||
// device C is not in the 5G band
|
||||
assertEquals(
|
||||
30,
|
||||
txPowerMap.get(DEVICE_C).get(UCentralConstants.BAND_5G)
|
||||
);
|
||||
|
||||
// now test when device C does not have a 5G radio
|
||||
dataModel.latestState.put(
|
||||
DEVICE_C,
|
||||
TestUtils.createState(
|
||||
1,
|
||||
DEFAULT_CHANNEL_WIDTH,
|
||||
MAX_TX_POWER,
|
||||
BSSID_C
|
||||
)
|
||||
);
|
||||
dataModel.latestDeviceStatusRadios.put(
|
||||
DEVICE_C,
|
||||
TestUtils.createDeviceStatus(
|
||||
UCentralConstants.BAND_2G,
|
||||
1,
|
||||
MAX_TX_POWER
|
||||
)
|
||||
);
|
||||
optimizer = new MeasurementBasedApApTPC(
|
||||
dataModel,
|
||||
TEST_ZONE,
|
||||
deviceDataManager,
|
||||
-80,
|
||||
0
|
||||
);
|
||||
txPowerMap = optimizer.computeTxPowerMap();
|
||||
|
||||
// every AP operates in at least one band
|
||||
assertEquals(3, txPowerMap.size());
|
||||
|
||||
// test 2G band (all APs), same as testComputeTxPowerMapSimpleInOneBand
|
||||
assertEquals(
|
||||
2,
|
||||
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_2G)
|
||||
);
|
||||
assertEquals(
|
||||
15,
|
||||
txPowerMap.get(DEVICE_B).get(UCentralConstants.BAND_2G)
|
||||
);
|
||||
assertEquals(
|
||||
10,
|
||||
txPowerMap.get(DEVICE_C).get(UCentralConstants.BAND_2G)
|
||||
);
|
||||
|
||||
// test 5G band (only device A and device B operate in 5G)
|
||||
assertEquals(
|
||||
0,
|
||||
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_5G)
|
||||
);
|
||||
assertEquals(
|
||||
0,
|
||||
txPowerMap.get(DEVICE_B).get(UCentralConstants.BAND_5G)
|
||||
);
|
||||
// this time device C has no 5G radio so it is not set to max power (30)
|
||||
assertFalse(
|
||||
txPowerMap.get(DEVICE_C).containsKey(UCentralConstants.BAND_5G)
|
||||
);
|
||||
|
||||
@@ -6,17 +6,17 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class HTOperationElementTest {
|
||||
public class HTOperationTest {
|
||||
@Test
|
||||
void testGetHtOper() {
|
||||
String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
||||
HTOperationElement htOperObj = new HTOperationElement(htOper);
|
||||
HTOperation htOperObj = new HTOperation(htOper);
|
||||
byte expectedPrimaryChannel = 1;
|
||||
byte expectedSecondaryChannelOffset = 0;
|
||||
boolean expectedStaChannelWidth = false;
|
||||
@@ -28,7 +28,7 @@ public class HTOperationElementTest {
|
||||
boolean expectedDualBeacon = false;
|
||||
boolean expectedDualCtsProtection = false;
|
||||
boolean expectedStbcBeacon = false;
|
||||
HTOperationElement expectedHtOperObj = new HTOperationElement(
|
||||
HTOperation expectedHtOperObj = new HTOperation(
|
||||
expectedPrimaryChannel,
|
||||
expectedSecondaryChannelOffset,
|
||||
expectedStaChannelWidth,
|
||||
@@ -44,11 +44,11 @@ public class HTOperationElementTest {
|
||||
assertEquals(expectedHtOperObj, htOperObj);
|
||||
|
||||
htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
||||
htOperObj = new HTOperationElement(htOper);
|
||||
htOperObj = new HTOperation(htOper);
|
||||
// all fields except the primary channel and nongreenfield field are the same
|
||||
expectedPrimaryChannel = 36;
|
||||
expectedNongreenfieldHtStasPresent = false;
|
||||
expectedHtOperObj = new HTOperationElement(
|
||||
expectedHtOperObj = new HTOperation(
|
||||
expectedPrimaryChannel,
|
||||
expectedSecondaryChannelOffset,
|
||||
expectedStaChannelWidth,
|
||||
@@ -6,23 +6,23 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifirrm.ucentral.operationelement;
|
||||
package com.facebook.openwifirrm.ucentral.informationelement;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class VHTOperationElementTest {
|
||||
public class VHTOperationTest {
|
||||
|
||||
@Test
|
||||
void testGetVhtOper() {
|
||||
String vhtOper = "ACQAAAA=";
|
||||
VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper);
|
||||
VHTOperation vhtOperObj = new VHTOperation(vhtOper);
|
||||
byte expectedChannelWidthIndicator = 0; // 20 MHz channel width
|
||||
short expectedChannel1 = 36;
|
||||
short expectedChannel2 = 0;
|
||||
byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
VHTOperationElement expectedVhtOperObj = new VHTOperationElement(
|
||||
VHTOperation expectedVhtOperObj = new VHTOperation(
|
||||
expectedChannelWidthIndicator,
|
||||
expectedChannel1,
|
||||
expectedChannel2,
|
||||
@@ -31,12 +31,12 @@ public class VHTOperationElementTest {
|
||||
assertEquals(expectedVhtOperObj, vhtOperObj);
|
||||
|
||||
vhtOper = "AToAUAE=";
|
||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
||||
vhtOperObj = new VHTOperation(vhtOper);
|
||||
expectedChannelWidthIndicator = 1; // 80 MHz channel width
|
||||
expectedChannel1 = 58;
|
||||
// same channel2
|
||||
expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 };
|
||||
expectedVhtOperObj = new VHTOperationElement(
|
||||
expectedVhtOperObj = new VHTOperation(
|
||||
expectedChannelWidthIndicator,
|
||||
expectedChannel1,
|
||||
expectedChannel2,
|
||||
@@ -45,12 +45,12 @@ public class VHTOperationElementTest {
|
||||
assertEquals(expectedVhtOperObj, vhtOperObj);
|
||||
|
||||
vhtOper = "ASoyUAE=";
|
||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
||||
vhtOperObj = new VHTOperation(vhtOper);
|
||||
// same channel width indicator (160 MHz channel width)
|
||||
expectedChannel1 = 42;
|
||||
expectedChannel2 = 50;
|
||||
// same vhtMcsForNss
|
||||
expectedVhtOperObj = new VHTOperationElement(
|
||||
expectedVhtOperObj = new VHTOperation(
|
||||
expectedChannelWidthIndicator,
|
||||
expectedChannel1,
|
||||
expectedChannel2,
|
||||
@@ -60,12 +60,12 @@ public class VHTOperationElementTest {
|
||||
|
||||
// test with channel number >= 128 (channel fields should be unsigned)
|
||||
vhtOper = "AJUAAAA=";
|
||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
||||
vhtOperObj = new VHTOperation(vhtOper);
|
||||
expectedChannelWidthIndicator = 0;
|
||||
expectedChannel1 = 149;
|
||||
expectedChannel2 = 0;
|
||||
expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
expectedVhtOperObj = new VHTOperationElement(
|
||||
expectedVhtOperObj = new VHTOperation(
|
||||
expectedChannelWidthIndicator,
|
||||
expectedChannel1,
|
||||
expectedChannel2,
|
||||
Reference in New Issue
Block a user