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