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