mirror of
https://github.com/Telecominfraproject/wlan-cloud-rrm.git
synced 2025-10-30 18:17:58 +00:00
Compare commits
21 Commits
release/v2
...
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:
|
images:
|
||||||
owrrm:
|
owrrm:
|
||||||
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owrrm
|
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owrrm
|
||||||
tag: v2.7.0
|
tag: main
|
||||||
pullPolicy: Always
|
pullPolicy: Always
|
||||||
# regcred:
|
# regcred:
|
||||||
# registry: tip-tip-wlan-cloud-ucentral.jfrog.io
|
# registry: tip-tip-wlan-cloud-ucentral.jfrog.io
|
||||||
|
|||||||
@@ -478,8 +478,10 @@ components:
|
|||||||
RRMSchedule:
|
RRMSchedule:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
cron:
|
crons:
|
||||||
type: string
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
algorithms:
|
algorithms:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ public class RRMAlgorithm {
|
|||||||
* @param dryRun if set, do not apply changes
|
* @param dryRun if set, do not apply changes
|
||||||
* @param allowDefaultMode if false, "mode" argument must be present and
|
* @param allowDefaultMode if false, "mode" argument must be present and
|
||||||
* valid (returns error if invalid)
|
* 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
|
* @return the algorithm result, with exactly one field set ("error" upon
|
||||||
* failure, any others upon success)
|
* failure, any others upon success)
|
||||||
@@ -153,7 +156,8 @@ public class RRMAlgorithm {
|
|||||||
Modeler modeler,
|
Modeler modeler,
|
||||||
String zone,
|
String zone,
|
||||||
boolean dryRun,
|
boolean dryRun,
|
||||||
boolean allowDefaultMode
|
boolean allowDefaultMode,
|
||||||
|
boolean updateImmediately
|
||||||
) {
|
) {
|
||||||
AlgorithmResult result = new AlgorithmResult();
|
AlgorithmResult result = new AlgorithmResult();
|
||||||
if (name == null || args == null) {
|
if (name == null || args == null) {
|
||||||
@@ -212,11 +216,14 @@ public class RRMAlgorithm {
|
|||||||
}
|
}
|
||||||
result.channelMap = optimizer.computeChannelMap();
|
result.channelMap = optimizer.computeChannelMap();
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
optimizer.applyConfig(
|
optimizer.updateDeviceApConfig(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
result.channelMap
|
result.channelMap
|
||||||
);
|
);
|
||||||
|
if (updateImmediately) {
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
|
name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name())
|
||||||
@@ -270,11 +277,14 @@ public class RRMAlgorithm {
|
|||||||
}
|
}
|
||||||
result.txPowerMap = optimizer.computeTxPowerMap();
|
result.txPowerMap = optimizer.computeTxPowerMap();
|
||||||
if (!dryRun) {
|
if (!dryRun) {
|
||||||
optimizer.applyConfig(
|
optimizer.updateDeviceApConfig(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
result.txPowerMap
|
result.txPowerMap
|
||||||
);
|
);
|
||||||
|
if (updateImmediately) {
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.error = String.format("Unknown algorithm: '%s'", name);
|
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
|
* The main logic loop interval (i.e. sleep time), in ms
|
||||||
* ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS})
|
* ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS})
|
||||||
*/
|
*/
|
||||||
public int updateIntervalMs = 5000;
|
public int updateIntervalMs = 30000; // 30sec
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The expected device statistics interval, in seconds (or -1 to
|
* The expected device statistics interval, in seconds (or -1 to
|
||||||
@@ -246,13 +246,13 @@ public class RRMConfig {
|
|||||||
* automatic scans)
|
* automatic scans)
|
||||||
* ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC})
|
* ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC})
|
||||||
*/
|
*/
|
||||||
public int wifiScanIntervalSec = 900;
|
public int wifiScanIntervalSec = 900; // 15min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The capabilities request interval (per device), in seconds
|
* The capabilities request interval (per device), in seconds
|
||||||
* ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC})
|
* ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC})
|
||||||
*/
|
*/
|
||||||
public int capabilitiesIntervalSec = 3600;
|
public int capabilitiesIntervalSec = 3600; // 1hr
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Number of executor threads for async tasks (ex. wifi scans)
|
* 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
|
* The main logic loop interval (i.e. sleep time), in ms
|
||||||
* ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS})
|
* ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS})
|
||||||
*/
|
*/
|
||||||
public int updateIntervalMs = 60000;
|
public int updateIntervalMs = 120000; // 2min
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enable pushing device config changes?
|
* Enable pushing device config changes?
|
||||||
@@ -363,7 +363,7 @@ public class RRMConfig {
|
|||||||
* Sync interval, in ms, for owprov venue information etc.
|
* Sync interval, in ms, for owprov venue information etc.
|
||||||
* ({@code PROVMONITORPARAMS_SYNCINTERVALMS})
|
* ({@code PROVMONITORPARAMS_SYNCINTERVALMS})
|
||||||
*/
|
*/
|
||||||
public int syncIntervalMs = 300000;
|
public int syncIntervalMs = 300000; // 5min
|
||||||
}
|
}
|
||||||
|
|
||||||
/** ProvMonitor parameters. */
|
/** ProvMonitor parameters. */
|
||||||
|
|||||||
@@ -19,9 +19,9 @@ public class RRMSchedule {
|
|||||||
*
|
*
|
||||||
* This field expects a cron-like format as defined by the Quartz Job
|
* This field expects a cron-like format as defined by the Quartz Job
|
||||||
* Scheduler (CronTrigger):
|
* 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.
|
* 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
|
* @return true if token is valid
|
||||||
*/
|
*/
|
||||||
private boolean validateOpenWifiToken(String token) {
|
private boolean validateOpenWifiToken(String token) {
|
||||||
// The below only checks /api/v1/validateToken and caches it as necessary.
|
// 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);
|
Long expiry = tokenCache.get(token);
|
||||||
if (expiry == null) {
|
if (expiry == null) {
|
||||||
TokenValidationResult result = client.validateToken(token);
|
TokenValidationResult result = client.validateToken(token);
|
||||||
@@ -711,7 +712,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
venue,
|
venue,
|
||||||
mock,
|
mock,
|
||||||
true /* allowDefaultMode */
|
true, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
@@ -917,7 +919,7 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig networkConfig =
|
DeviceConfig networkConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceNetworkConfig(networkConfig);
|
deviceDataManager.setDeviceNetworkConfig(networkConfig);
|
||||||
configManager.wakeUp();
|
configManager.queueAllZonesAndWakeUp();
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -981,7 +983,7 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig zoneConfig =
|
DeviceConfig zoneConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
|
deviceDataManager.setDeviceZoneConfig(zone, zoneConfig);
|
||||||
configManager.wakeUp();
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
|
|
||||||
// Revalidate data model
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1044,7 +1046,10 @@ public class ApiServer implements Runnable {
|
|||||||
DeviceConfig apConfig =
|
DeviceConfig apConfig =
|
||||||
gson.fromJson(request.body(), DeviceConfig.class);
|
gson.fromJson(request.body(), DeviceConfig.class);
|
||||||
deviceDataManager.setDeviceApConfig(serialNumber, apConfig);
|
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
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1117,7 +1122,10 @@ public class ApiServer implements Runnable {
|
|||||||
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
|
.computeIfAbsent(serialNumber, k -> new DeviceConfig())
|
||||||
.apply(apConfig);
|
.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
|
// Revalidate data model
|
||||||
modeler.revalidate();
|
modeler.revalidate();
|
||||||
@@ -1260,7 +1268,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
dryRun,
|
dryRun,
|
||||||
false /* allowDefaultMode */
|
false, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
@@ -1371,7 +1380,8 @@ public class ApiServer implements Runnable {
|
|||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
dryRun,
|
dryRun,
|
||||||
false /* allowDefaultMode */
|
false, /* allowDefaultMode */
|
||||||
|
true /* updateImmediately */
|
||||||
);
|
);
|
||||||
if (result.error != null) {
|
if (result.error != null) {
|
||||||
response.status(400);
|
response.status(400);
|
||||||
|
|||||||
@@ -10,9 +10,12 @@ package com.facebook.openwifirrm.modules;
|
|||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -63,8 +66,11 @@ public class ConfigManager implements Runnable {
|
|||||||
/** Is the main thread sleeping? */
|
/** Is the main thread sleeping? */
|
||||||
private final AtomicBoolean sleepingFlag = new AtomicBoolean(false);
|
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. */
|
/** Config listener interface. */
|
||||||
public interface ConfigListener {
|
public interface ConfigListener {
|
||||||
@@ -165,6 +171,7 @@ public class ConfigManager implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
client.refreshAccessToken();
|
||||||
|
|
||||||
// Fetch device list
|
// Fetch device list
|
||||||
List<DeviceWithStatus> devices = client.getDevices();
|
List<DeviceWithStatus> devices = client.getDevices();
|
||||||
@@ -180,7 +187,10 @@ public class ConfigManager implements Runnable {
|
|||||||
List<String> devicesNeedingUpdate = new ArrayList<>();
|
List<String> devicesNeedingUpdate = new ArrayList<>();
|
||||||
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
||||||
params.configDebounceIntervalSec * 1_000_000_000L;
|
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) {
|
for (DeviceWithStatus device : devices) {
|
||||||
// Update config structure
|
// Update config structure
|
||||||
DeviceData data = deviceDataMap.computeIfAbsent(
|
DeviceData data = deviceDataMap.computeIfAbsent(
|
||||||
@@ -201,11 +211,13 @@ public class ConfigManager implements Runnable {
|
|||||||
for (ConfigListener listener : configListeners.values()) {
|
for (ConfigListener listener : configListeners.values()) {
|
||||||
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
||||||
}
|
}
|
||||||
|
// Check if there are requested updates for this zone
|
||||||
// Check event flag
|
String deviceZone =
|
||||||
|
deviceDataManager.getDeviceZone(device.serialNumber);
|
||||||
|
boolean isEvent = zonesToUpdateCopy.contains(deviceZone);
|
||||||
if (params.configOnEventOnly && !isEvent) {
|
if (params.configOnEventOnly && !isEvent) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Skipping config for {} (event flag not set)",
|
"Skipping config for {} (zone not marked for updates)",
|
||||||
device.serialNumber
|
device.serialNumber
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
@@ -251,15 +263,16 @@ public class ConfigManager implements Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final boolean shouldUpdate = !zonesToUpdateCopy.isEmpty();
|
||||||
// Send config changes to devices
|
// Send config changes to devices
|
||||||
if (!params.configEnabled) {
|
if (!params.configEnabled) {
|
||||||
logger.trace("Config changes are disabled.");
|
logger.trace("Config changes are disabled.");
|
||||||
} else if (devicesNeedingUpdate.isEmpty()) {
|
} else if (devicesNeedingUpdate.isEmpty()) {
|
||||||
logger.debug("No device configs to send.");
|
logger.debug("No device configs to send.");
|
||||||
} else if (params.configOnEventOnly && !isEvent) {
|
} else if (params.configOnEventOnly && !shouldUpdate) {
|
||||||
// shouldn't happen
|
// shouldn't happen
|
||||||
logger.error(
|
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()
|
devicesNeedingUpdate.size()
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -364,9 +377,38 @@ public class ConfigManager implements Runnable {
|
|||||||
return (configListeners.remove(id) != null);
|
return (configListeners.remove(id) != null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Interrupt the main thread, possibly triggering an update immediately. */
|
/**
|
||||||
public void wakeUp() {
|
* Mark the zone to be updated, then interrupt the main thread to possibly
|
||||||
eventFlag.set(true);
|
* 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()) {
|
if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) {
|
||||||
wakeupFlag.set(true);
|
wakeupFlag.set(true);
|
||||||
mainThread.interrupt();
|
mainThread.interrupt();
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ public class DataCollector implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
client.refreshAccessToken();
|
||||||
|
|
||||||
// Fetch device list
|
// Fetch device list
|
||||||
List<DeviceWithStatus> devices = client.getDevices();
|
List<DeviceWithStatus> devices = client.getDevices();
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ public class Modeler implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
client.refreshAccessToken();
|
||||||
|
|
||||||
// TODO: backfill data from database?
|
// 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.aggregators.MeanAggregator;
|
||||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
|
import com.facebook.openwifirrm.ucentral.informationelement.HTOperation;
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
|
import com.facebook.openwifirrm.ucentral.informationelement.VHTOperation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modeler utilities.
|
* Modeler utilities.
|
||||||
@@ -239,9 +239,9 @@ public class ModelerUtils {
|
|||||||
return Objects.equals(entry1.bssid, entry2.bssid) &&
|
return Objects.equals(entry1.bssid, entry2.bssid) &&
|
||||||
entry1.frequency == entry2.frequency &&
|
entry1.frequency == entry2.frequency &&
|
||||||
entry1.channel == entry2.channel &&
|
entry1.channel == entry2.channel &&
|
||||||
HTOperationElement
|
HTOperation
|
||||||
.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) &&
|
.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) &&
|
||||||
VHTOperationElement
|
VHTOperation
|
||||||
.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper);
|
.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifirrm.modules;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -102,6 +103,7 @@ public class ProvMonitor implements Runnable {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
client.refreshAccessToken();
|
||||||
|
|
||||||
// Fetch data from owprov
|
// Fetch data from owprov
|
||||||
// TODO: this may change later - for now, we only fetch inventory and
|
// TODO: this may change later - for now, we only fetch inventory and
|
||||||
@@ -159,12 +161,21 @@ public class ProvMonitor implements Runnable {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
RRMSchedule schedule = new RRMSchedule();
|
String[] crons = RRMScheduler
|
||||||
schedule.cron = RRMScheduler
|
|
||||||
.parseIntoQuartzCron(details.rrm.schedule);
|
.parseIntoQuartzCron(details.rrm.schedule);
|
||||||
if (schedule.cron == null || schedule.cron.isEmpty()) {
|
if (crons == null || crons.length == 0) {
|
||||||
return null;
|
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) {
|
if (details.rrm.algorithms != null) {
|
||||||
schedule.algorithms =
|
schedule.algorithms =
|
||||||
@@ -175,6 +186,7 @@ public class ProvMonitor implements Runnable {
|
|||||||
)
|
)
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,15 @@
|
|||||||
|
|
||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifirrm.modules;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.text.ParseException;
|
|
||||||
|
|
||||||
import org.quartz.CronScheduleBuilder;
|
|
||||||
import org.quartz.CronExpression;
|
import org.quartz.CronExpression;
|
||||||
|
import org.quartz.CronScheduleBuilder;
|
||||||
import org.quartz.Job;
|
import org.quartz.Job;
|
||||||
import org.quartz.JobBuilder;
|
import org.quartz.JobBuilder;
|
||||||
import org.quartz.JobDetail;
|
import org.quartz.JobDetail;
|
||||||
@@ -35,6 +35,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifirrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifirrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
import com.facebook.openwifirrm.RRMAlgorithm;
|
||||||
|
import com.facebook.openwifirrm.RRMSchedule;
|
||||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
|
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
@@ -74,15 +75,21 @@ public class RRMScheduler {
|
|||||||
/** The scheduler instance. */
|
/** The scheduler instance. */
|
||||||
private Scheduler scheduler;
|
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. */
|
/** RRM job. */
|
||||||
public static class RRMJob implements Job {
|
public static class RRMJob implements Job {
|
||||||
@Override
|
@Override
|
||||||
public void execute(JobExecutionContext context)
|
public void execute(JobExecutionContext context)
|
||||||
throws JobExecutionException {
|
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);
|
logger.debug("Executing job for zone: {}", zone);
|
||||||
try {
|
try {
|
||||||
SchedulerContext schedulerContext =
|
SchedulerContext schedulerContext =
|
||||||
@@ -107,13 +114,14 @@ public class RRMScheduler {
|
|||||||
* @param linuxCron Linux cron with seconds
|
* @param linuxCron Linux cron with seconds
|
||||||
* (seconds minutes hours day_of_month month day_of_week [year])
|
* (seconds minutes hours day_of_month month day_of_week [year])
|
||||||
*
|
*
|
||||||
* @throws IllegalArgumentException when a linux cron cannot be parsed
|
* @throws IllegalArgumentException when a linux cron cannot be parsed into a
|
||||||
* into a valid Quartz spec
|
* valid Quartz spec
|
||||||
* @return String a Quartz supported cron
|
* @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)) {
|
if (CronExpression.isValidExpression(linuxCron)) {
|
||||||
return linuxCron;
|
return new String[] { linuxCron };
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] split = linuxCron.split(" ");
|
String[] split = linuxCron.split(" ");
|
||||||
@@ -144,15 +152,36 @@ public class RRMScheduler {
|
|||||||
// if first case failed and only day of week is *, set to ?
|
// if first case failed and only day of week is *, set to ?
|
||||||
split[DAY_OF_WEEK_INDEX] = "?";
|
split[DAY_OF_WEEK_INDEX] = "?";
|
||||||
} else {
|
} else {
|
||||||
// Quartz does not support both values being set, so return null
|
// Quartz does not support both values being set but the standard says that
|
||||||
return null;
|
// 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);
|
String quartzCron = String.join(" ", split);
|
||||||
if (!CronExpression.isValidExpression(quartzCron)) {
|
if (!CronExpression.isValidExpression(quartzCron)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return quartzCron;
|
|
||||||
|
return new String[] { quartzCron };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor. */
|
/** Constructor. */
|
||||||
@@ -194,7 +223,7 @@ public class RRMScheduler {
|
|||||||
// Schedule job and triggers
|
// Schedule job and triggers
|
||||||
scheduler.addJob(job, false);
|
scheduler.addJob(job, false);
|
||||||
syncTriggers();
|
syncTriggers();
|
||||||
logger.info("Scheduled {} RRM trigger(s)", scheduledZones.size());
|
logger.info("Scheduled {} RRM trigger(s)", scheduledJobKeys.size());
|
||||||
|
|
||||||
// Start scheduler
|
// Start scheduler
|
||||||
scheduler.start();
|
scheduler.start();
|
||||||
@@ -218,85 +247,98 @@ public class RRMScheduler {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Synchronize triggers to the current topology, adding/updating/deleting
|
* 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() {
|
public void syncTriggers() {
|
||||||
Set<String> scheduled = ConcurrentHashMap.newKeySet();
|
Set<String> scheduled = ConcurrentHashMap.newKeySet();
|
||||||
Set<String> prevScheduled = new HashSet<>();
|
Set<String> prevScheduled = new HashSet<>();
|
||||||
if (scheduledZones != null) {
|
if (scheduledJobKeys != null) {
|
||||||
prevScheduled.addAll(scheduledZones);
|
prevScheduled.addAll(scheduledJobKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new triggers
|
// Add new triggers
|
||||||
for (String zone : deviceDataManager.getZones()) {
|
for (String zone : deviceDataManager.getZones()) {
|
||||||
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
||||||
|
RRMSchedule schedule = config.schedule;
|
||||||
if (
|
if (
|
||||||
config.schedule == null ||
|
schedule == null || schedule.crons == null ||
|
||||||
config.schedule.cron == null ||
|
schedule.crons.isEmpty()
|
||||||
config.schedule.cron.isEmpty()
|
|
||||||
) {
|
) {
|
||||||
continue; // RRM not scheduled
|
continue; // RRM not scheduled
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
for (int i = 0; i < schedule.crons.size(); i++) {
|
||||||
CronExpression.validateExpression(config.schedule.cron);
|
String cron = schedule.crons.get(i);
|
||||||
} catch (ParseException e) {
|
// if even one schedule has invalid cron, the whole thing is probably wrong
|
||||||
logger.error(
|
if (cron == null || cron.isEmpty()) {
|
||||||
String.format(
|
logger.error("There was an invalid cron in the schedule");
|
||||||
"Invalid cron expression (%s) for zone %s",
|
break;
|
||||||
config.schedule.cron,
|
}
|
||||||
zone
|
|
||||||
),
|
try {
|
||||||
e
|
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
|
// Remove old triggers
|
||||||
prevScheduled.removeAll(scheduled);
|
prevScheduled.removeAll(scheduled);
|
||||||
for (String zone : prevScheduled) {
|
for (String jobKey : prevScheduled) {
|
||||||
try {
|
try {
|
||||||
scheduler.unscheduleJob(TriggerKey.triggerKey(zone));
|
scheduler.unscheduleJob(TriggerKey.triggerKey(jobKey));
|
||||||
} catch (SchedulerException e) {
|
} catch (SchedulerException e) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Failed to remove RRM trigger for zone: " + zone,
|
"Failed to remove RRM trigger for jobKey: " + jobKey,
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
continue;
|
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. */
|
/** Run RRM algorithms for the given zone. */
|
||||||
@@ -305,16 +347,19 @@ public class RRMScheduler {
|
|||||||
|
|
||||||
// Get algorithms from zone config
|
// Get algorithms from zone config
|
||||||
DeviceConfig config = deviceDataManager.getZoneConfig(zone);
|
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);
|
logger.error("RRM schedule missing for zone '{}', aborting!", zone);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
config.schedule.algorithms == null ||
|
schedule.algorithms == null ||
|
||||||
config.schedule.algorithms.isEmpty()
|
schedule.algorithms.isEmpty()
|
||||||
) {
|
) {
|
||||||
logger.debug("Using default RRM algorithms for zone '{}'", zone);
|
logger
|
||||||
config.schedule.algorithms = Arrays.asList(
|
.debug("Using default RRM algorithms for zone '{}'", zone);
|
||||||
|
schedule.algorithms = Arrays.asList(
|
||||||
new RRMAlgorithm(
|
new RRMAlgorithm(
|
||||||
RRMAlgorithm.AlgorithmType.OptimizeChannel.name()
|
RRMAlgorithm.AlgorithmType.OptimizeChannel.name()
|
||||||
),
|
),
|
||||||
@@ -325,14 +370,15 @@ public class RRMScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Execute algorithms
|
// Execute algorithms
|
||||||
for (RRMAlgorithm algo : config.schedule.algorithms) {
|
for (RRMAlgorithm algo : schedule.algorithms) {
|
||||||
RRMAlgorithm.AlgorithmResult result = algo.run(
|
RRMAlgorithm.AlgorithmResult result = algo.run(
|
||||||
deviceDataManager,
|
deviceDataManager,
|
||||||
configManager,
|
configManager,
|
||||||
modeler,
|
modeler,
|
||||||
zone,
|
zone,
|
||||||
params.dryRun,
|
params.dryRun,
|
||||||
true /* allowDefaultMode */
|
true, /* allowDefaultMode */
|
||||||
|
false /* updateImmediately */
|
||||||
);
|
);
|
||||||
logger.info(
|
logger.info(
|
||||||
"'{}' result for zone '{}': {}",
|
"'{}' result for zone '{}': {}",
|
||||||
@@ -341,5 +387,6 @@ public class RRMScheduler {
|
|||||||
gson.toJson(result)
|
gson.toJson(result)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
configManager.queueZoneAndWakeUp(zone);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -360,7 +360,7 @@ public class DatabaseManager {
|
|||||||
/** Convert a list of state records to a State object. */
|
/** Convert a list of state records to a State object. */
|
||||||
private State toState(List<StateRecord> records, long ts) {
|
private State toState(List<StateRecord> records, long ts) {
|
||||||
State state = new State();
|
State state = new State();
|
||||||
state.unit = state.new Unit();
|
state.unit = new State.Unit();
|
||||||
state.unit.localtime = ts;
|
state.unit.localtime = ts;
|
||||||
|
|
||||||
// Parse each record
|
// 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.UCentralConstants;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
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.models.State;
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement;
|
|
||||||
import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Channel optimizer base class.
|
* Channel optimizer base class.
|
||||||
@@ -208,13 +208,13 @@ public abstract class ChannelOptimizer {
|
|||||||
return MIN_CHANNEL_WIDTH;
|
return MIN_CHANNEL_WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
HTOperationElement htOperObj = new HTOperationElement(htOper);
|
HTOperation htOperObj = new HTOperation(htOper);
|
||||||
if (vhtOper == null) {
|
if (vhtOper == null) {
|
||||||
// HT mode only supports 20/40 MHz
|
// HT mode only supports 20/40 MHz
|
||||||
return htOperObj.staChannelWidth ? 40 : 20;
|
return htOperObj.staChannelWidth ? 40 : 20;
|
||||||
} else {
|
} else {
|
||||||
// VHT/HE mode supports 20/40/160/80+80 MHz
|
// 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) {
|
if (!htOperObj.staChannelWidth && vhtOperObj.channelWidth == 0) {
|
||||||
return 20;
|
return 20;
|
||||||
} else if (
|
} else if (
|
||||||
@@ -639,14 +639,13 @@ public abstract class ChannelOptimizer {
|
|||||||
public abstract Map<String, Map<String, Integer>> computeChannelMap();
|
public abstract Map<String, Map<String, Integer>> computeChannelMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program the given channel map into the AP config and notify the config
|
* Program the given channel map into the AP config.
|
||||||
* manager.
|
|
||||||
*
|
*
|
||||||
* @param deviceDataManager the DeviceDataManager instance
|
* @param deviceDataManager the DeviceDataManager instance
|
||||||
* @param configManager the ConfigManager instance
|
* @param configManager the ConfigManager instance
|
||||||
* @param channelMap the map of devices (by serial number) to radio to channel
|
* @param channelMap the map of devices (by serial number) to radio to channel
|
||||||
*/
|
*/
|
||||||
public void applyConfig(
|
public void updateDeviceApConfig(
|
||||||
DeviceDataManager deviceDataManager,
|
DeviceDataManager deviceDataManager,
|
||||||
ConfigManager configManager,
|
ConfigManager configManager,
|
||||||
Map<String, Map<String, Integer>> channelMap
|
Map<String, Map<String, Integer>> channelMap
|
||||||
@@ -664,8 +663,5 @@ public abstract class ChannelOptimizer {
|
|||||||
deviceConfig.autoChannels = entry.getValue();
|
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.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.IntStream;
|
import java.util.stream.IntStream;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import org.slf4j.Logger;
|
||||||
import java.util.TreeMap;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.facebook.openwifirrm.DeviceConfig;
|
import com.facebook.openwifirrm.DeviceConfig;
|
||||||
import com.facebook.openwifirrm.DeviceDataManager;
|
import com.facebook.openwifirrm.DeviceDataManager;
|
||||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
import com.facebook.openwifirrm.modules.ConfigManager;
|
||||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||||
import com.facebook.openwifirrm.ucentral.models.State;
|
import com.facebook.openwifirrm.ucentral.models.State;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TPC (Transmit Power Control) base class.
|
* 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 band the operational band
|
||||||
* @param serialNumber the device
|
* @param serialNumber the device's serial number
|
||||||
* @param txPowerChoices the available tx powers of the device
|
* @param txPowerChoices the device's available tx powers
|
||||||
* @return the updated tx powers of the device
|
* @return the device's updated tx powers
|
||||||
*/
|
*/
|
||||||
protected List<Integer> updateTxPowerChoices(
|
protected List<Integer> updateTxPowerChoices(
|
||||||
String band,
|
String band,
|
||||||
String serialNumber,
|
String serialNumber,
|
||||||
List<Integer> txPowerChoices
|
List<Integer> txPowerChoices
|
||||||
) {
|
) {
|
||||||
List<Integer> newTxPowerChoices =
|
List<Integer> newTxPowerChoices = new ArrayList<>(txPowerChoices);
|
||||||
new ArrayList<>(txPowerChoices);
|
|
||||||
|
|
||||||
// Update the available tx powers based on user tx powers or allowed tx powers
|
// Update the available tx powers based on user tx powers or allowed tx powers
|
||||||
DeviceConfig deviceCfg = deviceConfigs.get(serialNumber);
|
DeviceConfig deviceCfg = deviceConfigs.get(serialNumber);
|
||||||
@@ -127,8 +126,7 @@ public abstract class TPC {
|
|||||||
newTxPowerChoices.retainAll(allowedTxPowers);
|
newTxPowerChoices.retainAll(allowedTxPowers);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the intersection of the above steps gives an empty list,
|
// If newTxPowerChoices is empty, use default available tx powers list
|
||||||
// turn back to use the default available tx powers list
|
|
||||||
if (newTxPowerChoices.isEmpty()) {
|
if (newTxPowerChoices.isEmpty()) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Device {}: the updated availableTxPowersList is empty!!! " +
|
"Device {}: the updated availableTxPowersList is empty!!! " +
|
||||||
@@ -153,14 +151,13 @@ public abstract class TPC {
|
|||||||
public abstract Map<String, Map<String, Integer>> computeTxPowerMap();
|
public abstract Map<String, Map<String, Integer>> computeTxPowerMap();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Program the given tx power map into the AP config and notify the config
|
* Program the given tx power map into the AP config.
|
||||||
* manager.
|
|
||||||
*
|
*
|
||||||
* @param deviceDataManager the DeviceDataManager instance
|
* @param deviceDataManager the DeviceDataManager instance
|
||||||
* @param configManager the ConfigManager instance
|
* @param configManager the ConfigManager instance
|
||||||
* @param txPowerMap the map of devices (by serial number) to radio to tx power
|
* @param txPowerMap the map of devices (by serial number) to radio to tx power
|
||||||
*/
|
*/
|
||||||
public void applyConfig(
|
public void updateDeviceApConfig(
|
||||||
DeviceDataManager deviceDataManager,
|
DeviceDataManager deviceDataManager,
|
||||||
ConfigManager configManager,
|
ConfigManager configManager,
|
||||||
Map<String, Map<String, Integer>> txPowerMap
|
Map<String, Map<String, Integer>> txPowerMap
|
||||||
@@ -178,15 +175,12 @@ public abstract class TPC {
|
|||||||
deviceConfig.autoTxPowers = entry.getValue();
|
deviceConfig.autoTxPowers = entry.getValue();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Trigger config update now
|
|
||||||
configManager.wakeUp();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get AP serial numbers per channel.
|
* 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() {
|
protected Map<Integer, List<String>> getApsPerChannel() {
|
||||||
Map<Integer, List<String>> apsPerChannel = new TreeMap<>();
|
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;
|
package com.facebook.openwifirrm.ucentral;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
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.StatisticsRecords;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
||||||
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
|
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.gw.models.WifiScanRequest;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.EntityList;
|
import com.facebook.openwifirrm.ucentral.prov.models.EntityList;
|
||||||
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
|
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
|
* The access token obtained from uCentralSec, needed only when using public
|
||||||
* endpoints.
|
* 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.
|
* Constructor.
|
||||||
@@ -184,7 +198,8 @@ public class UCentralClient {
|
|||||||
Map<String, Object> body = new HashMap<>();
|
Map<String, Object> body = new HashMap<>();
|
||||||
body.put("userId", username);
|
body.put("userId", username);
|
||||||
body.put("password", password);
|
body.put("password", password);
|
||||||
HttpResponse<String> response = httpPost("oauth2", OWSEC_SERVICE, body);
|
HttpResponse<String> response =
|
||||||
|
httpPost("oauth2", OWSEC_SERVICE, body, null);
|
||||||
if (!response.isSuccess()) {
|
if (!response.isSuccess()) {
|
||||||
logger.error(
|
logger.error(
|
||||||
"Login failed: Response code {}, body: {}",
|
"Login failed: Response code {}, body: {}",
|
||||||
@@ -195,27 +210,146 @@ public class UCentralClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse access token from response
|
// Parse access token from response
|
||||||
JSONObject respBody;
|
WebTokenResult token;
|
||||||
try {
|
try {
|
||||||
respBody = new JSONObject(response.getBody());
|
token = gson.fromJson(response.getBody(), WebTokenResult.class);
|
||||||
} catch (JSONException e) {
|
} catch (JsonSyntaxException e) {
|
||||||
logger.error("Login failed: Unexpected response", e);
|
logger.error("Login failed: Unexpected response", e);
|
||||||
logger.debug("Response body: {}", response.getBody());
|
logger.debug("Response body: {}", response.getBody());
|
||||||
return false;
|
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.error("Login failed: Missing access token");
|
||||||
logger.debug("Response body: {}", respBody.toString());
|
logger.debug("Response body: {}", response.getBody());
|
||||||
return false;
|
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.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
|
// Load system endpoints
|
||||||
return loadSystemEndpoints();
|
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. */
|
/** Read system endpoint URLs from uCentralSec. */
|
||||||
private boolean loadSystemEndpoints() {
|
private boolean loadSystemEndpoints() {
|
||||||
// Make request
|
// Make request
|
||||||
@@ -324,8 +458,12 @@ public class UCentralClient {
|
|||||||
.connectTimeout(connectTimeoutMs)
|
.connectTimeout(connectTimeoutMs)
|
||||||
.socketTimeout(socketTimeoutMs);
|
.socketTimeout(socketTimeoutMs);
|
||||||
if (usePublicEndpoints) {
|
if (usePublicEndpoints) {
|
||||||
if (accessToken != null) {
|
if (!isAccessTokenExpired()) {
|
||||||
req.header("Authorization", "Bearer " + accessToken);
|
req.header(
|
||||||
|
"Authorization",
|
||||||
|
"Bearer " + accessToken.access_token
|
||||||
|
);
|
||||||
|
lastAccess = Instant.now().getEpochSecond();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
req
|
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(
|
private HttpResponse<String> httpPost(
|
||||||
String endpoint,
|
String endpoint,
|
||||||
String service,
|
String service,
|
||||||
Object body
|
Object body,
|
||||||
|
Map<String, Object> parameters
|
||||||
) {
|
) {
|
||||||
return httpPost(
|
return httpPost(
|
||||||
endpoint,
|
endpoint,
|
||||||
service,
|
service,
|
||||||
body,
|
body,
|
||||||
|
parameters,
|
||||||
socketParams.connectTimeoutMs,
|
socketParams.connectTimeoutMs,
|
||||||
socketParams.socketTimeoutMs
|
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(
|
private HttpResponse<String> httpPost(
|
||||||
String endpoint,
|
String endpoint,
|
||||||
String service,
|
String service,
|
||||||
Object body,
|
Object body,
|
||||||
|
Map<String, Object> parameters,
|
||||||
int connectTimeoutMs,
|
int connectTimeoutMs,
|
||||||
int socketTimeoutMs
|
int socketTimeoutMs
|
||||||
) {
|
) {
|
||||||
@@ -367,9 +508,16 @@ public class UCentralClient {
|
|||||||
.header("accept", "application/json")
|
.header("accept", "application/json")
|
||||||
.connectTimeout(connectTimeoutMs)
|
.connectTimeout(connectTimeoutMs)
|
||||||
.socketTimeout(socketTimeoutMs);
|
.socketTimeout(socketTimeoutMs);
|
||||||
|
if (parameters != null && !parameters.isEmpty()) {
|
||||||
|
req.queryString(parameters);
|
||||||
|
}
|
||||||
if (usePublicEndpoints) {
|
if (usePublicEndpoints) {
|
||||||
if (accessToken != null) {
|
if (!isAccessTokenExpired()) {
|
||||||
req.header("Authorization", "Bearer " + accessToken);
|
req.header(
|
||||||
|
"Authorization",
|
||||||
|
"Bearer " + accessToken.access_token
|
||||||
|
);
|
||||||
|
lastAccess = Instant.now().getEpochSecond();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
req
|
req
|
||||||
@@ -454,6 +602,7 @@ public class UCentralClient {
|
|||||||
String.format("device/%s/wifiscan", serialNumber),
|
String.format("device/%s/wifiscan", serialNumber),
|
||||||
OWGW_SERVICE,
|
OWGW_SERVICE,
|
||||||
req,
|
req,
|
||||||
|
null,
|
||||||
socketParams.connectTimeoutMs,
|
socketParams.connectTimeoutMs,
|
||||||
socketParams.wifiScanTimeoutMs
|
socketParams.wifiScanTimeoutMs
|
||||||
);
|
);
|
||||||
@@ -482,7 +631,8 @@ public class UCentralClient {
|
|||||||
HttpResponse<String> response = httpPost(
|
HttpResponse<String> response = httpPost(
|
||||||
String.format("device/%s/configure", serialNumber),
|
String.format("device/%s/configure", serialNumber),
|
||||||
OWGW_SERVICE,
|
OWGW_SERVICE,
|
||||||
req
|
req,
|
||||||
|
null
|
||||||
);
|
);
|
||||||
if (!response.isSuccess()) {
|
if (!response.isSuccess()) {
|
||||||
logger.error("Error: {}", response.getBody());
|
logger.error("Error: {}", response.getBody());
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ import org.slf4j.LoggerFactory;
|
|||||||
import com.facebook.openwifirrm.RRMConfig;
|
import com.facebook.openwifirrm.RRMConfig;
|
||||||
import com.facebook.openwifirrm.Utils;
|
import com.facebook.openwifirrm.Utils;
|
||||||
import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer;
|
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.facebook.openwifirrm.ucentral.models.State;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonArray;
|
import com.google.gson.JsonArray;
|
||||||
@@ -37,6 +41,9 @@ public class UCentralUtils {
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(UCentralUtils.class);
|
LoggerFactory.getLogger(UCentralUtils.class);
|
||||||
|
|
||||||
|
/** Information Element (IE) content field key */
|
||||||
|
private static final String IE_CONTENT_FIELD_KEY = "content";
|
||||||
|
|
||||||
/** The Gson instance. */
|
/** The Gson instance. */
|
||||||
private static final Gson gson = new Gson();
|
private static final Gson gson = new Gson();
|
||||||
|
|
||||||
@@ -79,15 +86,76 @@ public class UCentralUtils {
|
|||||||
for (JsonElement e : scanInfo) {
|
for (JsonElement e : scanInfo) {
|
||||||
WifiScanEntry entry = gson.fromJson(e, WifiScanEntry.class);
|
WifiScanEntry entry = gson.fromJson(e, WifiScanEntry.class);
|
||||||
entry.unixTimeMs = timestampMs;
|
entry.unixTimeMs = timestampMs;
|
||||||
|
extractIEs(e, entry);
|
||||||
entries.add(entry);
|
entries.add(entry);
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
logger.debug("Exception when parsing wifiscan entries", e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return entries;
|
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.
|
* Set all radios config of an AP to a given value.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
|||||||
* time reference.
|
* time reference.
|
||||||
*/
|
*/
|
||||||
public long unixTimeMs;
|
public long unixTimeMs;
|
||||||
|
/** Stores Information Elements (IEs) from the wifiscan entry. */
|
||||||
|
public InformationElements ieContainer;
|
||||||
|
|
||||||
/** Default Constructor. */
|
/** Default Constructor. */
|
||||||
public WifiScanEntry() {}
|
public WifiScanEntry() {}
|
||||||
@@ -30,13 +32,14 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
|||||||
public WifiScanEntry(WifiScanEntry o) {
|
public WifiScanEntry(WifiScanEntry o) {
|
||||||
super(o);
|
super(o);
|
||||||
this.unixTimeMs = o.unixTimeMs;
|
this.unixTimeMs = o.unixTimeMs;
|
||||||
|
this.ieContainer = o.ieContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
final int prime = 31;
|
final int prime = 31;
|
||||||
int result = super.hashCode();
|
int result = super.hashCode();
|
||||||
result = prime * result + Objects.hash(unixTimeMs);
|
result = prime * result + Objects.hash(ieContainer, unixTimeMs);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,7 +55,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
WifiScanEntry other = (WifiScanEntry) obj;
|
WifiScanEntry other = (WifiScanEntry) obj;
|
||||||
return unixTimeMs == other.unixTimeMs;
|
return Objects.equals(ieContainer, other.ieContainer) &&
|
||||||
|
unixTimeMs == other.unixTimeMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.
|
* 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.Arrays;
|
||||||
import java.util.Objects;
|
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
|
* High Throughput (HT) Operation Element, which is potentially present in
|
||||||
* wifiscan entries. Introduced in 802.11n (2009).
|
* wifiscan entries. Introduced in 802.11n (2009).
|
||||||
*/
|
*/
|
||||||
public class HTOperationElement {
|
public class HTOperation {
|
||||||
|
|
||||||
/** Channel number of the primary channel. */
|
/** Channel number of the primary channel. */
|
||||||
public final byte primaryChannel;
|
public final byte primaryChannel;
|
||||||
@@ -78,7 +78,7 @@ public class HTOperationElement {
|
|||||||
* For details about the parameters, see the javadocs for the corresponding
|
* For details about the parameters, see the javadocs for the corresponding
|
||||||
* member variables.
|
* member variables.
|
||||||
*/
|
*/
|
||||||
public HTOperationElement(
|
public HTOperation(
|
||||||
byte primaryChannel,
|
byte primaryChannel,
|
||||||
byte secondaryChannelOffset,
|
byte secondaryChannelOffset,
|
||||||
boolean staChannelWidth,
|
boolean staChannelWidth,
|
||||||
@@ -114,7 +114,7 @@ public class HTOperationElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Constructor with the most used parameters. */
|
/** Constructor with the most used parameters. */
|
||||||
public HTOperationElement(
|
public HTOperation(
|
||||||
byte primaryChannel,
|
byte primaryChannel,
|
||||||
byte secondaryChannelOffset,
|
byte secondaryChannelOffset,
|
||||||
boolean staChannelWidth,
|
boolean staChannelWidth,
|
||||||
@@ -141,7 +141,7 @@ public class HTOperationElement {
|
|||||||
* @param htOper a base64 encoded properly formatted HT operation element (see
|
* @param htOper a base64 encoded properly formatted HT operation element (see
|
||||||
* 802.11)
|
* 802.11)
|
||||||
*/
|
*/
|
||||||
public HTOperationElement(String htOper) {
|
public HTOperation(String htOper) {
|
||||||
byte[] bytes = Base64.decodeBase64(htOper);
|
byte[] bytes = Base64.decodeBase64(htOper);
|
||||||
/*
|
/*
|
||||||
* Note that the code here may seem to read "reversed" compared to 802.11. This
|
* 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
|
* @return true if the the operation elements "match" for the purpose of
|
||||||
* aggregating statistics; false otherwise.
|
* aggregating statistics; false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean matchesForAggregation(HTOperationElement other) {
|
public boolean matchesForAggregation(HTOperation other) {
|
||||||
return other != null && primaryChannel == other.primaryChannel &&
|
return other != null && primaryChannel == other.primaryChannel &&
|
||||||
secondaryChannelOffset == other.secondaryChannelOffset &&
|
secondaryChannelOffset == other.secondaryChannelOffset &&
|
||||||
staChannelWidth == other.staChannelWidth &&
|
staChannelWidth == other.staChannelWidth &&
|
||||||
@@ -211,8 +211,8 @@ public class HTOperationElement {
|
|||||||
if (htOper1 == null || htOper2 == null) {
|
if (htOper1 == null || htOper2 == null) {
|
||||||
return false; // false if exactly one is null
|
return false; // false if exactly one is null
|
||||||
}
|
}
|
||||||
HTOperationElement htOperObj1 = new HTOperationElement(htOper1);
|
HTOperation htOperObj1 = new HTOperation(htOper1);
|
||||||
HTOperationElement htOperObj2 = new HTOperationElement(htOper2);
|
HTOperation htOperObj2 = new HTOperation(htOper2);
|
||||||
return htOperObj1.matchesForAggregation(htOperObj2);
|
return htOperObj1.matchesForAggregation(htOperObj2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,7 +248,7 @@ public class HTOperationElement {
|
|||||||
if (getClass() != obj.getClass()) {
|
if (getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
HTOperationElement other = (HTOperationElement) obj;
|
HTOperation other = (HTOperation) obj;
|
||||||
return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) &&
|
return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) &&
|
||||||
channelCenterFrequencySegment2 ==
|
channelCenterFrequencySegment2 ==
|
||||||
other.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.
|
* 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.Arrays;
|
||||||
import java.util.Objects;
|
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
|
* Very High Throughput (VHT) Operation Element, which is potentially present in
|
||||||
* wifiscan entries. Introduced in 802.11ac (2013).
|
* 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.
|
* 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
|
* @param vhtOper a base64 encoded properly formatted VHT operation element (see
|
||||||
* 802.11 standard)
|
* 802.11 standard)
|
||||||
*/
|
*/
|
||||||
public VHTOperationElement(String vhtOper) {
|
public VHTOperation(String vhtOper) {
|
||||||
byte[] bytes = Base64.decodeBase64(vhtOper);
|
byte[] bytes = Base64.decodeBase64(vhtOper);
|
||||||
this.channelWidth = bytes[0];
|
this.channelWidth = bytes[0];
|
||||||
this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value
|
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
|
* For details about the parameters, see the javadocs for the corresponding
|
||||||
* member variables.
|
* member variables.
|
||||||
*/
|
*/
|
||||||
public VHTOperationElement(
|
public VHTOperation(
|
||||||
byte channelWidth,
|
byte channelWidth,
|
||||||
short channel1,
|
short channel1,
|
||||||
short channel2,
|
short channel2,
|
||||||
@@ -114,7 +114,7 @@ public class VHTOperationElement {
|
|||||||
* @return true if the the operation elements "match" for the purpose of
|
* @return true if the the operation elements "match" for the purpose of
|
||||||
* aggregating statistics; false otherwise.
|
* aggregating statistics; false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean matchesForAggregation(VHTOperationElement other) {
|
public boolean matchesForAggregation(VHTOperation other) {
|
||||||
// check everything except vhtMcsForNss
|
// check everything except vhtMcsForNss
|
||||||
return other != null && channel1 == other.channel1 &&
|
return other != null && channel1 == other.channel1 &&
|
||||||
channel2 == other.channel2 && channelWidth == other.channelWidth;
|
channel2 == other.channel2 && channelWidth == other.channelWidth;
|
||||||
@@ -142,8 +142,8 @@ public class VHTOperationElement {
|
|||||||
if (vhtOper1 == null || vhtOper2 == null) {
|
if (vhtOper1 == null || vhtOper2 == null) {
|
||||||
return false; // false if exactly one is null
|
return false; // false if exactly one is null
|
||||||
}
|
}
|
||||||
VHTOperationElement vhtOperObj1 = new VHTOperationElement(vhtOper1);
|
VHTOperation vhtOperObj1 = new VHTOperation(vhtOper1);
|
||||||
VHTOperationElement vhtOperObj2 = new VHTOperationElement(vhtOper2);
|
VHTOperation vhtOperObj2 = new VHTOperation(vhtOper2);
|
||||||
return vhtOperObj1.matchesForAggregation(vhtOperObj2);
|
return vhtOperObj1.matchesForAggregation(vhtOperObj2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -168,7 +168,7 @@ public class VHTOperationElement {
|
|||||||
if (getClass() != obj.getClass()) {
|
if (getClass() != obj.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
VHTOperationElement other = (VHTOperationElement) obj;
|
VHTOperation other = (VHTOperation) obj;
|
||||||
return channel1 == other.channel1 && channel2 == other.channel2 &&
|
return channel1 == other.channel1 && channel2 == other.channel2 &&
|
||||||
channelWidth == other.channelWidth &&
|
channelWidth == other.channelWidth &&
|
||||||
Arrays.equals(vhtMcsForNss, other.vhtMcsForNss);
|
Arrays.equals(vhtMcsForNss, other.vhtMcsForNss);
|
||||||
@@ -12,8 +12,8 @@ import com.google.gson.JsonObject;
|
|||||||
import com.google.gson.annotations.SerializedName;
|
import com.google.gson.annotations.SerializedName;
|
||||||
|
|
||||||
public class State {
|
public class State {
|
||||||
public class Interface {
|
public static class Interface {
|
||||||
public class Client {
|
public static class Client {
|
||||||
public String mac;
|
public String mac;
|
||||||
public String[] ipv4_addresses;
|
public String[] ipv4_addresses;
|
||||||
public String[] ipv6_addresses;
|
public String[] ipv6_addresses;
|
||||||
@@ -21,9 +21,9 @@ public class State {
|
|||||||
// TODO last_seen
|
// TODO last_seen
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SSID {
|
public static class SSID {
|
||||||
public class Association {
|
public static class Association {
|
||||||
public class Rate {
|
public static class Rate {
|
||||||
public long bitrate;
|
public long bitrate;
|
||||||
public int chwidth;
|
public int chwidth;
|
||||||
public boolean sgi;
|
public boolean sgi;
|
||||||
@@ -66,7 +66,7 @@ public class State {
|
|||||||
public JsonObject radio;
|
public JsonObject radio;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class Counters {
|
public static class Counters {
|
||||||
public long collisions;
|
public long collisions;
|
||||||
public long multicast;
|
public long multicast;
|
||||||
public long rx_bytes;
|
public long rx_bytes;
|
||||||
@@ -96,8 +96,8 @@ public class State {
|
|||||||
|
|
||||||
public Interface[] interfaces;
|
public Interface[] interfaces;
|
||||||
|
|
||||||
public class Unit {
|
public static class Unit {
|
||||||
public class Memory {
|
public static class Memory {
|
||||||
public long buffered;
|
public long buffered;
|
||||||
public long cached;
|
public long cached;
|
||||||
public long free;
|
public long free;
|
||||||
|
|||||||
@@ -10,9 +10,11 @@ package com.facebook.openwifirrm.ucentral.models;
|
|||||||
|
|
||||||
import java.util.Objects;
|
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 class WifiScanEntryResult {
|
||||||
public int channel;
|
public int channel;
|
||||||
public long last_seen;
|
public long last_seen;
|
||||||
@@ -50,8 +52,6 @@ public class WifiScanEntryResult {
|
|||||||
public String vht_oper;
|
public String vht_oper;
|
||||||
public int capability;
|
public int capability;
|
||||||
public int frequency;
|
public int frequency;
|
||||||
/** IE = information element */
|
|
||||||
public JsonArray ies;
|
|
||||||
|
|
||||||
/** Default Constructor. */
|
/** Default Constructor. */
|
||||||
public WifiScanEntryResult() {}
|
public WifiScanEntryResult() {}
|
||||||
@@ -68,7 +68,6 @@ public class WifiScanEntryResult {
|
|||||||
this.vht_oper = o.vht_oper;
|
this.vht_oper = o.vht_oper;
|
||||||
this.capability = o.capability;
|
this.capability = o.capability;
|
||||||
this.frequency = o.frequency;
|
this.frequency = o.frequency;
|
||||||
this.ies = o.ies;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -79,7 +78,6 @@ public class WifiScanEntryResult {
|
|||||||
channel,
|
channel,
|
||||||
frequency,
|
frequency,
|
||||||
ht_oper,
|
ht_oper,
|
||||||
ies,
|
|
||||||
last_seen,
|
last_seen,
|
||||||
signal,
|
signal,
|
||||||
ssid,
|
ssid,
|
||||||
@@ -104,7 +102,9 @@ public class WifiScanEntryResult {
|
|||||||
capability == other.capability && channel == other.channel &&
|
capability == other.capability && channel == other.channel &&
|
||||||
frequency == other.frequency && Objects
|
frequency == other.frequency && Objects
|
||||||
.equals(ht_oper, other.ht_oper) &&
|
.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
|
@Override
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import java.util.List;
|
|||||||
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
|
import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
|
||||||
|
|
||||||
public class DeviceConfiguration {
|
public class DeviceConfiguration {
|
||||||
public class DeviceConfigurationElement {
|
public static class DeviceConfigurationElement {
|
||||||
public String name;
|
public String name;
|
||||||
public String description;
|
public String description;
|
||||||
public Integer weight;
|
public Integer weight;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ package com.facebook.openwifirrm.ucentral.prov.models;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public class RRMDetails {
|
public class RRMDetails {
|
||||||
public class RRMDetailsImpl {
|
public static class RRMDetailsImpl {
|
||||||
public String vendor;
|
public String vendor;
|
||||||
public String schedule;
|
public String schedule;
|
||||||
public List<RRMAlgorithmDetails> algorithms;
|
public List<RRMAlgorithmDetails> algorithms;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
package com.facebook.openwifirrm.modules;
|
package com.facebook.openwifirrm.modules;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
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;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
@@ -23,48 +23,48 @@ public class RRMSchedulerTest {
|
|||||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * * * *"));
|
assertNull(RRMScheduler.parseIntoQuartzCron("* * * * * * * *"));
|
||||||
|
|
||||||
// correct (6 fields)
|
// correct (6 fields)
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * *",
|
new String[] { "* * * ? * *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * *")
|
||||||
);
|
);
|
||||||
|
|
||||||
// correct (7 fields)
|
// correct (7 fields)
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * * *",
|
new String[] { "* * * ? * * *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * * *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * * *")
|
||||||
);
|
);
|
||||||
|
|
||||||
// correct value other than * for day of month
|
// correct value other than * for day of month
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1 * ?",
|
new String[] { "* * * 1 * ?" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1 * *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1 * ? *",
|
new String[] { "* * * 1 * ? *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1 * * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1 * * *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1/2 * ?",
|
new String[] { "* * * 1/2 * ?" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1/2 * ? *",
|
new String[] { "* * * 1/2 * ? *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1/2 * * *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1-2 * ?",
|
new String[] { "* * * 1-2 * ?" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1-2 * ? *",
|
new String[] { "* * * 1-2 * ? *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1-2 * * *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1,2 * ?",
|
new String[] { "* * * 1,2 * ?" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * 1,2 * ? *",
|
new String[] { "* * * 1,2 * ? *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * * *")
|
RRMScheduler.parseIntoQuartzCron("* * * 1,2 * * *")
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -79,70 +79,70 @@ public class RRMSchedulerTest {
|
|||||||
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * * *"));
|
assertNull(RRMScheduler.parseIntoQuartzCron("* * * 0,1 * * *"));
|
||||||
|
|
||||||
// correct value other than * for day of month
|
// correct value other than * for day of month
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1",
|
new String[] { "* * * ? * 1" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1 *",
|
new String[] { "* * * ? * 1 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1 *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1 *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1/3",
|
new String[] { "* * * ? * 1/3" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1/3 *",
|
new String[] { "* * * ? * 1/3 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3 *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1/3 *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1-3",
|
new String[] { "* * * ? * 1-3" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1-3 *",
|
new String[] { "* * * ? * 1-3 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3 *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1-3 *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1,3",
|
new String[] { "* * * ? * 1,3" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1,3 *",
|
new String[] { "* * * ? * 1,3 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3 *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1,3 *")
|
||||||
);
|
);
|
||||||
|
|
||||||
// correct value other than * for day of month, make sure 0 turns into 7
|
// correct value other than * for day of month, make sure 0 turns into 7
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 7",
|
new String[] { "* * * ? * 7" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 0")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 0")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 7 *",
|
new String[] { "* * * ? * 7 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 0 *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 0 *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1/7",
|
new String[] { "* * * ? * 1/7" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1/7 *",
|
new String[] { "* * * ? * 1/7 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0 *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1/0 *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1-7",
|
new String[] { "* * * ? * 1-7" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1-7 *",
|
new String[] { "* * * ? * 1-7 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0 *")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1-0 *")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1,7",
|
new String[] { "* * * ? * 1,7" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,0")
|
RRMScheduler.parseIntoQuartzCron("* * * * * 1,0")
|
||||||
);
|
);
|
||||||
assertEquals(
|
assertArrayEquals(
|
||||||
"* * * ? * 1,7 *",
|
new String[] { "* * * ? * 1,7 *" },
|
||||||
RRMScheduler.parseIntoQuartzCron("* * * * * 1,0 *")
|
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"));
|
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;
|
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
|
* Create an array with two radio info entries (2G and 5G), with the given
|
||||||
* tx powers and channels.
|
* tx powers and channels.
|
||||||
@@ -480,7 +500,7 @@ public class TestUtils {
|
|||||||
new State.Interface.SSID.Association[clientRssis[i].length];
|
new State.Interface.SSID.Association[clientRssis[i].length];
|
||||||
for (int j = 0; j < clientRssis[i].length; j++) {
|
for (int j = 0; j < clientRssis[i].length; j++) {
|
||||||
state.interfaces[i].ssids[0].associations[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 =
|
state.interfaces[i].ssids[0].associations[j].rssi =
|
||||||
clientRssis[i][j];
|
clientRssis[i][j];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import com.facebook.openwifirrm.optimizers.TestUtils;
|
|||||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
||||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||||
import com.facebook.openwifirrm.ucentral.models.State;
|
|
||||||
|
|
||||||
@TestMethodOrder(OrderAnnotation.class)
|
@TestMethodOrder(OrderAnnotation.class)
|
||||||
public class MeasurementBasedApApTPCTest {
|
public class MeasurementBasedApApTPCTest {
|
||||||
@@ -77,65 +76,84 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
return deviceDataManager;
|
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
|
* Creates a data model with 3 devices. All are at max_tx_power, which
|
||||||
* represents the first step in greedy TPC.
|
* represents the first step in greedy TPC.
|
||||||
*
|
*
|
||||||
* @return a data model
|
* @return a data model
|
||||||
*/
|
*/
|
||||||
private static DataModel createModel() {
|
private static DataModel createModelDualBand() {
|
||||||
DataModel model = new DataModel();
|
DataModel model = new DataModel();
|
||||||
|
|
||||||
State stateA = TestUtils.createState(
|
final int channel2G =
|
||||||
1,
|
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
||||||
DEFAULT_CHANNEL_WIDTH,
|
final int channel5G =
|
||||||
MAX_TX_POWER,
|
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_5G);
|
||||||
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
|
|
||||||
);
|
|
||||||
|
|
||||||
model.latestState.put(DEVICE_A, stateA);
|
List<String> bssids = Arrays.asList(BSSID_A, BSSID_B, BSSID_C);
|
||||||
model.latestState.put(DEVICE_B, stateB);
|
List<String> devices = Arrays.asList(DEVICE_A, DEVICE_B, DEVICE_C);
|
||||||
model.latestState.put(DEVICE_C, stateC);
|
for (int i = 0; i < devices.size(); i++) {
|
||||||
|
String device = devices.get(i);
|
||||||
model.latestDeviceStatusRadios.put(
|
String bssid = bssids.get(i);
|
||||||
DEVICE_A,
|
model.latestState.put(
|
||||||
TestUtils
|
device,
|
||||||
.createDeviceStatusDualBand(1, MAX_TX_POWER, 36, MAX_TX_POWER)
|
TestUtils.createState(
|
||||||
);
|
channel2G,
|
||||||
model.latestDeviceStatusRadios.put(
|
DEFAULT_CHANNEL_WIDTH,
|
||||||
DEVICE_B,
|
MAX_TX_POWER,
|
||||||
TestUtils
|
bssid,
|
||||||
.createDeviceStatusDualBand(1, MAX_TX_POWER, 36, MAX_TX_POWER)
|
channel5G,
|
||||||
);
|
DEFAULT_CHANNEL_WIDTH,
|
||||||
model.latestDeviceStatusRadios.put(
|
MAX_TX_POWER,
|
||||||
DEVICE_C,
|
bssid
|
||||||
TestUtils
|
)
|
||||||
.createDeviceStatusDualBand(1, MAX_TX_POWER, 36, MAX_TX_POWER)
|
);
|
||||||
);
|
model.latestDeviceStatusRadios.put(
|
||||||
|
device,
|
||||||
|
TestUtils
|
||||||
|
.createDeviceStatusDualBand(
|
||||||
|
channel2G,
|
||||||
|
MAX_TX_POWER,
|
||||||
|
channel5G,
|
||||||
|
MAX_TX_POWER
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
@@ -286,7 +304,7 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
void testGetManagedBSSIDs() throws Exception {
|
void testGetManagedBSSIDs() throws Exception {
|
||||||
DataModel dataModel = createModel();
|
DataModel dataModel = createModelDualBand();
|
||||||
Set<String> managedBSSIDs =
|
Set<String> managedBSSIDs =
|
||||||
MeasurementBasedApApTPC.getManagedBSSIDs(dataModel);
|
MeasurementBasedApApTPC.getManagedBSSIDs(dataModel);
|
||||||
assertEquals(3, managedBSSIDs.size());
|
assertEquals(3, managedBSSIDs.size());
|
||||||
@@ -393,7 +411,7 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
*/
|
*/
|
||||||
private static void testComputeTxPowerMapSimpleInOneBand(String band) {
|
private static void testComputeTxPowerMapSimpleInOneBand(String band) {
|
||||||
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
||||||
DataModel dataModel = createModel();
|
DataModel dataModel = createModelSingleBand(band);
|
||||||
dataModel.latestWifiScans = createLatestWifiScansB(channel);
|
dataModel.latestWifiScans = createLatestWifiScansB(channel);
|
||||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||||
|
|
||||||
@@ -418,7 +436,7 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
String band
|
String band
|
||||||
) {
|
) {
|
||||||
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
||||||
DataModel dataModel = createModel();
|
DataModel dataModel = createModelSingleBand(band);
|
||||||
dataModel.latestWifiScans = createLatestWifiScansC(channel);
|
dataModel.latestWifiScans = createLatestWifiScansC(channel);
|
||||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||||
|
|
||||||
@@ -445,7 +463,7 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
*/
|
*/
|
||||||
private static void testComputeTxPowerMapMissingDataInOneBand(String band) {
|
private static void testComputeTxPowerMapMissingDataInOneBand(String band) {
|
||||||
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
int channel = UCentralUtils.LOWER_CHANNEL_LIMIT.get(band);
|
||||||
DataModel dataModel = createModel();
|
DataModel dataModel = createModelSingleBand(band);
|
||||||
dataModel.latestWifiScans =
|
dataModel.latestWifiScans =
|
||||||
createLatestWifiScansWithMissingEntries(channel);
|
createLatestWifiScansWithMissingEntries(channel);
|
||||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||||
@@ -481,35 +499,17 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
@Order(6)
|
@Order(6)
|
||||||
void testComputeTxPowerMapMultiBand() {
|
void testComputeTxPowerMapMultiBand() {
|
||||||
// test both bands simultaneously with different setups on each band
|
// test both bands simultaneously with different setups on each band
|
||||||
DataModel dataModel = createModel();
|
DataModel dataModel = createModelDualBand();
|
||||||
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
DeviceDataManager deviceDataManager = createDeviceDataManager();
|
||||||
// 2G setup
|
// 2G: use testComputeTxPowerMapSimpleInOneBand setup
|
||||||
final int channel2G =
|
final int channel2G =
|
||||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
||||||
dataModel.latestWifiScans = createLatestWifiScansB(channel2G);
|
dataModel.latestWifiScans = createLatestWifiScansB(channel2G);
|
||||||
// 5G setup
|
// 5G: use testComputeTxPowerMapMissingDataInOneBand setup
|
||||||
final int channel5G =
|
final int channel5G =
|
||||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_5G);
|
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_5G);
|
||||||
|
// add 5G wifiscan results to dataModel.latestWifiScans
|
||||||
Map<String, List<List<WifiScanEntry>>> toMerge =
|
Map<String, List<List<WifiScanEntry>>> toMerge =
|
||||||
createLatestWifiScansWithMissingEntries(channel5G);
|
createLatestWifiScansWithMissingEntries(channel5G);
|
||||||
for (
|
for (
|
||||||
@@ -537,8 +537,10 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
Map<String, Map<String, Integer>> txPowerMap =
|
Map<String, Map<String, Integer>> txPowerMap =
|
||||||
optimizer.computeTxPowerMap();
|
optimizer.computeTxPowerMap();
|
||||||
|
|
||||||
// test 2G band
|
// every AP operates in at least one band
|
||||||
assertEquals(3, txPowerMap.size());
|
assertEquals(3, txPowerMap.size());
|
||||||
|
|
||||||
|
// test 2G band
|
||||||
assertEquals(
|
assertEquals(
|
||||||
2,
|
2,
|
||||||
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_2G)
|
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_2G)
|
||||||
@@ -557,11 +559,69 @@ public class MeasurementBasedApApTPCTest {
|
|||||||
0,
|
0,
|
||||||
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_5G)
|
txPowerMap.get(DEVICE_A).get(UCentralConstants.BAND_5G)
|
||||||
);
|
);
|
||||||
// deivce B does not have 5G radio
|
assertEquals(
|
||||||
assertFalse(
|
0,
|
||||||
txPowerMap.get(DEVICE_B).containsKey(UCentralConstants.BAND_5G)
|
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(
|
assertFalse(
|
||||||
txPowerMap.get(DEVICE_C).containsKey(UCentralConstants.BAND_5G)
|
txPowerMap.get(DEVICE_C).containsKey(UCentralConstants.BAND_5G)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,17 +6,17 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class HTOperationElementTest {
|
public class HTOperationTest {
|
||||||
@Test
|
@Test
|
||||||
void testGetHtOper() {
|
void testGetHtOper() {
|
||||||
String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
||||||
HTOperationElement htOperObj = new HTOperationElement(htOper);
|
HTOperation htOperObj = new HTOperation(htOper);
|
||||||
byte expectedPrimaryChannel = 1;
|
byte expectedPrimaryChannel = 1;
|
||||||
byte expectedSecondaryChannelOffset = 0;
|
byte expectedSecondaryChannelOffset = 0;
|
||||||
boolean expectedStaChannelWidth = false;
|
boolean expectedStaChannelWidth = false;
|
||||||
@@ -28,7 +28,7 @@ public class HTOperationElementTest {
|
|||||||
boolean expectedDualBeacon = false;
|
boolean expectedDualBeacon = false;
|
||||||
boolean expectedDualCtsProtection = false;
|
boolean expectedDualCtsProtection = false;
|
||||||
boolean expectedStbcBeacon = false;
|
boolean expectedStbcBeacon = false;
|
||||||
HTOperationElement expectedHtOperObj = new HTOperationElement(
|
HTOperation expectedHtOperObj = new HTOperation(
|
||||||
expectedPrimaryChannel,
|
expectedPrimaryChannel,
|
||||||
expectedSecondaryChannelOffset,
|
expectedSecondaryChannelOffset,
|
||||||
expectedStaChannelWidth,
|
expectedStaChannelWidth,
|
||||||
@@ -44,11 +44,11 @@ public class HTOperationElementTest {
|
|||||||
assertEquals(expectedHtOperObj, htOperObj);
|
assertEquals(expectedHtOperObj, htOperObj);
|
||||||
|
|
||||||
htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
|
||||||
htOperObj = new HTOperationElement(htOper);
|
htOperObj = new HTOperation(htOper);
|
||||||
// all fields except the primary channel and nongreenfield field are the same
|
// all fields except the primary channel and nongreenfield field are the same
|
||||||
expectedPrimaryChannel = 36;
|
expectedPrimaryChannel = 36;
|
||||||
expectedNongreenfieldHtStasPresent = false;
|
expectedNongreenfieldHtStasPresent = false;
|
||||||
expectedHtOperObj = new HTOperationElement(
|
expectedHtOperObj = new HTOperation(
|
||||||
expectedPrimaryChannel,
|
expectedPrimaryChannel,
|
||||||
expectedSecondaryChannelOffset,
|
expectedSecondaryChannelOffset,
|
||||||
expectedStaChannelWidth,
|
expectedStaChannelWidth,
|
||||||
@@ -6,23 +6,23 @@
|
|||||||
* LICENSE file in the root directory of this source tree.
|
* 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 static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
public class VHTOperationElementTest {
|
public class VHTOperationTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGetVhtOper() {
|
void testGetVhtOper() {
|
||||||
String vhtOper = "ACQAAAA=";
|
String vhtOper = "ACQAAAA=";
|
||||||
VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper);
|
VHTOperation vhtOperObj = new VHTOperation(vhtOper);
|
||||||
byte expectedChannelWidthIndicator = 0; // 20 MHz channel width
|
byte expectedChannelWidthIndicator = 0; // 20 MHz channel width
|
||||||
short expectedChannel1 = 36;
|
short expectedChannel1 = 36;
|
||||||
short expectedChannel2 = 0;
|
short expectedChannel2 = 0;
|
||||||
byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
VHTOperationElement expectedVhtOperObj = new VHTOperationElement(
|
VHTOperation expectedVhtOperObj = new VHTOperation(
|
||||||
expectedChannelWidthIndicator,
|
expectedChannelWidthIndicator,
|
||||||
expectedChannel1,
|
expectedChannel1,
|
||||||
expectedChannel2,
|
expectedChannel2,
|
||||||
@@ -31,12 +31,12 @@ public class VHTOperationElementTest {
|
|||||||
assertEquals(expectedVhtOperObj, vhtOperObj);
|
assertEquals(expectedVhtOperObj, vhtOperObj);
|
||||||
|
|
||||||
vhtOper = "AToAUAE=";
|
vhtOper = "AToAUAE=";
|
||||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
vhtOperObj = new VHTOperation(vhtOper);
|
||||||
expectedChannelWidthIndicator = 1; // 80 MHz channel width
|
expectedChannelWidthIndicator = 1; // 80 MHz channel width
|
||||||
expectedChannel1 = 58;
|
expectedChannel1 = 58;
|
||||||
// same channel2
|
// same channel2
|
||||||
expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 };
|
expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 };
|
||||||
expectedVhtOperObj = new VHTOperationElement(
|
expectedVhtOperObj = new VHTOperation(
|
||||||
expectedChannelWidthIndicator,
|
expectedChannelWidthIndicator,
|
||||||
expectedChannel1,
|
expectedChannel1,
|
||||||
expectedChannel2,
|
expectedChannel2,
|
||||||
@@ -45,12 +45,12 @@ public class VHTOperationElementTest {
|
|||||||
assertEquals(expectedVhtOperObj, vhtOperObj);
|
assertEquals(expectedVhtOperObj, vhtOperObj);
|
||||||
|
|
||||||
vhtOper = "ASoyUAE=";
|
vhtOper = "ASoyUAE=";
|
||||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
vhtOperObj = new VHTOperation(vhtOper);
|
||||||
// same channel width indicator (160 MHz channel width)
|
// same channel width indicator (160 MHz channel width)
|
||||||
expectedChannel1 = 42;
|
expectedChannel1 = 42;
|
||||||
expectedChannel2 = 50;
|
expectedChannel2 = 50;
|
||||||
// same vhtMcsForNss
|
// same vhtMcsForNss
|
||||||
expectedVhtOperObj = new VHTOperationElement(
|
expectedVhtOperObj = new VHTOperation(
|
||||||
expectedChannelWidthIndicator,
|
expectedChannelWidthIndicator,
|
||||||
expectedChannel1,
|
expectedChannel1,
|
||||||
expectedChannel2,
|
expectedChannel2,
|
||||||
@@ -60,12 +60,12 @@ public class VHTOperationElementTest {
|
|||||||
|
|
||||||
// test with channel number >= 128 (channel fields should be unsigned)
|
// test with channel number >= 128 (channel fields should be unsigned)
|
||||||
vhtOper = "AJUAAAA=";
|
vhtOper = "AJUAAAA=";
|
||||||
vhtOperObj = new VHTOperationElement(vhtOper);
|
vhtOperObj = new VHTOperation(vhtOper);
|
||||||
expectedChannelWidthIndicator = 0;
|
expectedChannelWidthIndicator = 0;
|
||||||
expectedChannel1 = 149;
|
expectedChannel1 = 149;
|
||||||
expectedChannel2 = 0;
|
expectedChannel2 = 0;
|
||||||
expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
expectedVhtOperObj = new VHTOperationElement(
|
expectedVhtOperObj = new VHTOperation(
|
||||||
expectedChannelWidthIndicator,
|
expectedChannelWidthIndicator,
|
||||||
expectedChannel1,
|
expectedChannel1,
|
||||||
expectedChannel2,
|
expectedChannel2,
|
||||||
Reference in New Issue
Block a user