mirror of
				https://github.com/Telecominfraproject/wlan-cloud-rrm.git
				synced 2025-10-31 02:28:15 +00:00 
			
		
		
		
	Compare commits
	
		
			29 Commits
		
	
	
		
			v2.7.0-RC2
			...
			scale-test
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0279437f5c | ||
|   | c635af6c1d | ||
|   | 37a904087d | ||
|   | 6df81b7fef | ||
|   | 8171cc74ae | ||
|   | 215d73fee5 | ||
|   | ecbf8fa644 | ||
|   | 19928e0286 | ||
|   | da978611d0 | ||
|   | e42eaa747b | ||
|   | 264f114be2 | ||
|   | dd2b485b00 | ||
|   | 033d93beff | ||
|   | e5d5f7d5c0 | ||
|   | 0b4fd49627 | ||
|   | d81df03637 | ||
|   | 594fd9fa91 | ||
|   | 8c48a8901b | ||
|   | 0ac189f493 | ||
|   | df21d07ec9 | ||
|   | 01a070c9b7 | ||
|   | 5211eae7c6 | ||
|   | fafbda0bd8 | ||
|   | 43c9aaafb2 | ||
|   | 89e637cfeb | ||
|   | 0a64fb4963 | ||
|   | 4191bc1a70 | ||
|   | 3b6e83d103 | ||
|   | 27c36ff444 | 
							
								
								
									
										2
									
								
								.github/workflows/deploy-gh-pages.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/deploy-gh-pages.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,7 +18,7 @@ jobs: | |||||||
|           distribution: 'adopt' |           distribution: 'adopt' | ||||||
|           cache: maven |           cache: maven | ||||||
|       - name: Build with Maven |       - name: Build with Maven | ||||||
|         run: mvn javadoc:javadoc |         run: mvn javadoc:aggregate | ||||||
|       - name: Deploy to GitHub Pages |       - name: Deploy to GitHub Pages | ||||||
|         uses: peaceiris/actions-gh-pages@v3 |         uses: peaceiris/actions-gh-pages@v3 | ||||||
|         with: |         with: | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										11
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,12 +1,9 @@ | |||||||
| /target | /target/ | ||||||
| /*.log* | */target/ | ||||||
| /device_config.json |  | ||||||
| /settings.json |  | ||||||
| /topology.json |  | ||||||
|  |  | ||||||
| # Eclipse | # Eclipse | ||||||
| /.settings/ | .settings/ | ||||||
| /bin/ | bin/ | ||||||
| .metadata | .metadata | ||||||
| .classpath | .classpath | ||||||
| .project | .project | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| FROM maven:3-jdk-11 as build | FROM maven:3-eclipse-temurin-11 as build | ||||||
| WORKDIR /usr/src/java | WORKDIR /usr/src/java | ||||||
| COPY . . | COPY . . | ||||||
| RUN mvn clean package -DappendVersionString="$(./scripts/get_build_version.sh)" | RUN mvn clean package -pl owrrm -am -DappendVersionString="$(./scripts/get_build_version.sh)" | ||||||
|  |  | ||||||
| FROM adoptopenjdk/openjdk11-openj9:latest | FROM adoptopenjdk/openjdk11-openj9:latest | ||||||
| RUN apt-get update && apt-get install -y gettext-base wget | RUN apt-get update && apt-get install -y gettext-base wget | ||||||
| @@ -10,8 +10,8 @@ RUN wget https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentr | |||||||
| RUN mkdir /owrrm-data | RUN mkdir /owrrm-data | ||||||
| WORKDIR /usr/src/java | WORKDIR /usr/src/java | ||||||
| COPY docker-entrypoint.sh / | COPY docker-entrypoint.sh / | ||||||
| COPY --from=build /usr/src/java/target/openwifi-rrm.jar /usr/local/bin/ | COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/ | ||||||
| EXPOSE 16789 | EXPOSE 16789 16790 | ||||||
| ENTRYPOINT ["/docker-entrypoint.sh"] | ENTRYPOINT ["/docker-entrypoint.sh"] | ||||||
| CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \ | CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \ | ||||||
|      "-jar", "/usr/local/bin/openwifi-rrm.jar", \ |      "-jar", "/usr/local/bin/openwifi-rrm.jar", \ | ||||||
|   | |||||||
							
								
								
									
										43
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										43
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,12 +1,10 @@ | |||||||
| # OpenWiFi RRM Service | # OpenWiFi RRM Service | ||||||
| OpenWiFi uCentral-based radio resource management (RRM) service, providing a | [See here](owrrm/README.md) for details. | ||||||
| cloud-based Wi-Fi RRM layer for APs running the OpenWiFi SDK. |  | ||||||
|  |  | ||||||
| This service collects data from OpenWiFi APs (e.g. Wi-Fi scans, stats, | ## Project Structure | ||||||
| capabilities) via the uCentral Gateway and Kafka, and integrates with the | This is an [Apache Maven] project with the following modules: | ||||||
| OpenWiFi Provisioning service to perform optimization across configured | * `lib-cloudsdk` - OpenWiFi CloudSDK Java Library | ||||||
| "venues". It pushes new device configuration parameters to APs after RRM | * `owrrm` - OpenWiFi RRM Service | ||||||
| algorithms are run (manually or periodically). |  | ||||||
|  |  | ||||||
| ## Requirements | ## Requirements | ||||||
| * **Running:** JRE 11. | * **Running:** JRE 11. | ||||||
| @@ -16,7 +14,7 @@ algorithms are run (manually or periodically). | |||||||
| ``` | ``` | ||||||
| $ mvn package [-DskipTests] | $ mvn package [-DskipTests] | ||||||
| ``` | ``` | ||||||
| This will build a runnable JAR located at `target/openwifi-rrm.jar`. | This will build a runnable JAR located at `owrrm/target/openwifi-rrm.jar`. | ||||||
|  |  | ||||||
| Alternatively, Docker builds can be launched using the provided | Alternatively, Docker builds can be launched using the provided | ||||||
| [Dockerfile](Dockerfile). | [Dockerfile](Dockerfile). | ||||||
| @@ -27,34 +25,7 @@ $ mvn test | |||||||
| ``` | ``` | ||||||
| Unit tests are written using [JUnit 5]. | Unit tests are written using [JUnit 5]. | ||||||
|  |  | ||||||
| ## Usage | ## Code Style | ||||||
| ``` |  | ||||||
| $ java -jar openwifi-rrm.jar [-h] |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| To start the service, use the `run` command while providing configuration via |  | ||||||
| either environment variables (`--config-env`) or a static JSON file |  | ||||||
| (`--config-file`, default `settings.json`). The following data is *required*: |  | ||||||
| * Service configuration |  | ||||||
|     * Env: `SERVICECONFIG_PRIVATEENDPOINT`, `SERVICECONFIG_PUBLICENDPOINT` |  | ||||||
|     * JSON: `serviceConfig` structure |  | ||||||
| * Kafka broker URL |  | ||||||
|     * Env: `KAFKACONFIG_BOOTSTRAPSERVER` |  | ||||||
|     * JSON: `kafkaConfig` structure |  | ||||||
| * MySQL database credentials |  | ||||||
|     * Env: `DATABASECONFIG_SERVER`, `DATABASECONFIG_USER`, `DATABASECONFIG_PASSWORD` |  | ||||||
|     * JSON: `databaseConfig` structure |  | ||||||
|  |  | ||||||
| ## OpenAPI |  | ||||||
| This service provides an OpenAPI HTTP interface on the port specified in the |  | ||||||
| service configuration (`moduleConfig.apiServerParams`). An auto-generated |  | ||||||
| OpenAPI 3.0 document is hosted at the endpoints `/openapi.{yaml,json}` and is |  | ||||||
| written to [openapi.yaml](openapi.yaml) during the Maven "compile" phase. |  | ||||||
|  |  | ||||||
| ## For Developers |  | ||||||
| See [IMPLEMENTATION.md](IMPLEMENTATION.md) for service architecture details and |  | ||||||
| [ALGORITHMS.md](ALGORITHMS.md) for descriptions of the RRM algorithms. |  | ||||||
|  |  | ||||||
| Code is auto-formatted using [Spotless] with a custom Eclipse style config (see | Code is auto-formatted using [Spotless] with a custom Eclipse style config (see | ||||||
| [spotless/eclipse-java-formatter.xml](spotless/eclipse-java-formatter.xml)). | [spotless/eclipse-java-formatter.xml](spotless/eclipse-java-formatter.xml)). | ||||||
| This can be applied via Maven (but is *not* enforced at build time): | This can be applied via Maven (but is *not* enforced at build time): | ||||||
|   | |||||||
| @@ -9,7 +9,7 @@ fullnameOverride: "" | |||||||
| images: | images: | ||||||
|   owrrm: |   owrrm: | ||||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owrrm |     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owrrm | ||||||
|     tag: v2.7.0-RC2 |     tag: main | ||||||
|     pullPolicy: Always |     pullPolicy: Always | ||||||
| #    regcred: | #    regcred: | ||||||
| #      registry: tip-tip-wlan-cloud-ucentral.jfrog.io | #      registry: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||||
| @@ -29,8 +29,8 @@ services: | |||||||
|         targetPort: 16789 |         targetPort: 16789 | ||||||
|         protocol: TCP |         protocol: TCP | ||||||
|       restapiinternal: |       restapiinternal: | ||||||
|         servicePort: 17007 |         servicePort: 16790 | ||||||
|         targetPort: 17007 |         targetPort: 16790 | ||||||
|         protocol: TCP |         protocol: TCP | ||||||
|  |  | ||||||
| checks: | checks: | ||||||
|   | |||||||
							
								
								
									
										3
									
								
								lib-cloudsdk/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								lib-cloudsdk/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | # OpenWiFi CloudSDK Java Library | ||||||
|  | A Java library providing clients and models for the OpenWiFi uCentral-based | ||||||
|  | CloudSDK. | ||||||
							
								
								
									
										80
									
								
								lib-cloudsdk/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								lib-cloudsdk/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||||
|  |   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||||||
|  |   <modelVersion>4.0.0</modelVersion> | ||||||
|  |   <artifactId>openwifi-cloudsdk</artifactId> | ||||||
|  |   <packaging>jar</packaging> | ||||||
|  |   <parent> | ||||||
|  |     <groupId>com.facebook</groupId> | ||||||
|  |     <artifactId>openwifi-base</artifactId> | ||||||
|  |     <version>2.7.0</version> | ||||||
|  |   </parent> | ||||||
|  |   <properties> | ||||||
|  |     <!-- Hack for static files located in root project --> | ||||||
|  |     <myproject.root>${project.basedir}/..</myproject.root> | ||||||
|  |   </properties> | ||||||
|  |   <build> | ||||||
|  |     <finalName>openwifi-cloudsdk</finalName> | ||||||
|  |     <resources> | ||||||
|  |       <resource> | ||||||
|  |         <directory>src/main/resources</directory> | ||||||
|  |         <includes> | ||||||
|  |           <include>**/*</include> | ||||||
|  |         </includes> | ||||||
|  |       </resource> | ||||||
|  |     </resources> | ||||||
|  |     <plugins> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-compiler-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-surefire-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-javadoc-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>com.diffplug.spotless</groupId> | ||||||
|  |         <artifactId>spotless-maven-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |     </plugins> | ||||||
|  |   </build> | ||||||
|  |   <dependencies> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.slf4j</groupId> | ||||||
|  |       <artifactId>slf4j-api</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.slf4j</groupId> | ||||||
|  |       <artifactId>slf4j-log4j12</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.junit.jupiter</groupId> | ||||||
|  |       <artifactId>junit-jupiter-api</artifactId> | ||||||
|  |       <scope>test</scope> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.junit.jupiter</groupId> | ||||||
|  |       <artifactId>junit-jupiter-engine</artifactId> | ||||||
|  |       <scope>test</scope> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.json</groupId> | ||||||
|  |       <artifactId>json</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.google.code.gson</groupId> | ||||||
|  |       <artifactId>gson</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.konghq</groupId> | ||||||
|  |       <artifactId>unirest-java</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.apache.kafka</groupId> | ||||||
|  |       <artifactId>kafka-clients</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |   </dependencies> | ||||||
|  | </project> | ||||||
| @@ -0,0 +1,201 @@ | |||||||
|  | /* | ||||||
|  |  * 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.openwifi.cloudsdk; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association.Rate; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Aggregation model for State aggregation. Only contains info useful for | ||||||
|  |  * analysis. | ||||||
|  |  */ | ||||||
|  | public class AggregatedState { | ||||||
|  |  | ||||||
|  | 	/** Rate information with aggregated fields. */ | ||||||
|  | 	public static class AggregatedRate { | ||||||
|  | 		/** | ||||||
|  | 		 * This is the common bitRate for all the aggregated fields. | ||||||
|  | 		 */ | ||||||
|  | 		public long bitRate; | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * This is the common channel width for all the aggregated fields. | ||||||
|  | 		 */ | ||||||
|  | 		public int chWidth; | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * Aggregated fields mcs | ||||||
|  | 		 */ | ||||||
|  | 		public List<Integer> mcs = new ArrayList<>(); | ||||||
|  |  | ||||||
|  | 		/** Constructor with no args */ | ||||||
|  | 		private AggregatedRate() {} | ||||||
|  |  | ||||||
|  | 		/** Add a Rate to the AggregatedRate */ | ||||||
|  | 		private void add(Rate rate) { | ||||||
|  | 			if (rate == null) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			if (mcs.isEmpty()) { | ||||||
|  | 				bitRate = rate.bitrate; | ||||||
|  | 				chWidth = rate.chwidth; | ||||||
|  | 			} | ||||||
|  | 			mcs.add(rate.mcs); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * Add an AggregatedRate with the same channel_width to the | ||||||
|  | 		 * AggregatedRate | ||||||
|  | 		 */ | ||||||
|  | 		private void add(AggregatedRate rate) { | ||||||
|  | 			if (rate == null || rate.chWidth != chWidth) { | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 			if (mcs.isEmpty()) { | ||||||
|  | 				bitRate = rate.bitRate; | ||||||
|  | 				chWidth = rate.chWidth; | ||||||
|  | 			} | ||||||
|  | 			mcs.addAll(rate.mcs); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Radio information with channel, channel_width and tx_power. | ||||||
|  | 	 */ | ||||||
|  | 	public static class Radio { | ||||||
|  | 		public int channel; | ||||||
|  | 		public int channelWidth; | ||||||
|  | 		public int txPower; | ||||||
|  |  | ||||||
|  | 		private Radio() {} | ||||||
|  |  | ||||||
|  | 		public Radio(int channel, int channelWidth, int txPower) { | ||||||
|  | 			this.channel = channel; | ||||||
|  | 			this.channelWidth = channelWidth; | ||||||
|  | 			this.txPower = txPower; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		private Radio(Map<String, Integer> radioInfo) { | ||||||
|  | 			channel = radioInfo.getOrDefault("channel", -1); | ||||||
|  | 			channelWidth = radioInfo.getOrDefault("channel_width", -1); | ||||||
|  | 			txPower = radioInfo.getOrDefault("tx_power", -1); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public int hashCode() { | ||||||
|  | 			return Objects.hash(channel, channelWidth, txPower); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public boolean equals(Object obj) { | ||||||
|  | 			if (this == obj) { | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  | 			if (obj == null) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  | 			if (getClass() != obj.getClass()) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			Radio other = (Radio) obj; | ||||||
|  | 			return channel == other.channel && | ||||||
|  | 				channelWidth == other.channelWidth && txPower == other.txPower; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public String bssid; | ||||||
|  | 	public String station; | ||||||
|  | 	public long connected; | ||||||
|  | 	public long inactive; | ||||||
|  | 	public List<Integer> rssi; | ||||||
|  | 	public long rxBytes; | ||||||
|  | 	public long rxPackets; | ||||||
|  | 	public AggregatedRate rxRate; | ||||||
|  | 	public long txBytes; | ||||||
|  | 	public long txDuration; | ||||||
|  | 	public long txFailed; | ||||||
|  | 	public long txPackets; | ||||||
|  | 	public AggregatedRate txRate; | ||||||
|  | 	public long txRetries; | ||||||
|  | 	public int ackSignal; | ||||||
|  | 	public int ackSignalAvg; | ||||||
|  | 	public Radio radio; | ||||||
|  |  | ||||||
|  | 	/** Constructor with no args */ | ||||||
|  | 	public AggregatedState() { | ||||||
|  | 		this.rxRate = new AggregatedRate(); | ||||||
|  | 		this.txRate = new AggregatedRate(); | ||||||
|  | 		this.rssi = new ArrayList<>(); | ||||||
|  | 		this.radio = new Radio(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Construct from Association and radio */ | ||||||
|  | 	public AggregatedState( | ||||||
|  | 		Association association, | ||||||
|  | 		Map<String, Integer> radioInfo | ||||||
|  | 	) { | ||||||
|  | 		this.rxRate = new AggregatedRate(); | ||||||
|  | 		this.txRate = new AggregatedRate(); | ||||||
|  | 		this.rssi = new ArrayList<>(); | ||||||
|  |  | ||||||
|  | 		this.bssid = association.bssid; | ||||||
|  | 		this.station = association.station; | ||||||
|  | 		this.connected = association.connected; | ||||||
|  | 		this.inactive = association.inactive; | ||||||
|  | 		this.rssi.add(association.rssi); | ||||||
|  | 		this.rxBytes = association.rx_bytes; | ||||||
|  | 		this.rxPackets = association.rx_packets; | ||||||
|  | 		this.rxRate.add(association.rx_rate); | ||||||
|  | 		this.txBytes = association.tx_bytes; | ||||||
|  | 		this.txDuration = association.tx_duration; | ||||||
|  | 		this.txFailed = association.tx_failed; | ||||||
|  | 		this.txPackets = association.tx_packets; | ||||||
|  | 		this.txRate.add(association.tx_rate); | ||||||
|  | 		this.txRetries = association.tx_retries; | ||||||
|  | 		this.ackSignal = association.ack_signal; | ||||||
|  | 		this.ackSignalAvg = association.ack_signal_avg; | ||||||
|  | 		this.radio = new Radio(radioInfo); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Check whether the passed-in AggregatedState and this one match for aggregation. | ||||||
|  | 	 * If the two match in bssid, station and radio. Then they could be aggregated. | ||||||
|  | 	 * | ||||||
|  | 	 * @param state the reference AggregatedState with which to check with. | ||||||
|  | 	 * @return boolean return true if the two matches for aggregation. | ||||||
|  | 	 */ | ||||||
|  | 	public boolean matchesForAggregation(AggregatedState state) { | ||||||
|  | 		return bssid == state.bssid && station == state.station && | ||||||
|  | 			Objects.equals(radio, state.radio); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Add an AggregatedState to this AggregatedState. Succeed only when the two | ||||||
|  | 	 * match for aggregation. | ||||||
|  | 	 * | ||||||
|  | 	 * @param state input AggregatedState | ||||||
|  | 	 * @return boolean true if the two match in bssid, station, channel, | ||||||
|  | 	 *         channel_width and tx_power | ||||||
|  | 	 */ | ||||||
|  | 	public boolean add(AggregatedState state) { | ||||||
|  | 		if (matchesForAggregation(state)) { | ||||||
|  | 			this.rssi.addAll(state.rssi); | ||||||
|  | 			this.rxRate.add(state.rxRate); | ||||||
|  | 			this.txRate.add(state.txRate); | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package com.facebook.openwifi.cloudsdk; | ||||||
|  |  | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import com.facebook.openwifi.cloudsdk.ies.Country; | ||||||
|  | import com.facebook.openwifi.cloudsdk.ies.LocalPowerConstraint; | ||||||
|  | import com.facebook.openwifi.cloudsdk.ies.QbssLoad; | ||||||
|  | import com.facebook.openwifi.cloudsdk.ies.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); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -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; | package com.facebook.openwifi.cloudsdk; | ||||||
| 
 | 
 | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| @@ -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; | package com.facebook.openwifi.cloudsdk; | ||||||
| 
 | 
 | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| @@ -20,22 +20,21 @@ import org.json.JSONObject; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.RRMConfig.UCentralConfig.UCentralSocketParams; | import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceConfigureRequest; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceConfigureRequest; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceListWithStatus; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceListWithStatus; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus; | import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent; | import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords; | import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults; | import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult; | import com.facebook.openwifi.cloudsdk.models.gw.WifiScanRequest; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.WifiScanRequest; | import com.facebook.openwifi.cloudsdk.models.prov.EntityList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.EntityList; | import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList; | import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails; | import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList; | import com.facebook.openwifi.cloudsdk.models.prov.VenueList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.VenueList; |  | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.JsonSyntaxException; | import com.google.gson.JsonSyntaxException; | ||||||
| 
 | 
 | ||||||
| @@ -127,8 +126,14 @@ public class UCentralClient { | |||||||
| 	/** uCentral password */ | 	/** uCentral password */ | ||||||
| 	private final String password; | 	private final String password; | ||||||
| 
 | 
 | ||||||
| 	/** Socket parameters */ | 	/** Connection timeout for all requests, in ms */ | ||||||
| 	private final UCentralSocketParams socketParams; | 	private final int connectTimeoutMs; | ||||||
|  | 
 | ||||||
|  | 	/** Socket timeout for all requests, in ms */ | ||||||
|  | 	private final int socketTimeoutMs; | ||||||
|  | 
 | ||||||
|  | 	/** Socket timeout for wifi scan requests, in ms */ | ||||||
|  | 	private final int wifiScanTimeoutMs; | ||||||
| 
 | 
 | ||||||
| 	/** The learned service endpoints. */ | 	/** The learned service endpoints. */ | ||||||
| 	private final Map<String, ServiceEvent> serviceEndpoints = new HashMap<>(); | 	private final Map<String, ServiceEvent> serviceEndpoints = new HashMap<>(); | ||||||
| @@ -147,7 +152,9 @@ public class UCentralClient { | |||||||
| 	 *        (if needed) | 	 *        (if needed) | ||||||
| 	 * @param username uCentral username (for public endpoints only) | 	 * @param username uCentral username (for public endpoints only) | ||||||
| 	 * @param password uCentral password (for public endpoints only) | 	 * @param password uCentral password (for public endpoints only) | ||||||
| 	 * @param socketParams Socket parameters | 	 * @param connectTimeoutMs connection timeout for all requests, in ms | ||||||
|  | 	 * @param socketTimeoutMs socket timeout for all requests, in ms | ||||||
|  | 	 * @param wifiScanTimeoutMs socket timeout for wifi scan requests, in ms | ||||||
| 	 */ | 	 */ | ||||||
| 	public UCentralClient( | 	public UCentralClient( | ||||||
| 		String rrmEndpoint, | 		String rrmEndpoint, | ||||||
| @@ -155,13 +162,17 @@ public class UCentralClient { | |||||||
| 		String uCentralSecPublicEndpoint, | 		String uCentralSecPublicEndpoint, | ||||||
| 		String username, | 		String username, | ||||||
| 		String password, | 		String password, | ||||||
| 		UCentralSocketParams socketParams | 		int connectTimeoutMs, | ||||||
|  | 		int socketTimeoutMs, | ||||||
|  | 		int wifiScanTimeoutMs | ||||||
| 	) { | 	) { | ||||||
| 		this.rrmEndpoint = rrmEndpoint; | 		this.rrmEndpoint = rrmEndpoint; | ||||||
| 		this.usePublicEndpoints = usePublicEndpoints; | 		this.usePublicEndpoints = usePublicEndpoints; | ||||||
| 		this.username = username; | 		this.username = username; | ||||||
| 		this.password = password; | 		this.password = password; | ||||||
| 		this.socketParams = socketParams; | 		this.connectTimeoutMs = connectTimeoutMs; | ||||||
|  | 		this.socketTimeoutMs = socketTimeoutMs; | ||||||
|  | 		this.wifiScanTimeoutMs = wifiScanTimeoutMs; | ||||||
| 
 | 
 | ||||||
| 		if (usePublicEndpoints) { | 		if (usePublicEndpoints) { | ||||||
| 			setServicePublicEndpoint(OWSEC_SERVICE, uCentralSecPublicEndpoint); | 			setServicePublicEndpoint(OWSEC_SERVICE, uCentralSecPublicEndpoint); | ||||||
| @@ -305,8 +316,8 @@ public class UCentralClient { | |||||||
| 			endpoint, | 			endpoint, | ||||||
| 			service, | 			service, | ||||||
| 			parameters, | 			parameters, | ||||||
| 			socketParams.connectTimeoutMs, | 			connectTimeoutMs, | ||||||
| 			socketParams.socketTimeoutMs | 			socketTimeoutMs | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -349,8 +360,8 @@ public class UCentralClient { | |||||||
| 			endpoint, | 			endpoint, | ||||||
| 			service, | 			service, | ||||||
| 			body, | 			body, | ||||||
| 			socketParams.connectTimeoutMs, | 			connectTimeoutMs, | ||||||
| 			socketParams.socketTimeoutMs | 			socketTimeoutMs | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -454,8 +465,8 @@ public class UCentralClient { | |||||||
| 			String.format("device/%s/wifiscan", serialNumber), | 			String.format("device/%s/wifiscan", serialNumber), | ||||||
| 			OWGW_SERVICE, | 			OWGW_SERVICE, | ||||||
| 			req, | 			req, | ||||||
| 			socketParams.connectTimeoutMs, | 			connectTimeoutMs, | ||||||
| 			socketParams.wifiScanTimeoutMs | 			wifiScanTimeoutMs | ||||||
| 		); | 		); | ||||||
| 		if (!response.isSuccess()) { | 		if (!response.isSuccess()) { | ||||||
| 			logger.error("Error: {}", response.getBody()); | 			logger.error("Error: {}", response.getBody()); | ||||||
| @@ -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; | package com.facebook.openwifi.cloudsdk; | ||||||
| 
 | 
 | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Collections; | import java.util.Collections; | ||||||
| @@ -22,7 +22,7 @@ public final class UCentralConstants { | |||||||
| 	public static final String BAND_2G = "2G"; | 	public static final String BAND_2G = "2G"; | ||||||
| 	/** String of the 5 GHz band */ | 	/** String of the 5 GHz band */ | ||||||
| 	public static final String BAND_5G = "5G"; | 	public static final String BAND_5G = "5G"; | ||||||
| 	/** List of all bands */ | 	/** List of all bands ordered from lowest to highest */ | ||||||
| 	public static final List<String> BANDS = Collections | 	public static final List<String> BANDS = Collections | ||||||
| 		.unmodifiableList(Arrays.asList(BAND_2G, BAND_5G)); | 		.unmodifiableList(Arrays.asList(BAND_2G, BAND_5G)); | ||||||
| 
 | 
 | ||||||
| @@ -6,11 +6,11 @@ | |||||||
|  * 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; | package com.facebook.openwifi.cloudsdk; | ||||||
| 
 | 
 | ||||||
| import java.security.MessageDigest; |  | ||||||
| import java.security.NoSuchAlgorithmException; |  | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -21,10 +21,11 @@ import java.util.Set; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.RRMConfig; | import com.facebook.openwifi.cloudsdk.ies.Country; | ||||||
| import com.facebook.openwifirrm.Utils; | import com.facebook.openwifi.cloudsdk.ies.LocalPowerConstraint; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer; | import com.facebook.openwifi.cloudsdk.ies.QbssLoad; | ||||||
| import com.facebook.openwifirrm.ucentral.models.State; | import com.facebook.openwifi.cloudsdk.ies.TxPwrInfo; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.JsonArray; | import com.google.gson.JsonArray; | ||||||
| import com.google.gson.JsonElement; | import com.google.gson.JsonElement; | ||||||
| @@ -37,28 +38,53 @@ 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(); | ||||||
| 
 | 
 | ||||||
| 	/** Map of band to the band-specific lowest available channel*/ | 	/** Map from band to ordered (increasing) list of available channels */ | ||||||
| 	public static final Map<String, Integer> LOWER_CHANNEL_LIMIT = | 	public static final Map<String, List<Integer>> AVAILABLE_CHANNELS_BAND = | ||||||
| 		new HashMap<>(); | 		Collections | ||||||
| 	static { | 			.unmodifiableMap(buildBandToChannelsMap()); | ||||||
| 		UCentralUtils.LOWER_CHANNEL_LIMIT.put(UCentralConstants.BAND_2G, 1); |  | ||||||
| 		UCentralUtils.LOWER_CHANNEL_LIMIT.put(UCentralConstants.BAND_5G, 36); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** Map of band to the band-specific highest available channel*/ |  | ||||||
| 	public static final Map<String, Integer> UPPER_CHANNEL_LIMIT = |  | ||||||
| 		new HashMap<>(); |  | ||||||
| 	static { |  | ||||||
| 		UCentralUtils.UPPER_CHANNEL_LIMIT.put(UCentralConstants.BAND_2G, 11); |  | ||||||
| 		UCentralUtils.UPPER_CHANNEL_LIMIT.put(UCentralConstants.BAND_5G, 165); |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// This class should not be instantiated. | 	// This class should not be instantiated. | ||||||
| 	private UCentralUtils() {} | 	private UCentralUtils() {} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Builds map from band to ordered (increasing) list of available channels. | ||||||
|  | 	 */ | ||||||
|  | 	private static Map<String, List<Integer>> buildBandToChannelsMap() { | ||||||
|  | 		Map<String, List<Integer>> bandToChannelsMap = new HashMap<>(); | ||||||
|  | 		bandToChannelsMap.put( | ||||||
|  | 			UCentralConstants.BAND_5G, | ||||||
|  | 			Collections.unmodifiableList( | ||||||
|  | 				Arrays.asList(36, 40, 44, 48, 149, 153, 157, 161, 165) | ||||||
|  | 			) | ||||||
|  | 		); | ||||||
|  | 		// NOTE: later, we may want to support channels 12, 13, and/or 14, if | ||||||
|  | 		// the AP supports it and OWF vendors will use them | ||||||
|  | 		bandToChannelsMap.put( | ||||||
|  | 			UCentralConstants.BAND_2G, | ||||||
|  | 			Collections.unmodifiableList( | ||||||
|  | 				Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) | ||||||
|  | 			) | ||||||
|  | 		); | ||||||
|  | 		return bandToChannelsMap; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** Return the lowest available channel for the given band */ | ||||||
|  | 	public static int getLowerChannelLimit(String band) { | ||||||
|  | 		return AVAILABLE_CHANNELS_BAND.get(band).get(0); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** Return the lowest available channel for the given band */ | ||||||
|  | 	public static int getUpperChannelLimit(String band) { | ||||||
|  | 		List<Integer> channels = AVAILABLE_CHANNELS_BAND.get(band); | ||||||
|  | 		return channels.get(channels.size() - 1); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * Parse a JSON wifi scan result into a list of WifiScanEntry objects. | 	 * Parse a JSON wifi scan result into a list of WifiScanEntry objects. | ||||||
| 	 * | 	 * | ||||||
| @@ -79,15 +105,76 @@ public class UCentralUtils { | |||||||
| 			for (JsonElement e : scanInfo) { | 			for (JsonElement e : scanInfo) { | ||||||
| 				WifiScanEntry entry = gson.fromJson(e, WifiScanEntry.class); | 				WifiScanEntry entry = gson.fromJson(e, WifiScanEntry.class); | ||||||
| 				entry.unixTimeMs = timestampMs; | 				entry.unixTimeMs = timestampMs; | ||||||
|  | 				extractIEs(e, entry); | ||||||
| 				entries.add(entry); | 				entries.add(entry); | ||||||
| 
 |  | ||||||
| 			} | 			} | ||||||
| 		} catch (Exception e) { | 		} catch (Exception e) { | ||||||
|  | 			logger.debug("Exception when parsing wifiscan entries", e); | ||||||
| 			return null; | 			return null; | ||||||
| 		} | 		} | ||||||
| 		return entries; | 		return entries; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Extract desired information elements (IEs) from the wifiscan entry. | ||||||
|  | 	 * Modifies {@code entry} argument. Skips invalid IEs (IEs with missing | ||||||
|  | 	 * fields). | ||||||
|  | 	 */ | ||||||
|  | 	private static void extractIEs( | ||||||
|  | 		JsonElement entryJsonElement, | ||||||
|  | 		WifiScanEntry entry | ||||||
|  | 	) { | ||||||
|  | 		JsonElement iesJsonElement = | ||||||
|  | 			entryJsonElement.getAsJsonObject().get("ies"); | ||||||
|  | 		if (iesJsonElement == null) { | ||||||
|  | 			logger.debug("Wifiscan entry does not contain 'ies' field."); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		JsonArray iesJsonArray = iesJsonElement.getAsJsonArray(); | ||||||
|  | 		InformationElements ieContainer = new InformationElements(); | ||||||
|  | 		for (JsonElement ieJsonElement : iesJsonArray) { | ||||||
|  | 			JsonElement typeElement = | ||||||
|  | 				ieJsonElement.getAsJsonObject().get("type"); | ||||||
|  | 			if (typeElement == null) { // shouldn't happen | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			if (!ieJsonElement.isJsonObject()) { | ||||||
|  | 				// the IEs we are interested in are Json objects | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			JsonObject ie = ieJsonElement.getAsJsonObject(); | ||||||
|  | 			JsonElement contentsJsonElement = ie.get(IE_CONTENT_FIELD_KEY); | ||||||
|  | 			if ( | ||||||
|  | 				contentsJsonElement == null || | ||||||
|  | 					!contentsJsonElement.isJsonObject() | ||||||
|  | 			) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			JsonObject contents = contentsJsonElement.getAsJsonObject(); | ||||||
|  | 			try { | ||||||
|  | 				switch (typeElement.getAsInt()) { | ||||||
|  | 				case Country.TYPE: | ||||||
|  | 					ieContainer.country = Country.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				case QbssLoad.TYPE: | ||||||
|  | 					ieContainer.qbssLoad = QbssLoad.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				case LocalPowerConstraint.TYPE: | ||||||
|  | 					ieContainer.localPowerConstraint = | ||||||
|  | 						LocalPowerConstraint.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				case TxPwrInfo.TYPE: | ||||||
|  | 					ieContainer.txPwrInfo = TxPwrInfo.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 			} catch (Exception e) { | ||||||
|  | 				logger.error(String.format("Skipping invalid IE %s", ie), e); | ||||||
|  | 				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. | ||||||
| 	 * | 	 * | ||||||
| @@ -130,9 +217,24 @@ public class UCentralUtils { | |||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			// Compare vs. existing value | 			// Compare vs. existing value. | ||||||
| 			int currentValue = radioConfig.get(fieldName).getAsInt(); | 			// not all values are int so override those values | ||||||
| 			if (currentValue == newValue) { | 			Integer currentValue = null; | ||||||
|  | 			JsonElement fieldValue = radioConfig.get(fieldName); | ||||||
|  | 			if ( | ||||||
|  | 				fieldValue.isJsonPrimitive() && | ||||||
|  | 					fieldValue.getAsJsonPrimitive().isNumber() | ||||||
|  | 			) { | ||||||
|  | 				currentValue = fieldValue.getAsInt(); | ||||||
|  | 			} else { | ||||||
|  | 				logger.debug( | ||||||
|  | 					"Unable to get field '{}' as int, value was {}", | ||||||
|  | 					fieldName, | ||||||
|  | 					fieldValue.toString() | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (currentValue != null && currentValue == newValue) { | ||||||
| 				logger.info( | 				logger.info( | ||||||
| 					"Device {}: {} {} is already {}", | 					"Device {}: {} {} is already {}", | ||||||
| 					serialNumber, | 					serialNumber, | ||||||
| @@ -150,7 +252,7 @@ public class UCentralUtils { | |||||||
| 					operationalBand, | 					operationalBand, | ||||||
| 					fieldName, | 					fieldName, | ||||||
| 					newValue, | 					newValue, | ||||||
| 					currentValue | 					currentValue != null ? currentValue : fieldValue.toString() | ||||||
| 				); | 				); | ||||||
| 				wasModified = true; | 				wasModified = true; | ||||||
| 			} | 			} | ||||||
| @@ -316,47 +418,6 @@ public class UCentralUtils { | |||||||
| 		return bssidMap; | 		return bssidMap; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Generate the RRM service key. */ |  | ||||||
| 	public static String generateServiceKey( |  | ||||||
| 		RRMConfig.ServiceConfig serviceConfig |  | ||||||
| 	) { |  | ||||||
| 		try { |  | ||||||
| 			MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); |  | ||||||
| 			sha256.update(serviceConfig.publicEndpoint.getBytes()); |  | ||||||
| 			sha256.update(serviceConfig.privateEndpoint.getBytes()); |  | ||||||
| 			return Utils.bytesToHex(sha256.digest()); |  | ||||||
| 		} catch (NoSuchAlgorithmException e) { |  | ||||||
| 			logger.error("Unable to generate service key", e); |  | ||||||
| 			return ""; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** |  | ||||||
| 	 * Converts channel number to that channel's center frequency in MHz. |  | ||||||
| 	 * |  | ||||||
| 	 * @param channel channel number. See |  | ||||||
| 	 *                {@link ChannelOptimizer#AVAILABLE_CHANNELS_BAND} for channels |  | ||||||
| 	 *                in each band. |  | ||||||
| 	 * @return the center frequency of the given channel in MHz |  | ||||||
| 	 */ |  | ||||||
| 	public static int channelToFrequencyMHz(int channel) { |  | ||||||
| 		if ( |  | ||||||
| 			ChannelOptimizer.AVAILABLE_CHANNELS_BAND |  | ||||||
| 				.get(UCentralConstants.BAND_2G) |  | ||||||
| 				.contains(channel) |  | ||||||
| 		) { |  | ||||||
| 			return 2407 + 5 * channel; |  | ||||||
| 		} else if ( |  | ||||||
| 			ChannelOptimizer.AVAILABLE_CHANNELS_BAND |  | ||||||
| 				.get(UCentralConstants.BAND_5G) |  | ||||||
| 				.contains(channel) |  | ||||||
| 		) { |  | ||||||
| 			return 5000 + channel; |  | ||||||
| 		} else { |  | ||||||
| 			throw new IllegalArgumentException("Must provide a valid channel."); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** | 	/** | ||||||
| 	 * Determines if the given channel is in the given band. | 	 * Determines if the given channel is in the given band. | ||||||
| 	 * | 	 * | ||||||
| @@ -365,25 +426,17 @@ public class UCentralUtils { | |||||||
| 	 * @return true if the given channel is in the given band; false otherwise | 	 * @return true if the given channel is in the given band; false otherwise | ||||||
| 	 */ | 	 */ | ||||||
| 	public static boolean isChannelInBand(int channel, String band) { | 	public static boolean isChannelInBand(int channel, String band) { | ||||||
| 		return LOWER_CHANNEL_LIMIT.get(band) <= channel && | 		return AVAILABLE_CHANNELS_BAND.get(band).contains(channel); | ||||||
| 			channel <= UPPER_CHANNEL_LIMIT.get(band); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** Return which band contains the given frequency (MHz). */ | ||||||
| 	 * Given the channel, gets the band by checking lower bound and upper bound | 	public static String freqToBand(int freqMHz) { | ||||||
| 	 * of each band | 		if (2412 <= freqMHz && freqMHz <= 2484) { | ||||||
| 	 * | 			return "2G"; | ||||||
| 	 * @param channel channel number | 		} else { | ||||||
| 	 * @return band if the channel can be mapped to a valid band; null otherwise | 			return "5G"; | ||||||
| 	 */ |  | ||||||
| 	public static String getBandFromChannel(int channel) { |  | ||||||
| 		for (String band : UCentralConstants.BANDS) { |  | ||||||
| 			if (isChannelInBand(channel, band)) { |  | ||||||
| 				return band; |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 		return null; |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * Tries to parse channel width, if it encounters an error it will return null. | 	 * Tries to parse channel width, if it encounters an error it will return null. | ||||||
| @@ -6,11 +6,11 @@ | |||||||
|  * 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; | package com.facebook.openwifi.cloudsdk; | ||||||
| 
 | 
 | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.models.WifiScanEntryResult; | import com.facebook.openwifi.cloudsdk.models.ap.WifiScanEntryResult; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Extends {@link WifiScanEntryResult} to track the response time of the entry. |  * Extends {@link WifiScanEntryResult} to track the response time of the entry. | ||||||
| @@ -22,6 +22,8 @@ public class WifiScanEntry extends WifiScanEntryResult { | |||||||
| 	 * time reference. | 	 * time reference. | ||||||
| 	 */ | 	 */ | ||||||
| 	public long unixTimeMs; | 	public long unixTimeMs; | ||||||
|  | 	/** Stores Information Elements (IEs) from the wifiscan entry. */ | ||||||
|  | 	public InformationElements ieContainer; | ||||||
| 
 | 
 | ||||||
| 	/** Default Constructor. */ | 	/** Default Constructor. */ | ||||||
| 	public WifiScanEntry() {} | 	public WifiScanEntry() {} | ||||||
| @@ -30,13 +32,14 @@ public class WifiScanEntry extends WifiScanEntryResult { | |||||||
| 	public WifiScanEntry(WifiScanEntry o) { | 	public WifiScanEntry(WifiScanEntry o) { | ||||||
| 		super(o); | 		super(o); | ||||||
| 		this.unixTimeMs = o.unixTimeMs; | 		this.unixTimeMs = o.unixTimeMs; | ||||||
|  | 		this.ieContainer = o.ieContainer; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Override | 	@Override | ||||||
| 	public int hashCode() { | 	public int hashCode() { | ||||||
| 		final int prime = 31; | 		final int prime = 31; | ||||||
| 		int result = super.hashCode(); | 		int result = super.hashCode(); | ||||||
| 		result = prime * result + Objects.hash(unixTimeMs); | 		result = prime * result + Objects.hash(ieContainer, unixTimeMs); | ||||||
| 		return result; | 		return result; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -52,7 +55,8 @@ public class WifiScanEntry extends WifiScanEntryResult { | |||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
| 		WifiScanEntry other = (WifiScanEntry) obj; | 		WifiScanEntry other = (WifiScanEntry) obj; | ||||||
| 		return unixTimeMs == other.unixTimeMs; | 		return Objects.equals(ieContainer, other.ieContainer) && | ||||||
|  | 			unixTimeMs == other.unixTimeMs; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Override | 	@Override | ||||||
| @@ -0,0 +1,156 @@ | |||||||
|  | /* | ||||||
|  |  * 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.openwifi.cloudsdk.ies; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | 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 { | ||||||
|  | 	/** 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()) { | ||||||
|  | 				JsonObject innerElem = jsonElement.getAsJsonObject(); | ||||||
|  | 				CountryInfo countryInfo = | ||||||
|  | 					CountryInfo.parse(innerElem.get("Country Info").getAsJsonObject()); | ||||||
|  | 				constraints.add(countryInfo); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return new Country(constraints); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(constraints); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		Country other = (Country) obj; | ||||||
|  | 		return Objects.equals(constraints, other.constraints); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public String toString() { | ||||||
|  | 		return "Country [constraints=" + constraints + "]"; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -6,7 +6,7 @@ | |||||||
|  * LICENSE file in the root directory of this source tree. |  * LICENSE file in the root directory of this source tree. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package com.facebook.openwifirrm.ucentral.operationelement; | package com.facebook.openwifi.cloudsdk.ies; | ||||||
| 
 | 
 | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| @@ -17,7 +17,7 @@ import org.apache.commons.codec.binary.Base64; | |||||||
|  * High Throughput (HT) Operation Element, which is potentially present in |  * High Throughput (HT) Operation Element, which is potentially present in | ||||||
|  * wifiscan entries. Introduced in 802.11n (2009). |  * wifiscan entries. Introduced in 802.11n (2009). | ||||||
|  */ |  */ | ||||||
| public class HTOperationElement { | public class HTOperation { | ||||||
| 
 | 
 | ||||||
| 	/** Channel number of the primary channel. */ | 	/** Channel number of the primary channel. */ | ||||||
| 	public final byte primaryChannel; | 	public final byte primaryChannel; | ||||||
| @@ -78,7 +78,7 @@ public class HTOperationElement { | |||||||
| 	 * For details about the parameters, see the javadocs for the corresponding | 	 * For details about the parameters, see the javadocs for the corresponding | ||||||
| 	 * member variables. | 	 * member variables. | ||||||
| 	 */ | 	 */ | ||||||
| 	public HTOperationElement( | 	public HTOperation( | ||||||
| 		byte primaryChannel, | 		byte primaryChannel, | ||||||
| 		byte secondaryChannelOffset, | 		byte secondaryChannelOffset, | ||||||
| 		boolean staChannelWidth, | 		boolean staChannelWidth, | ||||||
| @@ -114,7 +114,7 @@ public class HTOperationElement { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Constructor with the most used parameters. */ | 	/** Constructor with the most used parameters. */ | ||||||
| 	public HTOperationElement( | 	public HTOperation( | ||||||
| 		byte primaryChannel, | 		byte primaryChannel, | ||||||
| 		byte secondaryChannelOffset, | 		byte secondaryChannelOffset, | ||||||
| 		boolean staChannelWidth, | 		boolean staChannelWidth, | ||||||
| @@ -141,7 +141,7 @@ public class HTOperationElement { | |||||||
| 	 * @param htOper a base64 encoded properly formatted HT operation element (see | 	 * @param htOper a base64 encoded properly formatted HT operation element (see | ||||||
| 	 *               802.11) | 	 *               802.11) | ||||||
| 	 */ | 	 */ | ||||||
| 	public HTOperationElement(String htOper) { | 	public HTOperation(String htOper) { | ||||||
| 		byte[] bytes = Base64.decodeBase64(htOper); | 		byte[] bytes = Base64.decodeBase64(htOper); | ||||||
| 		/* | 		/* | ||||||
| 		 * Note that the code here may seem to read "reversed" compared to 802.11. This | 		 * Note that the code here may seem to read "reversed" compared to 802.11. This | ||||||
| @@ -182,7 +182,7 @@ public class HTOperationElement { | |||||||
| 	 * @return true if the the operation elements "match" for the purpose of | 	 * @return true if the the operation elements "match" for the purpose of | ||||||
| 	 *         aggregating statistics; false otherwise. | 	 *         aggregating statistics; false otherwise. | ||||||
| 	 */ | 	 */ | ||||||
| 	public boolean matchesForAggregation(HTOperationElement other) { | 	public boolean matchesForAggregation(HTOperation other) { | ||||||
| 		return other != null && primaryChannel == other.primaryChannel && | 		return other != null && primaryChannel == other.primaryChannel && | ||||||
| 			secondaryChannelOffset == other.secondaryChannelOffset && | 			secondaryChannelOffset == other.secondaryChannelOffset && | ||||||
| 			staChannelWidth == other.staChannelWidth && | 			staChannelWidth == other.staChannelWidth && | ||||||
| @@ -211,8 +211,8 @@ public class HTOperationElement { | |||||||
| 		if (htOper1 == null || htOper2 == null) { | 		if (htOper1 == null || htOper2 == null) { | ||||||
| 			return false; // false if exactly one is null | 			return false; // false if exactly one is null | ||||||
| 		} | 		} | ||||||
| 		HTOperationElement htOperObj1 = new HTOperationElement(htOper1); | 		HTOperation htOperObj1 = new HTOperation(htOper1); | ||||||
| 		HTOperationElement htOperObj2 = new HTOperationElement(htOper2); | 		HTOperation htOperObj2 = new HTOperation(htOper2); | ||||||
| 		return htOperObj1.matchesForAggregation(htOperObj2); | 		return htOperObj1.matchesForAggregation(htOperObj2); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -248,7 +248,7 @@ public class HTOperationElement { | |||||||
| 		if (getClass() != obj.getClass()) { | 		if (getClass() != obj.getClass()) { | ||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
| 		HTOperationElement other = (HTOperationElement) obj; | 		HTOperation other = (HTOperation) obj; | ||||||
| 		return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) && | 		return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) && | ||||||
| 			channelCenterFrequencySegment2 == | 			channelCenterFrequencySegment2 == | ||||||
| 				other.channelCenterFrequencySegment2 && | 				other.channelCenterFrequencySegment2 && | ||||||
| @@ -0,0 +1,72 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package com.facebook.openwifi.cloudsdk.ies; | ||||||
|  |  | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It is called | ||||||
|  |  * "Local Power Constraint" in these entries, and just "Power Constraint" in | ||||||
|  |  * the 802.11 specification. Refer to the specification for more details. | ||||||
|  |  * Language in javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class LocalPowerConstraint { | ||||||
|  |  | ||||||
|  | 	/** Defined in 802.11 */ | ||||||
|  | 	public static final int TYPE = 32; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Units are dB. | ||||||
|  | 	 * <p> | ||||||
|  | 	 * The local maximum transmit power for a channel is defined as the maximum | ||||||
|  | 	 * transmit power level specified for the channel in the Country IE minus | ||||||
|  | 	 * this variable for the given channel. | ||||||
|  | 	 */ | ||||||
|  | 	public final int localPowerConstraint; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public LocalPowerConstraint(int localPowerConstraint) { | ||||||
|  | 		this.localPowerConstraint = localPowerConstraint; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse LocalPowerConstraint IE from appropriate Json object. */ | ||||||
|  | 	public static LocalPowerConstraint parse(JsonObject contents) { | ||||||
|  | 		final int localPowerConstraint = | ||||||
|  | 			contents.get("Local Power Constraint").getAsInt(); | ||||||
|  | 		return new LocalPowerConstraint(localPowerConstraint); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(localPowerConstraint); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		LocalPowerConstraint other = (LocalPowerConstraint) obj; | ||||||
|  | 		return localPowerConstraint == other.localPowerConstraint; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public String toString() { | ||||||
|  | 		return "LocalPowerConstraint [localPowerConstraint=" + | ||||||
|  | 			localPowerConstraint + "]"; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,117 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package com.facebook.openwifi.cloudsdk.ies; | ||||||
|  |  | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import com.google.gson.JsonElement; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It is called | ||||||
|  |  * "QBSS Load" in these entries, and just "BSS Load" in the 802.11 | ||||||
|  |  * specification. Refer to the specification for more details. Language in | ||||||
|  |  * javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class QbssLoad { | ||||||
|  |  | ||||||
|  | 	/** Defined in 802.11 */ | ||||||
|  | 	public static final int TYPE = 11; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The total number of STAs currently associated with the BSS. | ||||||
|  | 	 */ | ||||||
|  | 	public final int stationCount; | ||||||
|  | 	/** | ||||||
|  | 	 * The Channel Utilization field is defined as the percentage of time, | ||||||
|  | 	 * linearly scaled with 255 representing 100%, that the AP sensed the | ||||||
|  | 	 * medium was busy, as indicated by either the physical or virtual carrier | ||||||
|  | 	 * sense (CS) mechanism. When more than one channel is in use for the BSS, | ||||||
|  | 	 * the Channel Utilization field value is calculated only for the primary | ||||||
|  | 	 * channel. This percentage is computed using the following formula: | ||||||
|  | 	 * <p> | ||||||
|  | 	 * floor(255 * channelBusyTime / | ||||||
|  | 	 * 		(dot11ChannelUtilizationBeaconIntervals * dot11BeaconPeriod * 1024) | ||||||
|  | 	 * ) | ||||||
|  | 	 */ | ||||||
|  | 	public final int channelUtilization; | ||||||
|  | 	/** | ||||||
|  | 	 * The Available Admission Capacity field contains an unsigned integer that | ||||||
|  | 	 * specifies the remaining amount of medium time available via explicit | ||||||
|  | 	 * admission control, in units of 32 miscrosecond/second. The field is | ||||||
|  | 	 * helpful for roaming STAs to select an AP that is likely to accept future | ||||||
|  | 	 * admission control requests, but it does not represent an assurance that | ||||||
|  | 	 * the HC admits these requests. | ||||||
|  | 	 */ | ||||||
|  | 	public final int availableAdmissionCapacity; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public QbssLoad( | ||||||
|  | 		int stationCount, | ||||||
|  | 		int channelUtilization, | ||||||
|  | 		int availableAdmissionCapacity | ||||||
|  | 	) { | ||||||
|  | 		this.stationCount = stationCount; | ||||||
|  | 		this.channelUtilization = channelUtilization; | ||||||
|  | 		this.availableAdmissionCapacity = availableAdmissionCapacity; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse QbssLoad IE from appropriate Json object; return null if invalid. */ | ||||||
|  | 	public static QbssLoad parse(JsonObject contents) { | ||||||
|  | 		// unclear why there is this additional nested layer | ||||||
|  | 		JsonElement ccaContentJsonElement = contents.get("802.11e CCA Version"); | ||||||
|  | 		if (ccaContentJsonElement == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		contents = ccaContentJsonElement.getAsJsonObject(); | ||||||
|  | 		final int stationCount = contents.get("Station Count").getAsInt(); | ||||||
|  | 		final int channelUtilization = | ||||||
|  | 			contents.get("Channel Utilization").getAsInt(); | ||||||
|  | 		final int availableAdmissionCapacity = | ||||||
|  | 			contents.get("Available Admission Capabilities").getAsInt(); | ||||||
|  | 		return new QbssLoad( | ||||||
|  | 			stationCount, | ||||||
|  | 			channelUtilization, | ||||||
|  | 			availableAdmissionCapacity | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash( | ||||||
|  | 			availableAdmissionCapacity, | ||||||
|  | 			channelUtilization, | ||||||
|  | 			stationCount | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		QbssLoad other = (QbssLoad) obj; | ||||||
|  | 		return availableAdmissionCapacity == other.availableAdmissionCapacity && | ||||||
|  | 			channelUtilization == other.channelUtilization && | ||||||
|  | 			stationCount == other.stationCount; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public String toString() { | ||||||
|  | 		return "QbssLoad [stationCount=" + stationCount + | ||||||
|  | 			", channelUtilization=" + channelUtilization + | ||||||
|  | 			", availableAdmissionCapacity=" + availableAdmissionCapacity + "]"; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,120 @@ | |||||||
|  | /* | ||||||
|  |  * 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.openwifi.cloudsdk.ies; | ||||||
|  |  | ||||||
|  | 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 int 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, | ||||||
|  | 		Integer localMaxTxPwrConstraint40MHz, | ||||||
|  | 		Integer localMaxTxPwrConstraint80MHz, | ||||||
|  | 		Integer 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) { | ||||||
|  | 		JsonObject innerObj = contents.get("Tx Pwr Info").getAsJsonObject(); | ||||||
|  | 		// required field | ||||||
|  | 		int localMaxTxPwrConstraint20MHz = | ||||||
|  | 			innerObj.get("Local Max Tx Pwr Constraint 20MHz").getAsInt(); | ||||||
|  | 		// optional field | ||||||
|  | 		Integer localMaxTxPwrConstraint40MHz = | ||||||
|  | 			parseOptionalField(innerObj, "Local Max Tx Pwr Constraint 40MHz"); | ||||||
|  | 		Integer localMaxTxPwrConstraint80MHz = | ||||||
|  | 			parseOptionalField(innerObj, "Local Max Tx Pwr Constraint 80MHz"); | ||||||
|  | 		Integer localMaxTxPwrConstraint160MHz = | ||||||
|  | 			parseOptionalField(innerObj, "Local Max Tx Pwr Constraint 160MHz"); | ||||||
|  | 		return new TxPwrInfo( | ||||||
|  | 			localMaxTxPwrConstraint20MHz, | ||||||
|  | 			localMaxTxPwrConstraint40MHz, | ||||||
|  | 			localMaxTxPwrConstraint80MHz, | ||||||
|  | 			localMaxTxPwrConstraint160MHz | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	private static Integer parseOptionalField( | ||||||
|  | 		JsonObject contents, | ||||||
|  | 		String fieldName | ||||||
|  | 	) { | ||||||
|  | 		JsonElement element = contents.get(fieldName); | ||||||
|  | 		if (element == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		return element.getAsInt(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash( | ||||||
|  | 			localMaxTxPwrConstraint160MHz, | ||||||
|  | 			localMaxTxPwrConstraint20MHz, | ||||||
|  | 			localMaxTxPwrConstraint40MHz, | ||||||
|  | 			localMaxTxPwrConstraint80MHz | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		TxPwrInfo other = (TxPwrInfo) obj; | ||||||
|  | 		return localMaxTxPwrConstraint160MHz == | ||||||
|  | 			other.localMaxTxPwrConstraint160MHz && | ||||||
|  | 			localMaxTxPwrConstraint20MHz == | ||||||
|  | 				other.localMaxTxPwrConstraint20MHz && | ||||||
|  | 			localMaxTxPwrConstraint40MHz == | ||||||
|  | 				other.localMaxTxPwrConstraint40MHz && | ||||||
|  | 			localMaxTxPwrConstraint80MHz == other.localMaxTxPwrConstraint80MHz; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public String toString() { | ||||||
|  | 		return "TxPwrInfo [localMaxTxPwrConstraint20MHz=" + | ||||||
|  | 			localMaxTxPwrConstraint20MHz + ", localMaxTxPwrConstraint40MHz=" + | ||||||
|  | 			localMaxTxPwrConstraint40MHz + ", localMaxTxPwrConstraint80MHz=" + | ||||||
|  | 			localMaxTxPwrConstraint80MHz + ", localMaxTxPwrConstraint160MHz=" + | ||||||
|  | 			localMaxTxPwrConstraint160MHz + "]"; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -6,7 +6,7 @@ | |||||||
|  * LICENSE file in the root directory of this source tree. |  * LICENSE file in the root directory of this source tree. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package com.facebook.openwifirrm.ucentral.operationelement; | package com.facebook.openwifi.cloudsdk.ies; | ||||||
| 
 | 
 | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| @@ -17,7 +17,7 @@ import org.apache.commons.codec.binary.Base64; | |||||||
|  * Very High Throughput (VHT) Operation Element, which is potentially present in |  * Very High Throughput (VHT) Operation Element, which is potentially present in | ||||||
|  * wifiscan entries. Introduced in 802.11ac (2013). |  * wifiscan entries. Introduced in 802.11ac (2013). | ||||||
|  */ |  */ | ||||||
| public class VHTOperationElement { | public class VHTOperation { | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * This field is 0 if the channel width is 20 MHz or 40 MHz, and 1 otherwise. | 	 * This field is 0 if the channel width is 20 MHz or 40 MHz, and 1 otherwise. | ||||||
| @@ -65,7 +65,7 @@ public class VHTOperationElement { | |||||||
| 	 * @param vhtOper a base64 encoded properly formatted VHT operation element (see | 	 * @param vhtOper a base64 encoded properly formatted VHT operation element (see | ||||||
| 	 *                802.11 standard) | 	 *                802.11 standard) | ||||||
| 	 */ | 	 */ | ||||||
| 	public VHTOperationElement(String vhtOper) { | 	public VHTOperation(String vhtOper) { | ||||||
| 		byte[] bytes = Base64.decodeBase64(vhtOper); | 		byte[] bytes = Base64.decodeBase64(vhtOper); | ||||||
| 		this.channelWidth = bytes[0]; | 		this.channelWidth = bytes[0]; | ||||||
| 		this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value | 		this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value | ||||||
| @@ -89,7 +89,7 @@ public class VHTOperationElement { | |||||||
| 	 * For details about the parameters, see the javadocs for the corresponding | 	 * For details about the parameters, see the javadocs for the corresponding | ||||||
| 	 * member variables. | 	 * member variables. | ||||||
| 	 */ | 	 */ | ||||||
| 	public VHTOperationElement( | 	public VHTOperation( | ||||||
| 		byte channelWidth, | 		byte channelWidth, | ||||||
| 		short channel1, | 		short channel1, | ||||||
| 		short channel2, | 		short channel2, | ||||||
| @@ -114,7 +114,7 @@ public class VHTOperationElement { | |||||||
| 	 * @return true if the the operation elements "match" for the purpose of | 	 * @return true if the the operation elements "match" for the purpose of | ||||||
| 	 *         aggregating statistics; false otherwise. | 	 *         aggregating statistics; false otherwise. | ||||||
| 	 */ | 	 */ | ||||||
| 	public boolean matchesForAggregation(VHTOperationElement other) { | 	public boolean matchesForAggregation(VHTOperation other) { | ||||||
| 		// check everything except vhtMcsForNss | 		// check everything except vhtMcsForNss | ||||||
| 		return other != null && channel1 == other.channel1 && | 		return other != null && channel1 == other.channel1 && | ||||||
| 			channel2 == other.channel2 && channelWidth == other.channelWidth; | 			channel2 == other.channel2 && channelWidth == other.channelWidth; | ||||||
| @@ -142,8 +142,8 @@ public class VHTOperationElement { | |||||||
| 		if (vhtOper1 == null || vhtOper2 == null) { | 		if (vhtOper1 == null || vhtOper2 == null) { | ||||||
| 			return false; // false if exactly one is null | 			return false; // false if exactly one is null | ||||||
| 		} | 		} | ||||||
| 		VHTOperationElement vhtOperObj1 = new VHTOperationElement(vhtOper1); | 		VHTOperation vhtOperObj1 = new VHTOperation(vhtOper1); | ||||||
| 		VHTOperationElement vhtOperObj2 = new VHTOperationElement(vhtOper2); | 		VHTOperation vhtOperObj2 = new VHTOperation(vhtOper2); | ||||||
| 		return vhtOperObj1.matchesForAggregation(vhtOperObj2); | 		return vhtOperObj1.matchesForAggregation(vhtOperObj2); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -168,7 +168,7 @@ public class VHTOperationElement { | |||||||
| 		if (getClass() != obj.getClass()) { | 		if (getClass() != obj.getClass()) { | ||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
| 		VHTOperationElement other = (VHTOperationElement) obj; | 		VHTOperation other = (VHTOperation) obj; | ||||||
| 		return channel1 == other.channel1 && channel2 == other.channel2 && | 		return channel1 == other.channel1 && channel2 == other.channel2 && | ||||||
| 			channelWidth == other.channelWidth && | 			channelWidth == other.channelWidth && | ||||||
| 			Arrays.equals(vhtMcsForNss, other.vhtMcsForNss); | 			Arrays.equals(vhtMcsForNss, other.vhtMcsForNss); | ||||||
| @@ -6,13 +6,13 @@ | |||||||
|  * 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; | package com.facebook.openwifi.cloudsdk.kafka; | ||||||
| 
 | 
 | ||||||
| import java.util.concurrent.atomic.AtomicBoolean; | import java.util.concurrent.atomic.AtomicBoolean; | ||||||
| 
 | 
 | ||||||
| import org.apache.kafka.common.errors.WakeupException; | import org.apache.kafka.common.errors.WakeupException; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent; | import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Kafka runner. |  * Kafka runner. | ||||||
| @@ -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; | package com.facebook.openwifi.cloudsdk.kafka; | ||||||
| 
 | 
 | ||||||
| import java.time.Duration; | import java.time.Duration; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| @@ -29,7 +29,8 @@ import org.apache.kafka.common.serialization.StringDeserializer; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| 
 | 
 | ||||||
| @@ -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; | package com.facebook.openwifi.cloudsdk.kafka; | ||||||
| 
 | 
 | ||||||
| import java.util.Properties; | import java.util.Properties; | ||||||
| 
 | 
 | ||||||
| @@ -18,7 +18,7 @@ import org.apache.kafka.common.serialization.StringSerializer; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent; | import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @@ -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.models; | package com.facebook.openwifi.cloudsdk.models.ap; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| import com.google.gson.annotations.SerializedName; | import com.google.gson.annotations.SerializedName; | ||||||
| @@ -6,14 +6,14 @@ | |||||||
|  * 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.models; | package com.facebook.openwifi.cloudsdk.models.ap; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| import com.google.gson.annotations.SerializedName; | import com.google.gson.annotations.SerializedName; | ||||||
| 
 | 
 | ||||||
| public class State { | public class State { | ||||||
| 	public class Interface { | 	public static class Interface { | ||||||
| 		public class Client { | 		public static class Client { | ||||||
| 			public String mac; | 			public String mac; | ||||||
| 			public String[] ipv4_addresses; | 			public String[] ipv4_addresses; | ||||||
| 			public String[] ipv6_addresses; | 			public String[] ipv6_addresses; | ||||||
| @@ -21,9 +21,9 @@ public class State { | |||||||
| 			// TODO last_seen | 			// TODO last_seen | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public class SSID { | 		public static class SSID { | ||||||
| 			public class Association { | 			public static class Association { | ||||||
| 				public class Rate { | 				public static class Rate { | ||||||
| 					public long bitrate; | 					public long bitrate; | ||||||
| 					public int chwidth; | 					public int chwidth; | ||||||
| 					public boolean sgi; | 					public boolean sgi; | ||||||
| @@ -66,7 +66,7 @@ public class State { | |||||||
| 			public JsonObject radio; | 			public JsonObject radio; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		public class Counters { | 		public static class Counters { | ||||||
| 			public long collisions; | 			public long collisions; | ||||||
| 			public long multicast; | 			public long multicast; | ||||||
| 			public long rx_bytes; | 			public long rx_bytes; | ||||||
| @@ -96,8 +96,8 @@ public class State { | |||||||
| 
 | 
 | ||||||
| 	public Interface[] interfaces; | 	public Interface[] interfaces; | ||||||
| 
 | 
 | ||||||
| 	public class Unit { | 	public static class Unit { | ||||||
| 		public class Memory { | 		public static class Memory { | ||||||
| 			public long buffered; | 			public long buffered; | ||||||
| 			public long cached; | 			public long cached; | ||||||
| 			public long free; | 			public long free; | ||||||
| @@ -6,13 +6,15 @@ | |||||||
|  * 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.models; | package com.facebook.openwifi.cloudsdk.models.ap; | ||||||
| 
 | 
 | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonArray; | /** | ||||||
| 
 |  * Represents a single entry in wifi scan results. | ||||||
| /** Represents a single entry in wifi scan results. */ |  * ies[] array is not stored directly, but parsed into WifiScanEntry fields | ||||||
|  |  * | ||||||
|  |  */ | ||||||
| public class WifiScanEntryResult { | public class WifiScanEntryResult { | ||||||
| 	public int channel; | 	public int channel; | ||||||
| 	public long last_seen; | 	public long last_seen; | ||||||
| @@ -50,8 +52,6 @@ public class WifiScanEntryResult { | |||||||
| 	public String vht_oper; | 	public String vht_oper; | ||||||
| 	public int capability; | 	public int capability; | ||||||
| 	public int frequency; | 	public int frequency; | ||||||
| 	/** IE = information element */ |  | ||||||
| 	public JsonArray ies; |  | ||||||
| 
 | 
 | ||||||
| 	/** Default Constructor. */ | 	/** Default Constructor. */ | ||||||
| 	public WifiScanEntryResult() {} | 	public WifiScanEntryResult() {} | ||||||
| @@ -68,7 +68,6 @@ public class WifiScanEntryResult { | |||||||
| 		this.vht_oper = o.vht_oper; | 		this.vht_oper = o.vht_oper; | ||||||
| 		this.capability = o.capability; | 		this.capability = o.capability; | ||||||
| 		this.frequency = o.frequency; | 		this.frequency = o.frequency; | ||||||
| 		this.ies = o.ies; |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Override | 	@Override | ||||||
| @@ -79,7 +78,6 @@ public class WifiScanEntryResult { | |||||||
| 			channel, | 			channel, | ||||||
| 			frequency, | 			frequency, | ||||||
| 			ht_oper, | 			ht_oper, | ||||||
| 			ies, |  | ||||||
| 			last_seen, | 			last_seen, | ||||||
| 			signal, | 			signal, | ||||||
| 			ssid, | 			ssid, | ||||||
| @@ -104,7 +102,9 @@ public class WifiScanEntryResult { | |||||||
| 			capability == other.capability && channel == other.channel && | 			capability == other.capability && channel == other.channel && | ||||||
| 			frequency == other.frequency && Objects | 			frequency == other.frequency && Objects | ||||||
| 				.equals(ht_oper, other.ht_oper) && | 				.equals(ht_oper, other.ht_oper) && | ||||||
| 			Objects.equals(ies, other.ies) && last_seen == other.last_seen && signal == other.signal && Objects.equals(ssid, other.ssid) && tsf == other.tsf && Objects.equals(vht_oper, other.vht_oper); | 			last_seen == other.last_seen && | ||||||
|  | 			Objects.equals(ssid, other.ssid) && tsf == other.tsf && | ||||||
|  | 			Objects.equals(vht_oper, other.vht_oper); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	@Override | 	@Override | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class AclTemplate { | public class AclTemplate { | ||||||
| 	public boolean Read; | 	public boolean Read; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class DeviceConfigureRequest { | public class DeviceConfigureRequest { | ||||||
| 	public String serialNumber; | 	public String serialNumber; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -6,6 +6,6 @@ | |||||||
|  * 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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public enum DeviceType { AP, SWITCH, IOT, MESH } | public enum DeviceType { AP, SWITCH, IOT, MESH } | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class MfaAuthInfo { | public class MfaAuthInfo { | ||||||
| 	public boolean enabled; | 	public boolean enabled; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class MobilePhoneNumber { | public class MobilePhoneNumber { | ||||||
| 	public String number; | 	public String number; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class NoteInfo { | public class NoteInfo { | ||||||
| 	public long created; | 	public long created; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class ServiceEvent { | public class ServiceEvent { | ||||||
| 	public static final String EVENT_JOIN = "join"; | 	public static final String EVENT_JOIN = "join"; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class TokenValidationResult { | public class TokenValidationResult { | ||||||
| 	public UserInfo userInfo; | 	public UserInfo userInfo; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public enum VerifiedCertificate { | public enum VerifiedCertificate { | ||||||
| 	NO_CERTIFICATE, VALID_CERTIFICATE, MISMATCH_SERIAL, VERIFIED | 	NO_CERTIFICATE, VALID_CERTIFICATE, MISMATCH_SERIAL, VERIFIED | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class WebTokenAclTemplate { | public class WebTokenAclTemplate { | ||||||
| 	public AclTemplate aclTemplate; | 	public AclTemplate aclTemplate; | ||||||
| @@ -6,13 +6,13 @@ | |||||||
|  * 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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| public class WebTokenResult { | public class WebTokenResult { | ||||||
| 	public String access_token; | 	public String access_token; | ||||||
| 	public String refresh_token; | 	public String refresh_token; | ||||||
| 	public String token_type; | 	public String token_type; | ||||||
| 	public int expires_in; | 	public long expires_in; | ||||||
| 	public int idle_timeout; | 	public int idle_timeout; | ||||||
| 	public String username; | 	public String username; | ||||||
| 	public long created; | 	public long created; | ||||||
| @@ -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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -6,14 +6,14 @@ | |||||||
|  * 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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo; | import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo; | ||||||
| 
 | 
 | ||||||
| public class DeviceConfiguration { | public class DeviceConfiguration { | ||||||
| 	public class DeviceConfigurationElement { | 	public static class DeviceConfigurationElement { | ||||||
| 		public String name; | 		public String name; | ||||||
| 		public String description; | 		public String description; | ||||||
| 		public Integer weight; | 		public Integer weight; | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| public class DeviceRules { | public class DeviceRules { | ||||||
| 	public String rcOnly; | 	public String rcOnly; | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| 
 | 
 | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| public class DiGraphEntry { | public class DiGraphEntry { | ||||||
| 	public String parent; | 	public String parent; | ||||||
| @@ -6,11 +6,11 @@ | |||||||
|  * 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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo; | import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo; | ||||||
| 
 | 
 | ||||||
| public class Entity { | public class Entity { | ||||||
| 	// from ObjectInfo | 	// from ObjectInfo | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -6,11 +6,11 @@ | |||||||
|  * 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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo; | import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo; | ||||||
| 
 | 
 | ||||||
| public class InventoryTag { | public class InventoryTag { | ||||||
| 	// from ObjectInfo | 	// from ObjectInfo | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| public class RRMAlgorithmDetails { | public class RRMAlgorithmDetails { | ||||||
| 	public String name; | 	public String name; | ||||||
| @@ -6,12 +6,12 @@ | |||||||
|  * 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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| public class RRMDetails { | public class RRMDetails { | ||||||
| 	public class RRMDetailsImpl { | 	public static class RRMDetailsImpl { | ||||||
| 		public String vendor; | 		public String vendor; | ||||||
| 		public String schedule; | 		public String schedule; | ||||||
| 		public List<RRMAlgorithmDetails> algorithms; | 		public List<RRMAlgorithmDetails> algorithms; | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -6,11 +6,11 @@ | |||||||
|  * 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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.NoteInfo; | import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo; | ||||||
| 
 | 
 | ||||||
| public class Venue { | public class Venue { | ||||||
| 	// from ObjectInfo | 	// from ObjectInfo | ||||||
| @@ -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.prov.models; | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.prov.rrm.models; | package com.facebook.openwifi.cloudsdk.models.prov.rrm; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -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.prov.rrm.models; | package com.facebook.openwifi.cloudsdk.models.prov.rrm; | ||||||
| 
 | 
 | ||||||
| public class Provider { | public class Provider { | ||||||
| 	public String vendor; | 	public String vendor; | ||||||
							
								
								
									
										6
									
								
								lib-cloudsdk/src/main/resources/log4j.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								lib-cloudsdk/src/main/resources/log4j.properties
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | log4j.rootLogger=DEBUG, stdout | ||||||
|  |  | ||||||
|  | log4j.appender.stdout=org.apache.log4j.ConsoleAppender | ||||||
|  | log4j.appender.stdout.Target=System.out | ||||||
|  | log4j.appender.stdout.layout=org.apache.log4j.PatternLayout | ||||||
|  | log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd'T'HH:mm:ss.SSS} %-5p [%c{1}:%L] - %m%n | ||||||
| @@ -0,0 +1,81 @@ | |||||||
|  | /* | ||||||
|  |  * 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.openwifi.cloudsdk; | ||||||
|  |  | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertFalse; | ||||||
|  | import static org.junit.jupiter.api.Assertions.assertTrue; | ||||||
|  |  | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import org.junit.jupiter.api.Test; | ||||||
|  |  | ||||||
|  | public class UCentralUtilsTest { | ||||||
|  | 	@Test | ||||||
|  | 	void test_placeholder() throws Exception { | ||||||
|  | 		assertEquals(3, 1 + 2); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Test | ||||||
|  | 	void test_setRadioConfigFieldChannel() throws Exception { | ||||||
|  | 		final String serialNumber = "aaaaaaaaaaaa"; | ||||||
|  | 		final int expectedChannel = 1; | ||||||
|  | 		final Map<String, Integer> newValueList = Collections | ||||||
|  | 			.singletonMap(UCentralConstants.BAND_5G, expectedChannel); | ||||||
|  |  | ||||||
|  | 		// test case where channel value is a string and not an integer | ||||||
|  | 		UCentralApConfiguration config = new UCentralApConfiguration( | ||||||
|  | 			"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\", \"channel\": \"auto\"}]}" | ||||||
|  | 		); | ||||||
|  | 		boolean modified = UCentralUtils | ||||||
|  | 			.setRadioConfigField(serialNumber, config, "channel", newValueList); | ||||||
|  | 		assertTrue(modified); | ||||||
|  | 		assertEquals( | ||||||
|  | 			config.getRadioConfig(0).get("channel").getAsInt(), | ||||||
|  | 			expectedChannel | ||||||
|  | 		); | ||||||
|  |  | ||||||
|  | 		// field doesn't exist | ||||||
|  | 		config = new UCentralApConfiguration( | ||||||
|  | 			"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\"}]}" | ||||||
|  | 		); | ||||||
|  | 		modified = UCentralUtils | ||||||
|  | 			.setRadioConfigField(serialNumber, config, "channel", newValueList); | ||||||
|  | 		assertTrue(modified); | ||||||
|  | 		assertEquals( | ||||||
|  | 			config.getRadioConfig(0).get("channel").getAsInt(), | ||||||
|  | 			expectedChannel | ||||||
|  | 		); | ||||||
|  |  | ||||||
|  | 		// normal field, not modified | ||||||
|  | 		config = new UCentralApConfiguration( | ||||||
|  | 			"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\", \"channel\": 1}]}" | ||||||
|  | 		); | ||||||
|  | 		modified = UCentralUtils | ||||||
|  | 			.setRadioConfigField(serialNumber, config, "channel", newValueList); | ||||||
|  | 		assertFalse(modified); | ||||||
|  | 		assertEquals( | ||||||
|  | 			config.getRadioConfig(0).get("channel").getAsInt(), | ||||||
|  | 			expectedChannel | ||||||
|  | 		); | ||||||
|  |  | ||||||
|  | 		// normal field, modified | ||||||
|  | 		config = new UCentralApConfiguration( | ||||||
|  | 			"{\"interfaces\": [], \"radios\": [{\"band\": \"5G\", \"channel\": 15}]}" | ||||||
|  | 		); | ||||||
|  | 		modified = UCentralUtils | ||||||
|  | 			.setRadioConfigField(serialNumber, config, "channel", newValueList); | ||||||
|  | 		assertTrue(modified); | ||||||
|  | 		assertEquals( | ||||||
|  | 			config.getRadioConfig(0).get("channel").getAsInt(), | ||||||
|  | 			expectedChannel | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -6,17 +6,17 @@ | |||||||
|  * LICENSE file in the root directory of this source tree. |  * LICENSE file in the root directory of this source tree. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package com.facebook.openwifirrm.ucentral.operationelement; | package com.facebook.openwifi.cloudsdk.ies; | ||||||
| 
 | 
 | ||||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
| 
 | 
 | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| 
 | 
 | ||||||
| public class HTOperationElementTest { | public class HTOperationTest { | ||||||
| 	@Test | 	@Test | ||||||
| 	void testGetHtOper() { | 	void testGetHtOper() { | ||||||
| 		String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA=="; | 		String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA=="; | ||||||
| 		HTOperationElement htOperObj = new HTOperationElement(htOper); | 		HTOperation htOperObj = new HTOperation(htOper); | ||||||
| 		byte expectedPrimaryChannel = 1; | 		byte expectedPrimaryChannel = 1; | ||||||
| 		byte expectedSecondaryChannelOffset = 0; | 		byte expectedSecondaryChannelOffset = 0; | ||||||
| 		boolean expectedStaChannelWidth = false; | 		boolean expectedStaChannelWidth = false; | ||||||
| @@ -28,7 +28,7 @@ public class HTOperationElementTest { | |||||||
| 		boolean expectedDualBeacon = false; | 		boolean expectedDualBeacon = false; | ||||||
| 		boolean expectedDualCtsProtection = false; | 		boolean expectedDualCtsProtection = false; | ||||||
| 		boolean expectedStbcBeacon = false; | 		boolean expectedStbcBeacon = false; | ||||||
| 		HTOperationElement expectedHtOperObj = new HTOperationElement( | 		HTOperation expectedHtOperObj = new HTOperation( | ||||||
| 			expectedPrimaryChannel, | 			expectedPrimaryChannel, | ||||||
| 			expectedSecondaryChannelOffset, | 			expectedSecondaryChannelOffset, | ||||||
| 			expectedStaChannelWidth, | 			expectedStaChannelWidth, | ||||||
| @@ -44,11 +44,11 @@ public class HTOperationElementTest { | |||||||
| 		assertEquals(expectedHtOperObj, htOperObj); | 		assertEquals(expectedHtOperObj, htOperObj); | ||||||
| 
 | 
 | ||||||
| 		htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; | 		htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="; | ||||||
| 		htOperObj = new HTOperationElement(htOper); | 		htOperObj = new HTOperation(htOper); | ||||||
| 		// all fields except the primary channel and nongreenfield field are the same | 		// all fields except the primary channel and nongreenfield field are the same | ||||||
| 		expectedPrimaryChannel = 36; | 		expectedPrimaryChannel = 36; | ||||||
| 		expectedNongreenfieldHtStasPresent = false; | 		expectedNongreenfieldHtStasPresent = false; | ||||||
| 		expectedHtOperObj = new HTOperationElement( | 		expectedHtOperObj = new HTOperation( | ||||||
| 			expectedPrimaryChannel, | 			expectedPrimaryChannel, | ||||||
| 			expectedSecondaryChannelOffset, | 			expectedSecondaryChannelOffset, | ||||||
| 			expectedStaChannelWidth, | 			expectedStaChannelWidth, | ||||||
| @@ -6,23 +6,23 @@ | |||||||
|  * LICENSE file in the root directory of this source tree. |  * LICENSE file in the root directory of this source tree. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package com.facebook.openwifirrm.ucentral.operationelement; | package com.facebook.openwifi.cloudsdk.ies; | ||||||
| 
 | 
 | ||||||
| import static org.junit.jupiter.api.Assertions.assertEquals; | import static org.junit.jupiter.api.Assertions.assertEquals; | ||||||
| 
 | 
 | ||||||
| import org.junit.jupiter.api.Test; | import org.junit.jupiter.api.Test; | ||||||
| 
 | 
 | ||||||
| public class VHTOperationElementTest { | public class VHTOperationTest { | ||||||
| 
 | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	void testGetVhtOper() { | 	void testGetVhtOper() { | ||||||
| 		String vhtOper = "ACQAAAA="; | 		String vhtOper = "ACQAAAA="; | ||||||
| 		VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper); | 		VHTOperation vhtOperObj = new VHTOperation(vhtOper); | ||||||
| 		byte expectedChannelWidthIndicator = 0; // 20 MHz channel width | 		byte expectedChannelWidthIndicator = 0; // 20 MHz channel width | ||||||
| 		short expectedChannel1 = 36; | 		short expectedChannel1 = 36; | ||||||
| 		short expectedChannel2 = 0; | 		short expectedChannel2 = 0; | ||||||
| 		byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; | 		byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; | ||||||
| 		VHTOperationElement expectedVhtOperObj = new VHTOperationElement( | 		VHTOperation expectedVhtOperObj = new VHTOperation( | ||||||
| 			expectedChannelWidthIndicator, | 			expectedChannelWidthIndicator, | ||||||
| 			expectedChannel1, | 			expectedChannel1, | ||||||
| 			expectedChannel2, | 			expectedChannel2, | ||||||
| @@ -31,12 +31,12 @@ public class VHTOperationElementTest { | |||||||
| 		assertEquals(expectedVhtOperObj, vhtOperObj); | 		assertEquals(expectedVhtOperObj, vhtOperObj); | ||||||
| 
 | 
 | ||||||
| 		vhtOper = "AToAUAE="; | 		vhtOper = "AToAUAE="; | ||||||
| 		vhtOperObj = new VHTOperationElement(vhtOper); | 		vhtOperObj = new VHTOperation(vhtOper); | ||||||
| 		expectedChannelWidthIndicator = 1; // 80 MHz channel width | 		expectedChannelWidthIndicator = 1; // 80 MHz channel width | ||||||
| 		expectedChannel1 = 58; | 		expectedChannel1 = 58; | ||||||
| 		// same channel2 | 		// same channel2 | ||||||
| 		expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 }; | 		expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 }; | ||||||
| 		expectedVhtOperObj = new VHTOperationElement( | 		expectedVhtOperObj = new VHTOperation( | ||||||
| 			expectedChannelWidthIndicator, | 			expectedChannelWidthIndicator, | ||||||
| 			expectedChannel1, | 			expectedChannel1, | ||||||
| 			expectedChannel2, | 			expectedChannel2, | ||||||
| @@ -45,12 +45,12 @@ public class VHTOperationElementTest { | |||||||
| 		assertEquals(expectedVhtOperObj, vhtOperObj); | 		assertEquals(expectedVhtOperObj, vhtOperObj); | ||||||
| 
 | 
 | ||||||
| 		vhtOper = "ASoyUAE="; | 		vhtOper = "ASoyUAE="; | ||||||
| 		vhtOperObj = new VHTOperationElement(vhtOper); | 		vhtOperObj = new VHTOperation(vhtOper); | ||||||
| 		// same channel width indicator (160 MHz channel width) | 		// same channel width indicator (160 MHz channel width) | ||||||
| 		expectedChannel1 = 42; | 		expectedChannel1 = 42; | ||||||
| 		expectedChannel2 = 50; | 		expectedChannel2 = 50; | ||||||
| 		// same vhtMcsForNss | 		// same vhtMcsForNss | ||||||
| 		expectedVhtOperObj = new VHTOperationElement( | 		expectedVhtOperObj = new VHTOperation( | ||||||
| 			expectedChannelWidthIndicator, | 			expectedChannelWidthIndicator, | ||||||
| 			expectedChannel1, | 			expectedChannel1, | ||||||
| 			expectedChannel2, | 			expectedChannel2, | ||||||
| @@ -60,12 +60,12 @@ public class VHTOperationElementTest { | |||||||
| 
 | 
 | ||||||
| 		// test with channel number >= 128 (channel fields should be unsigned) | 		// test with channel number >= 128 (channel fields should be unsigned) | ||||||
| 		vhtOper = "AJUAAAA="; | 		vhtOper = "AJUAAAA="; | ||||||
| 		vhtOperObj = new VHTOperationElement(vhtOper); | 		vhtOperObj = new VHTOperation(vhtOper); | ||||||
| 		expectedChannelWidthIndicator = 0; | 		expectedChannelWidthIndicator = 0; | ||||||
| 		expectedChannel1 = 149; | 		expectedChannel1 = 149; | ||||||
| 		expectedChannel2 = 0; | 		expectedChannel2 = 0; | ||||||
| 		expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; | 		expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }; | ||||||
| 		expectedVhtOperObj = new VHTOperationElement( | 		expectedVhtOperObj = new VHTOperation( | ||||||
| 			expectedChannelWidthIndicator, | 			expectedChannelWidthIndicator, | ||||||
| 			expectedChannel1, | 			expectedChannel1, | ||||||
| 			expectedChannel2, | 			expectedChannel2, | ||||||
							
								
								
									
										4
									
								
								owrrm/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								owrrm/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | /*.log* | ||||||
|  | /device_config.json | ||||||
|  | /settings.json | ||||||
|  | /topology.json | ||||||
							
								
								
									
										39
									
								
								owrrm/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								owrrm/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | # OpenWiFi RRM Service | ||||||
|  | OpenWiFi uCentral-based radio resource management (RRM) service, providing a | ||||||
|  | cloud-based Wi-Fi RRM layer for APs running the OpenWiFi SDK. | ||||||
|  |  | ||||||
|  | This service collects data from OpenWiFi APs (e.g. Wi-Fi scans, stats, | ||||||
|  | capabilities) via the uCentral Gateway and Kafka, and integrates with the | ||||||
|  | OpenWiFi Provisioning service to perform optimization across configured | ||||||
|  | "venues". It pushes new device configuration parameters to APs after RRM | ||||||
|  | algorithms are run (manually or periodically). | ||||||
|  |  | ||||||
|  | See [IMPLEMENTATION.md](IMPLEMENTATION.md) for service architecture details and | ||||||
|  | [ALGORITHMS.md](ALGORITHMS.md) for descriptions of the RRM algorithms. | ||||||
|  |  | ||||||
|  | ## Usage | ||||||
|  | ``` | ||||||
|  | $ java -jar openwifi-rrm.jar [-h] | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | To start the service, use the `run` command while providing configuration via | ||||||
|  | either environment variables (`--config-env`) or a static JSON file | ||||||
|  | (`--config-file`, default `settings.json`). The following data is *required*: | ||||||
|  | * Service configuration | ||||||
|  |     * Env: `SERVICECONFIG_PRIVATEENDPOINT`, `SERVICECONFIG_PUBLICENDPOINT` | ||||||
|  |     * JSON: `serviceConfig` structure | ||||||
|  | * Kafka broker URL | ||||||
|  |     * Env: `KAFKACONFIG_BOOTSTRAPSERVER` | ||||||
|  |     * JSON: `kafkaConfig` structure | ||||||
|  | * MySQL database credentials | ||||||
|  |     * Env: `DATABASECONFIG_SERVER`, `DATABASECONFIG_USER`, `DATABASECONFIG_PASSWORD` | ||||||
|  |     * JSON: `databaseConfig` structure | ||||||
|  |  | ||||||
|  | ## OpenAPI | ||||||
|  | This service provides an OpenAPI HTTP interface on the port specified in the | ||||||
|  | service configuration (`moduleConfig.apiServerParams`). An auto-generated | ||||||
|  | OpenAPI 3.0 document is hosted at the endpoints `/openapi.{yaml,json}` and is | ||||||
|  | written to [openapi.yaml](openapi.yaml) during the Maven "compile" phase. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | [JUnit 5]: https://junit.org/junit5/ | ||||||
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| @@ -478,7 +478,9 @@ components: | |||||||
|     RRMSchedule: |     RRMSchedule: | ||||||
|       type: object |       type: object | ||||||
|       properties: |       properties: | ||||||
|         cron: |         crons: | ||||||
|  |           type: array | ||||||
|  |           items: | ||||||
|             type: string |             type: string | ||||||
|         algorithms: |         algorithms: | ||||||
|           type: array |           type: array | ||||||
							
								
								
									
										143
									
								
								owrrm/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								owrrm/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | |||||||
|  | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||||
|  |   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> | ||||||
|  |   <modelVersion>4.0.0</modelVersion> | ||||||
|  |   <artifactId>openwifi-rrm</artifactId> | ||||||
|  |   <packaging>jar</packaging> | ||||||
|  |   <parent> | ||||||
|  |     <groupId>com.facebook</groupId> | ||||||
|  |     <artifactId>openwifi-base</artifactId> | ||||||
|  |     <version>2.7.0</version> | ||||||
|  |   </parent> | ||||||
|  |   <properties> | ||||||
|  |     <!-- Hack for static files located in root project --> | ||||||
|  |     <myproject.root>${project.basedir}/..</myproject.root> | ||||||
|  |     <mainClassName>com.facebook.openwifi.rrm.Launcher</mainClassName> | ||||||
|  |     <appendVersionString></appendVersionString> | ||||||
|  |   </properties> | ||||||
|  |   <build> | ||||||
|  |     <finalName>openwifi-rrm</finalName> | ||||||
|  |     <resources> | ||||||
|  |       <resource> | ||||||
|  |         <directory>src/main/resources</directory> | ||||||
|  |         <includes> | ||||||
|  |           <include>**/*</include> | ||||||
|  |         </includes> | ||||||
|  |       </resource> | ||||||
|  |       <resource> | ||||||
|  |         <directory>src/main/resources-filtered</directory> | ||||||
|  |         <filtering>true</filtering> | ||||||
|  |         <includes> | ||||||
|  |           <include>**/*</include> | ||||||
|  |         </includes> | ||||||
|  |       </resource> | ||||||
|  |     </resources> | ||||||
|  |     <plugins> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-compiler-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-surefire-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-shade-plugin</artifactId> | ||||||
|  |         <configuration> | ||||||
|  |           <transformers> | ||||||
|  |             <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> | ||||||
|  |               <manifestEntries> | ||||||
|  |                 <Main-Class>${mainClassName}</Main-Class> | ||||||
|  |               </manifestEntries> | ||||||
|  |             </transformer> | ||||||
|  |           </transformers> | ||||||
|  |         </configuration> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>org.apache.maven.plugins</groupId> | ||||||
|  |         <artifactId>maven-javadoc-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>io.swagger.core.v3</groupId> | ||||||
|  |         <artifactId>swagger-maven-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |       <plugin> | ||||||
|  |         <groupId>com.diffplug.spotless</groupId> | ||||||
|  |         <artifactId>spotless-maven-plugin</artifactId> | ||||||
|  |       </plugin> | ||||||
|  |     </plugins> | ||||||
|  |   </build> | ||||||
|  |   <dependencies> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.facebook</groupId> | ||||||
|  |       <artifactId>openwifi-cloudsdk</artifactId> | ||||||
|  |       <version>${project.version}</version> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.slf4j</groupId> | ||||||
|  |       <artifactId>slf4j-api</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.slf4j</groupId> | ||||||
|  |       <artifactId>slf4j-log4j12</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.junit.jupiter</groupId> | ||||||
|  |       <artifactId>junit-jupiter-api</artifactId> | ||||||
|  |       <scope>test</scope> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.junit.jupiter</groupId> | ||||||
|  |       <artifactId>junit-jupiter-engine</artifactId> | ||||||
|  |       <scope>test</scope> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>info.picocli</groupId> | ||||||
|  |       <artifactId>picocli</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.json</groupId> | ||||||
|  |       <artifactId>json</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.google.code.gson</groupId> | ||||||
|  |       <artifactId>gson</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.konghq</groupId> | ||||||
|  |       <artifactId>unirest-java</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>mysql</groupId> | ||||||
|  |       <artifactId>mysql-connector-java</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.zaxxer</groupId> | ||||||
|  |       <artifactId>HikariCP</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>com.sparkjava</groupId> | ||||||
|  |       <artifactId>spark-core</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>io.swagger.core.v3</groupId> | ||||||
|  |       <artifactId>swagger-jaxrs2</artifactId> | ||||||
|  |       </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>javax.ws.rs</groupId> | ||||||
|  |       <artifactId>javax.ws.rs-api</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.reflections</groupId> | ||||||
|  |       <artifactId>reflections</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.quartz-scheduler</groupId> | ||||||
|  |       <artifactId>quartz</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |     <dependency> | ||||||
|  |       <groupId>org.openjdk.jol</groupId> | ||||||
|  |       <artifactId>jol-core</artifactId> | ||||||
|  |     </dependency> | ||||||
|  |   </dependencies> | ||||||
|  | </project> | ||||||
| @@ -0,0 +1,160 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package com.facebook.openwifi.rrm; | ||||||
|  |  | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  |  | ||||||
|  | import org.eclipse.jetty.server.Connector; | ||||||
|  | import org.eclipse.jetty.server.ForwardedRequestCustomizer; | ||||||
|  | import org.eclipse.jetty.server.HttpConfiguration; | ||||||
|  | import org.eclipse.jetty.server.HttpConnectionFactory; | ||||||
|  | import org.eclipse.jetty.server.Server; | ||||||
|  | import org.eclipse.jetty.server.ServerConnector; | ||||||
|  | import org.eclipse.jetty.util.thread.QueuedThreadPool; | ||||||
|  | import org.eclipse.jetty.util.thread.ThreadPool; | ||||||
|  |  | ||||||
|  | import spark.embeddedserver.jetty.JettyServerFactory; | ||||||
|  | import spark.utils.Assert; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Creates Jetty Server instances. Majority of the logic is taken from | ||||||
|  |  * JettyServerFactory. The additional feature is that this class will actually | ||||||
|  |  * set two connectors (original class doesn't set any connectors at all and | ||||||
|  |  * leaves it up to the serivce start logic). Since we set two connectors here on | ||||||
|  |  * the server, Spark uses the existing conectors instead of trying to spin up | ||||||
|  |  * its own connectors. The other difference is that it uses a different | ||||||
|  |  * ServerConnector constructor to avoid allocating additional threads that | ||||||
|  |  * aren't necessary ({@link #makeConnector}) | ||||||
|  |  * | ||||||
|  |  * @see spark.embeddedserver.jetty.EmbeddedJettyFactory | ||||||
|  |  */ | ||||||
|  | public class CustomJettyServerFactory implements JettyServerFactory { | ||||||
|  | 	// normally this is set in EmbeddedJettyServer but since we create our own connectors here, | ||||||
|  | 	// we need the value here | ||||||
|  | 	private boolean trustForwardHeaders = true; // true by default | ||||||
|  | 	private final int internalPort; | ||||||
|  | 	private final int externalPort; | ||||||
|  |  | ||||||
|  | 	public CustomJettyServerFactory(int internalPort, int externalPort) { | ||||||
|  | 		this.internalPort = internalPort; | ||||||
|  | 		this.externalPort = externalPort; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public void setTrustForwardHeaders(boolean trustForwardHeaders) { | ||||||
|  | 		this.trustForwardHeaders = trustForwardHeaders; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * This is basically | ||||||
|  | 	 * spark.embeddedserver.jetty.SocketConnectorFactory.createSocketConnector, | ||||||
|  | 	 * the only difference being that we use a different constructor for the | ||||||
|  | 	 * Connector and that the private methods called are just inlined. | ||||||
|  | 	 */ | ||||||
|  | 	public Connector makeConnector( | ||||||
|  | 		Server server, | ||||||
|  | 		String host, | ||||||
|  | 		int port, | ||||||
|  | 		boolean trustForwardHeaders | ||||||
|  | 	) { | ||||||
|  | 		Assert.notNull(server, "'server' must not be null"); | ||||||
|  | 		Assert.notNull(host, "'host' must not be null"); | ||||||
|  |  | ||||||
|  | 		// spark.embeddedserver.jetty.SocketConnectorFactory.createHttpConnectionFactory | ||||||
|  | 		HttpConfiguration httpConfig = new HttpConfiguration(); | ||||||
|  | 		httpConfig.setSecureScheme("https"); | ||||||
|  | 		if (trustForwardHeaders) { | ||||||
|  | 			httpConfig.addCustomizer(new ForwardedRequestCustomizer()); | ||||||
|  | 		} | ||||||
|  | 		HttpConnectionFactory httpConnectionFactory = | ||||||
|  | 			new HttpConnectionFactory(httpConfig); | ||||||
|  |  | ||||||
|  | 		ServerConnector connector = new ServerConnector( | ||||||
|  | 			server, | ||||||
|  | 			0, // acceptors, don't allocate separate threads for acceptor | ||||||
|  | 			0, // selectors, use default number | ||||||
|  | 			httpConnectionFactory | ||||||
|  | 		); | ||||||
|  | 		// spark.embeddedserver.jetty.SocketConnectorFactory.initializeConnector | ||||||
|  | 		connector.setIdleTimeout(TimeUnit.HOURS.toMillis(1)); | ||||||
|  | 		connector.setHost(host); | ||||||
|  | 		connector.setPort(port); | ||||||
|  |  | ||||||
|  | 		return connector; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Creates a Jetty server. | ||||||
|  | 	 * | ||||||
|  | 	 * @param maxThreads          maxThreads | ||||||
|  | 	 * @param minThreads          minThreads | ||||||
|  | 	 * @param threadTimeoutMillis threadTimeoutMillis | ||||||
|  | 	 * @return a new jetty server instance | ||||||
|  | 	 */ | ||||||
|  | 	@Override | ||||||
|  | 	public Server create( | ||||||
|  | 		int maxThreads, | ||||||
|  | 		int minThreads, | ||||||
|  | 		int threadTimeoutMillis | ||||||
|  | 	) { | ||||||
|  | 		Server server; | ||||||
|  |  | ||||||
|  | 		if (maxThreads > 0) { | ||||||
|  | 			int max = maxThreads; | ||||||
|  | 			int min = (minThreads > 0) ? minThreads : 8; | ||||||
|  | 			int idleTimeout = | ||||||
|  | 				(threadTimeoutMillis > 0) ? threadTimeoutMillis : 60000; | ||||||
|  |  | ||||||
|  | 			server = new Server(new QueuedThreadPool(max, min, idleTimeout)); | ||||||
|  | 		} else { | ||||||
|  | 			server = new Server(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Connector internalConnector = null; | ||||||
|  | 		if (internalPort != -1) { | ||||||
|  | 			internalConnector = makeConnector( | ||||||
|  | 				server, | ||||||
|  | 				"0.0.0.0", | ||||||
|  | 				internalPort, | ||||||
|  | 				trustForwardHeaders | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Connector externalConnector = null; | ||||||
|  | 		if (externalPort != -1) { | ||||||
|  | 			externalConnector = makeConnector( | ||||||
|  | 				server, | ||||||
|  | 				"0.0.0.0", | ||||||
|  | 				externalPort, | ||||||
|  | 				trustForwardHeaders | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (internalConnector == null) { | ||||||
|  | 			server.setConnectors(new Connector[] { externalConnector }); | ||||||
|  | 		} else if (externalConnector == null) { | ||||||
|  | 			server.setConnectors(new Connector[] { internalConnector }); | ||||||
|  | 		} else { | ||||||
|  | 			server.setConnectors( | ||||||
|  | 				new Connector[] { internalConnector, externalConnector } | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return server; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Creates a Jetty server with supplied thread pool | ||||||
|  | 	 * @param threadPool thread pool | ||||||
|  | 	 * @return a new jetty server instance | ||||||
|  | 	 */ | ||||||
|  | 	@Override | ||||||
|  | 	public Server create(ThreadPool threadPool) { | ||||||
|  | 		return threadPool != null ? new Server(threadPool) : new Server(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.lang.reflect.Field; | import java.lang.reflect.Field; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | ||||||
| @@ -47,17 +47,17 @@ public class DeviceDataManager { | |||||||
| 	private final ReadWriteLock topologyLock = new ReentrantReadWriteLock(); | 	private final ReadWriteLock topologyLock = new ReentrantReadWriteLock(); | ||||||
| 
 | 
 | ||||||
| 	/** Lock on {@link #deviceLayeredConfig}. */ | 	/** Lock on {@link #deviceLayeredConfig}. */ | ||||||
| 	private final ReadWriteLock deviceLayeredConfigLock = | 	public final ReadWriteLock deviceLayeredConfigLock = | ||||||
| 		new ReentrantReadWriteLock(); | 		new ReentrantReadWriteLock(); | ||||||
| 
 | 
 | ||||||
| 	/** The current device topology. */ | 	/** The current device topology. */ | ||||||
| 	private DeviceTopology topology; | 	public DeviceTopology topology; | ||||||
| 
 | 
 | ||||||
| 	/** The current layered device config. */ | 	/** The current layered device config. */ | ||||||
| 	private DeviceLayeredConfig deviceLayeredConfig; | 	public DeviceLayeredConfig deviceLayeredConfig; | ||||||
| 
 | 
 | ||||||
| 	/** The cached device configs (map of serial number to computed config). */ | 	/** The cached device configs (map of serial number to computed config). */ | ||||||
| 	private Map<String, DeviceConfig> cachedDeviceConfigs = | 	public Map<String, DeviceConfig> cachedDeviceConfigs = | ||||||
| 		new ConcurrentHashMap<>(); | 		new ConcurrentHashMap<>(); | ||||||
| 
 | 
 | ||||||
| 	/** Empty constructor without backing files (ex. for unit tests). */ | 	/** Empty constructor without backing files (ex. for unit tests). */ | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.TreeMap; | import java.util.TreeMap; | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.TreeMap; | import java.util.TreeMap; | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.FileWriter; | import java.io.FileWriter; | ||||||
| @@ -18,11 +18,10 @@ import org.json.JSONObject; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.mysql.DatabaseManager; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralClient; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer; | import com.facebook.openwifi.rrm.mysql.DatabaseManager; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; |  | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||||
| 
 | 
 | ||||||
| @@ -155,8 +154,7 @@ public class Launcher implements Callable<Integer> { | |||||||
| 				: DEFAULT_DEVICE_LAYERED_CONFIG_FILE | 				: DEFAULT_DEVICE_LAYERED_CONFIG_FILE | ||||||
| 		); | 		); | ||||||
| 
 | 
 | ||||||
| 		String serviceKey = | 		String serviceKey = Utils.generateServiceKey(config.serviceConfig); | ||||||
| 			UCentralUtils.generateServiceKey(config.serviceConfig); |  | ||||||
| 
 | 
 | ||||||
| 		// Instantiate clients | 		// Instantiate clients | ||||||
| 		UCentralClient.verifySsl(config.uCentralConfig.verifySsl); | 		UCentralClient.verifySsl(config.uCentralConfig.verifySsl); | ||||||
| @@ -166,7 +164,9 @@ public class Launcher implements Callable<Integer> { | |||||||
| 			config.uCentralConfig.uCentralSecPublicEndpoint, | 			config.uCentralConfig.uCentralSecPublicEndpoint, | ||||||
| 			config.uCentralConfig.username, | 			config.uCentralConfig.username, | ||||||
| 			config.uCentralConfig.password, | 			config.uCentralConfig.password, | ||||||
| 			config.uCentralConfig.uCentralSocketParams | 			config.uCentralConfig.uCentralSocketParams.connectTimeoutMs, | ||||||
|  | 			config.uCentralConfig.uCentralSocketParams.socketTimeoutMs, | ||||||
|  | 			config.uCentralConfig.uCentralSocketParams.wifiScanTimeoutMs | ||||||
| 		); | 		); | ||||||
| 		UCentralKafkaConsumer consumer; | 		UCentralKafkaConsumer consumer; | ||||||
| 		UCentralKafkaProducer producer; | 		UCentralKafkaProducer producer; | ||||||
| @@ -265,7 +265,7 @@ public class Launcher implements Callable<Integer> { | |||||||
| 			.setPrettyPrinting() | 			.setPrettyPrinting() | ||||||
| 			.serializeNulls() // for here only!! | 			.serializeNulls() // for here only!! | ||||||
| 			.create(); | 			.create(); | ||||||
| 		logger.info(gson.toJson(DeviceConfig.createDefault())); | 		System.out.println(gson.toJson(DeviceConfig.createDefault())); | ||||||
| 		return 0; | 		return 0; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -18,18 +18,18 @@ import java.util.stream.Collectors; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.modules.ApiServer; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
| import com.facebook.openwifirrm.modules.ConfigManager; | import com.facebook.openwifi.cloudsdk.kafka.KafkaRunner; | ||||||
| import com.facebook.openwifirrm.modules.DataCollector; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer; | ||||||
| import com.facebook.openwifirrm.modules.Modeler; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer; | ||||||
| import com.facebook.openwifirrm.modules.ProvMonitor; | import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults; | ||||||
| import com.facebook.openwifirrm.modules.RRMScheduler; | import com.facebook.openwifi.rrm.modules.ApiServer; | ||||||
| import com.facebook.openwifirrm.mysql.DatabaseManager; | import com.facebook.openwifi.rrm.modules.ConfigManager; | ||||||
| import com.facebook.openwifirrm.ucentral.KafkaRunner; | import com.facebook.openwifi.rrm.modules.DataCollector; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralClient; | import com.facebook.openwifi.rrm.modules.Modeler; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer; | import com.facebook.openwifi.rrm.modules.ProvMonitor; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer; | import com.facebook.openwifi.rrm.modules.RRMScheduler; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults; | import com.facebook.openwifi.rrm.mysql.DatabaseManager; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * RRM service runner. |  * RRM service runner. | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| @@ -14,17 +14,17 @@ import java.util.Map; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.modules.ConfigManager; | import com.facebook.openwifi.rrm.modules.ConfigManager; | ||||||
| import com.facebook.openwifirrm.modules.Modeler; | import com.facebook.openwifi.rrm.modules.Modeler; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer; | import com.facebook.openwifi.rrm.optimizers.channel.ChannelOptimizer; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer; | import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer; | import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer; | import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC; | import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC; | import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC; | import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer; | import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.TPC; | import com.facebook.openwifi.rrm.optimizers.tpc.TPC; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * RRM algorithm model and utility methods. |  * RRM algorithm model and utility methods. | ||||||
| @@ -143,6 +143,9 @@ public class RRMAlgorithm { | |||||||
| 	 * @param dryRun if set, do not apply changes | 	 * @param dryRun if set, do not apply changes | ||||||
| 	 * @param allowDefaultMode if false, "mode" argument must be present and | 	 * @param allowDefaultMode if false, "mode" argument must be present and | ||||||
| 	 *                         valid (returns error if invalid) | 	 *                         valid (returns error if invalid) | ||||||
|  | 	 * @param updateImmediately true if the method should queue the zone for | ||||||
|  | 	 * 							update and interrupt the config manager thread | ||||||
|  | 	 * 							to trigger immediate update | ||||||
| 	 * | 	 * | ||||||
| 	 * @return the algorithm result, with exactly one field set ("error" upon | 	 * @return the algorithm result, with exactly one field set ("error" upon | ||||||
| 	 *         failure, any others upon success) | 	 *         failure, any others upon success) | ||||||
| @@ -153,7 +156,8 @@ public class RRMAlgorithm { | |||||||
| 		Modeler modeler, | 		Modeler modeler, | ||||||
| 		String zone, | 		String zone, | ||||||
| 		boolean dryRun, | 		boolean dryRun, | ||||||
| 		boolean allowDefaultMode | 		boolean allowDefaultMode, | ||||||
|  | 		boolean updateImmediately | ||||||
| 	) { | 	) { | ||||||
| 		AlgorithmResult result = new AlgorithmResult(); | 		AlgorithmResult result = new AlgorithmResult(); | ||||||
| 		if (name == null || args == null) { | 		if (name == null || args == null) { | ||||||
| @@ -212,11 +216,14 @@ public class RRMAlgorithm { | |||||||
| 			} | 			} | ||||||
| 			result.channelMap = optimizer.computeChannelMap(); | 			result.channelMap = optimizer.computeChannelMap(); | ||||||
| 			if (!dryRun) { | 			if (!dryRun) { | ||||||
| 				optimizer.applyConfig( | 				optimizer.updateDeviceApConfig( | ||||||
| 					deviceDataManager, | 					deviceDataManager, | ||||||
| 					configManager, | 					configManager, | ||||||
| 					result.channelMap | 					result.channelMap | ||||||
| 				); | 				); | ||||||
|  | 				if (updateImmediately) { | ||||||
|  | 					configManager.queueZoneAndWakeUp(zone); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} else if ( | 		} else if ( | ||||||
| 			name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name()) | 			name.equals(RRMAlgorithm.AlgorithmType.OptimizeTxPower.name()) | ||||||
| @@ -270,11 +277,14 @@ public class RRMAlgorithm { | |||||||
| 			} | 			} | ||||||
| 			result.txPowerMap = optimizer.computeTxPowerMap(); | 			result.txPowerMap = optimizer.computeTxPowerMap(); | ||||||
| 			if (!dryRun) { | 			if (!dryRun) { | ||||||
| 				optimizer.applyConfig( | 				optimizer.updateDeviceApConfig( | ||||||
| 					deviceDataManager, | 					deviceDataManager, | ||||||
| 					configManager, | 					configManager, | ||||||
| 					result.txPowerMap | 					result.txPowerMap | ||||||
| 				); | 				); | ||||||
|  | 				if (updateImmediately) { | ||||||
|  | 					configManager.queueZoneAndWakeUp(zone); | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			result.error = String.format("Unknown algorithm: '%s'", name); | 			result.error = String.format("Unknown algorithm: '%s'", name); | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| 
 | 
 | ||||||
| @@ -34,7 +34,7 @@ public class RRMConfig { | |||||||
| 		 * Private endpoint for the RRM service | 		 * Private endpoint for the RRM service | ||||||
| 		 * ({@code SERVICECONFIG_PRIVATEENDPOINT}) | 		 * ({@code SERVICECONFIG_PRIVATEENDPOINT}) | ||||||
| 		 */ | 		 */ | ||||||
| 		public String privateEndpoint = "http://owrrm.wlan.local:16789"; // see ApiServerParams.httpPort | 		public String privateEndpoint = "http://owrrm.wlan.local:16790"; // see ApiServerParams.internalHttpPort | ||||||
| 
 | 
 | ||||||
| 		/** | 		/** | ||||||
| 		 * Public endpoint for the RRM service | 		 * Public endpoint for the RRM service | ||||||
| @@ -60,7 +60,7 @@ public class RRMConfig { | |||||||
| 		 * ({@code SERVICECONFIG_VENDORREFERENCEURL}) | 		 * ({@code SERVICECONFIG_VENDORREFERENCEURL}) | ||||||
| 		 */ | 		 */ | ||||||
| 		public String vendorReferenceUrl = | 		public String vendorReferenceUrl = | ||||||
| 			"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/ALGORITHMS.md"; | 			"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/owrrm/ALGORITHMS.md"; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Service configuration. */ | 	/** Service configuration. */ | ||||||
| @@ -232,7 +232,7 @@ public class RRMConfig { | |||||||
| 			 * The main logic loop interval (i.e. sleep time), in ms | 			 * The main logic loop interval (i.e. sleep time), in ms | ||||||
| 			 * ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS}) | 			 * ({@code DATACOLLECTORPARAMS_UPDATEINTERVALMS}) | ||||||
| 			 */ | 			 */ | ||||||
| 			public int updateIntervalMs = 5000; | 			public int updateIntervalMs = 30000; // 30sec | ||||||
| 
 | 
 | ||||||
| 			/** | 			/** | ||||||
| 			 * The expected device statistics interval, in seconds (or -1 to | 			 * The expected device statistics interval, in seconds (or -1 to | ||||||
| @@ -246,13 +246,13 @@ public class RRMConfig { | |||||||
| 			 * automatic scans) | 			 * automatic scans) | ||||||
| 			 * ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC}) | 			 * ({@code DATACOLLECTORPARAMS_WIFISCANINTERVALSEC}) | ||||||
| 			 */ | 			 */ | ||||||
| 			public int wifiScanIntervalSec = 900; | 			public int wifiScanIntervalSec = 900; // 15min | ||||||
| 
 | 
 | ||||||
| 			/** | 			/** | ||||||
| 			 * The capabilities request interval (per device), in seconds | 			 * The capabilities request interval (per device), in seconds | ||||||
| 			 * ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC}) | 			 * ({@code DATACOLLECTORPARAMS_CAPABILITIESINTERVALSEC}) | ||||||
| 			 */ | 			 */ | ||||||
| 			public int capabilitiesIntervalSec = 3600; | 			public int capabilitiesIntervalSec = 3600; // 1hr | ||||||
| 
 | 
 | ||||||
| 			/** | 			/** | ||||||
| 			 * Number of executor threads for async tasks (ex. wifi scans) | 			 * Number of executor threads for async tasks (ex. wifi scans) | ||||||
| @@ -273,7 +273,7 @@ public class RRMConfig { | |||||||
| 			 * The main logic loop interval (i.e. sleep time), in ms | 			 * The main logic loop interval (i.e. sleep time), in ms | ||||||
| 			 * ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS}) | 			 * ({@code CONFIGMANAGERPARAMS_UPDATEINTERVALMS}) | ||||||
| 			 */ | 			 */ | ||||||
| 			public int updateIntervalMs = 60000; | 			public int updateIntervalMs = 120000; // 2min | ||||||
| 
 | 
 | ||||||
| 			/** | 			/** | ||||||
| 			 * Enable pushing device config changes? | 			 * Enable pushing device config changes? | ||||||
| @@ -309,6 +309,12 @@ public class RRMConfig { | |||||||
| 			 * ({@code MODELERPARAMS_WIFISCANBUFFERSIZE}) | 			 * ({@code MODELERPARAMS_WIFISCANBUFFERSIZE}) | ||||||
| 			 */ | 			 */ | ||||||
| 			public int wifiScanBufferSize = 10; | 			public int wifiScanBufferSize = 10; | ||||||
|  | 
 | ||||||
|  | 			/** | ||||||
|  | 			 * Maximum rounds of States to store per device | ||||||
|  | 			 * ({@code MODELERPARAMS_STATEBUFFERSIZE}) | ||||||
|  | 			 */ | ||||||
|  | 			public int stateBufferSize = 10; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		/** Modeler parameters. */ | 		/** Modeler parameters. */ | ||||||
| @@ -319,10 +325,16 @@ public class RRMConfig { | |||||||
| 		 */ | 		 */ | ||||||
| 		public class ApiServerParams { | 		public class ApiServerParams { | ||||||
| 			/** | 			/** | ||||||
| 			 * The HTTP port to listen on, or -1 to disable | 			 * The HTTP port to listen on for internal traffic, or -1 to disable | ||||||
| 			 * ({@code APISERVERPARAMS_HTTPPORT}) | 			 * ({@code APISERVERPARAMS_INTERNALHTTPPORT}) | ||||||
| 			 */ | 			 */ | ||||||
| 			public int httpPort = 16789; | 			public int internalHttpPort = 16790; | ||||||
|  | 
 | ||||||
|  | 			/** | ||||||
|  | 			 * The HTTP port to listen on for external traffic, or -1 to disable | ||||||
|  | 			 * ({@code APISERVERPARAMS_EXTERNALHTTPPORT}) | ||||||
|  | 			 */ | ||||||
|  | 			public int externalHttpPort = 16789; | ||||||
| 
 | 
 | ||||||
| 			/** | 			/** | ||||||
| 			 * Comma-separated list of all allowed CORS domains (exact match | 			 * Comma-separated list of all allowed CORS domains (exact match | ||||||
| @@ -363,7 +375,7 @@ public class RRMConfig { | |||||||
| 			 * Sync interval, in ms, for owprov venue information etc. | 			 * Sync interval, in ms, for owprov venue information etc. | ||||||
| 			 * ({@code PROVMONITORPARAMS_SYNCINTERVALMS}) | 			 * ({@code PROVMONITORPARAMS_SYNCINTERVALMS}) | ||||||
| 			 */ | 			 */ | ||||||
| 			public int syncIntervalMs = 300000; | 			public int syncIntervalMs = 300000; // 5min | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		/** ProvMonitor parameters. */ | 		/** ProvMonitor parameters. */ | ||||||
| @@ -532,10 +544,16 @@ public class RRMConfig { | |||||||
| 		if ((v = env.get("MODELERPARAMS_WIFISCANBUFFERSIZE")) != null) { | 		if ((v = env.get("MODELERPARAMS_WIFISCANBUFFERSIZE")) != null) { | ||||||
| 			modelerParams.wifiScanBufferSize = Integer.parseInt(v); | 			modelerParams.wifiScanBufferSize = Integer.parseInt(v); | ||||||
| 		} | 		} | ||||||
|  | 		if ((v = env.get("MODELERPARAMS_STATEBUFFERSIZE")) != null) { | ||||||
|  | 			modelerParams.stateBufferSize = Integer.parseInt(v); | ||||||
|  | 		} | ||||||
| 		ModuleConfig.ApiServerParams apiServerParams = | 		ModuleConfig.ApiServerParams apiServerParams = | ||||||
| 			config.moduleConfig.apiServerParams; | 			config.moduleConfig.apiServerParams; | ||||||
| 		if ((v = env.get("APISERVERPARAMS_HTTPPORT")) != null) { | 		if ((v = env.get("APISERVERPARAMS_INTERNALHTTPPORT")) != null) { | ||||||
| 			apiServerParams.httpPort = Integer.parseInt(v); | 			apiServerParams.internalHttpPort = Integer.parseInt(v); | ||||||
|  | 		} | ||||||
|  | 		if ((v = env.get("APISERVERPARAMS_EXTERNALHTTPPORT")) != null) { | ||||||
|  | 			apiServerParams.externalHttpPort = Integer.parseInt(v); | ||||||
| 		} | 		} | ||||||
| 		if ((v = env.get("APISERVERPARAMS_CORSDOMAINLIST")) != null) { | 		if ((v = env.get("APISERVERPARAMS_CORSDOMAINLIST")) != null) { | ||||||
| 			apiServerParams.corsDomainList = v; | 			apiServerParams.corsDomainList = v; | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.util.List; | import java.util.List; | ||||||
| 
 | 
 | ||||||
| @@ -19,9 +19,9 @@ public class RRMSchedule { | |||||||
| 	 * | 	 * | ||||||
| 	 * This field expects a cron-like format as defined by the Quartz Job | 	 * This field expects a cron-like format as defined by the Quartz Job | ||||||
| 	 * Scheduler (CronTrigger): | 	 * Scheduler (CronTrigger): | ||||||
| 	 * https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html | 	 * https://www.quartz-scheduler.org/documentation/quartz-2.4.0/tutorials/crontrigger.html | ||||||
| 	 */ | 	 */ | ||||||
| 	public String cron; | 	public List<String> crons; | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * The list of RRM algorithms to run. | 	 * The list of RRM algorithms to run. | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import java.io.File; | import java.io.File; | ||||||
| import java.io.FileNotFoundException; | import java.io.FileNotFoundException; | ||||||
| @@ -16,6 +16,8 @@ import java.io.InputStream; | |||||||
| import java.io.PrintStream; | import java.io.PrintStream; | ||||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||||
| import java.nio.file.Files; | import java.nio.file.Files; | ||||||
|  | import java.security.MessageDigest; | ||||||
|  | import java.security.NoSuchAlgorithmException; | ||||||
| import java.util.LinkedHashMap; | import java.util.LinkedHashMap; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Scanner; | import java.util.Scanner; | ||||||
| @@ -23,6 +25,8 @@ import java.util.concurrent.ThreadFactory; | |||||||
| import java.util.concurrent.atomic.AtomicInteger; | import java.util.concurrent.atomic.AtomicInteger; | ||||||
| 
 | 
 | ||||||
| import org.json.JSONObject; | import org.json.JSONObject; | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||||
| @@ -31,6 +35,8 @@ import com.google.gson.GsonBuilder; | |||||||
|  * Generic utility methods. |  * Generic utility methods. | ||||||
|  */ |  */ | ||||||
| public class Utils { | public class Utils { | ||||||
|  | 	private static final Logger logger = LoggerFactory.getLogger(Utils.class); | ||||||
|  | 
 | ||||||
| 	/** Hex value array for use in {@link #longToMac(long)}. */ | 	/** Hex value array for use in {@link #longToMac(long)}. */ | ||||||
| 	private static final char[] HEX_VALUES = "0123456789abcdef".toCharArray(); | 	private static final char[] HEX_VALUES = "0123456789abcdef".toCharArray(); | ||||||
| 
 | 
 | ||||||
| @@ -193,4 +199,19 @@ public class Utils { | |||||||
| 	public static <T> T deepCopy(T obj, Class<T> classOfT) { | 	public static <T> T deepCopy(T obj, Class<T> classOfT) { | ||||||
| 		return gson.fromJson(gson.toJson(obj), classOfT); | 		return gson.fromJson(gson.toJson(obj), classOfT); | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	/** Generate the RRM service key. */ | ||||||
|  | 	public static String generateServiceKey( | ||||||
|  | 		RRMConfig.ServiceConfig serviceConfig | ||||||
|  | 	) { | ||||||
|  | 		try { | ||||||
|  | 			MessageDigest sha256 = MessageDigest.getInstance("SHA-256"); | ||||||
|  | 			sha256.update(serviceConfig.publicEndpoint.getBytes()); | ||||||
|  | 			sha256.update(serviceConfig.privateEndpoint.getBytes()); | ||||||
|  | 			return Utils.bytesToHex(sha256.digest()); | ||||||
|  | 		} catch (NoSuchAlgorithmException e) { | ||||||
|  | 			logger.error("Unable to generate service key", e); | ||||||
|  | 			return ""; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| } | } | ||||||
| @@ -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; | package com.facebook.openwifi.rrm; | ||||||
| 
 | 
 | ||||||
| import picocli.CommandLine.IVersionProvider; | import picocli.CommandLine.IVersionProvider; | ||||||
| 
 | 
 | ||||||
| @@ -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.aggregators; | package com.facebook.openwifi.rrm.aggregators; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Aggregates added values into one "aggregate" measure. |  * Aggregates added values into one "aggregate" measure. | ||||||
| @@ -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.aggregators; | package com.facebook.openwifi.rrm.aggregators; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Tracks the mean of all added values. If no values are added, the mean is 0. |  * Tracks the mean of all added values. If no values are added, the mean is 0. | ||||||
| @@ -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.modules; | package com.facebook.openwifi.rrm.modules; | ||||||
| 
 | 
 | ||||||
| import java.net.InetAddress; | import java.net.InetAddress; | ||||||
| import java.net.URI; | import java.net.URI; | ||||||
| @@ -35,31 +35,33 @@ import org.reflections.util.ConfigurationBuilder; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceConfig; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults; | ||||||
| import com.facebook.openwifirrm.DeviceLayeredConfig; | import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult; | ||||||
| import com.facebook.openwifirrm.DeviceTopology; | import com.facebook.openwifi.cloudsdk.models.prov.rrm.Algorithm; | ||||||
| import com.facebook.openwifirrm.RRMAlgorithm; | import com.facebook.openwifi.cloudsdk.models.prov.rrm.Provider; | ||||||
| import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ApiServerParams; | import com.facebook.openwifi.rrm.CustomJettyServerFactory; | ||||||
| import com.facebook.openwifirrm.RRMConfig.ServiceConfig; | import com.facebook.openwifi.rrm.DeviceConfig; | ||||||
| import com.facebook.openwifirrm.Utils.LruCache; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.VersionProvider; | import com.facebook.openwifi.rrm.DeviceLayeredConfig; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer; | import com.facebook.openwifi.rrm.DeviceTopology; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer; | import com.facebook.openwifi.rrm.RRMAlgorithm; | ||||||
| import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer; | import com.facebook.openwifi.rrm.Utils; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC; | import com.facebook.openwifi.rrm.VersionProvider; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC; | import com.facebook.openwifi.rrm.RRMConfig.ServiceConfig; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC; | import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ApiServerParams; | ||||||
| import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer; | import com.facebook.openwifi.rrm.Utils.LruCache; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralClient; | import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; | import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults; | import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult; | import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.rrm.models.Algorithm; | import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.rrm.models.Provider; | import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC; | ||||||
|  | import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||||
| 
 | 
 | ||||||
|  | import org.openjdk.jol.info.GraphLayout; | ||||||
| import io.swagger.v3.core.util.Json; | import io.swagger.v3.core.util.Json; | ||||||
| import io.swagger.v3.core.util.Yaml; | import io.swagger.v3.core.util.Yaml; | ||||||
| import io.swagger.v3.jaxrs2.Reader; | import io.swagger.v3.jaxrs2.Reader; | ||||||
| @@ -81,7 +83,9 @@ import io.swagger.v3.oas.models.OpenAPI; | |||||||
| import spark.Request; | import spark.Request; | ||||||
| import spark.Response; | import spark.Response; | ||||||
| import spark.Route; | import spark.Route; | ||||||
| import spark.Spark; | import spark.Service; | ||||||
|  | import spark.embeddedserver.EmbeddedServers; | ||||||
|  | import spark.embeddedserver.jetty.EmbeddedJettyFactory; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * HTTP API server. |  * HTTP API server. | ||||||
| @@ -110,6 +114,27 @@ public class ApiServer implements Runnable { | |||||||
| 	private static final Logger logger = | 	private static final Logger logger = | ||||||
| 		LoggerFactory.getLogger(ApiServer.class); | 		LoggerFactory.getLogger(ApiServer.class); | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * This is the identifier for the server factory that Spark should use. This | ||||||
|  | 	 * particular identifier points to the custom factory that we register to | ||||||
|  | 	 * enable running multiple ports on one service. | ||||||
|  | 	 * | ||||||
|  | 	 * @see #run() | ||||||
|  | 	 */ | ||||||
|  | 	private static final String SPARK_EMBEDDED_SERVER_IDENTIFIER = | ||||||
|  | 		ApiServer.class.getName(); | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * The Spark service instance. Normally, you would use the static methods on | ||||||
|  | 	 * Spark, but since we need to spin up multiple instances of Spark for testing, | ||||||
|  | 	 * we choose to go with instantiating the service ourselves. There is really no | ||||||
|  | 	 * difference except with the static method, Spark calls ignite and holds a | ||||||
|  | 	 * singleton instance for us. | ||||||
|  | 	 * | ||||||
|  | 	 * @see Spark | ||||||
|  | 	 */ | ||||||
|  | 	private final Service service; | ||||||
|  | 
 | ||||||
| 	/** The module parameters. */ | 	/** The module parameters. */ | ||||||
| 	private final ApiServerParams params; | 	private final ApiServerParams params; | ||||||
| 
 | 
 | ||||||
| @@ -164,9 +189,10 @@ public class ApiServer implements Runnable { | |||||||
| 		UCentralClient client, | 		UCentralClient client, | ||||||
| 		RRMScheduler scheduler | 		RRMScheduler scheduler | ||||||
| 	) { | 	) { | ||||||
|  | 		this.service = Service.ignite(); | ||||||
| 		this.params = params; | 		this.params = params; | ||||||
| 		this.serviceConfig = serviceConfig; | 		this.serviceConfig = serviceConfig; | ||||||
| 		this.serviceKey = UCentralUtils.generateServiceKey(serviceConfig); | 		this.serviceKey = Utils.generateServiceKey(serviceConfig); | ||||||
| 		this.deviceDataManager = deviceDataManager; | 		this.deviceDataManager = deviceDataManager; | ||||||
| 		this.configManager = configManager; | 		this.configManager = configManager; | ||||||
| 		this.modeler = modeler; | 		this.modeler = modeler; | ||||||
| @@ -194,64 +220,117 @@ public class ApiServer implements Runnable { | |||||||
| 		return ret; | 		return ret; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Block until initialization finishes. Just calls the method on the | ||||||
|  | 	 * underlying service. | ||||||
|  | 	 */ | ||||||
|  | 	public void awaitInitialization() { | ||||||
|  | 		service.awaitInitialization(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Override | 	@Override | ||||||
| 	public void run() { | 	public void run() { | ||||||
| 		this.startTimeMs = System.currentTimeMillis(); | 		this.startTimeMs = System.currentTimeMillis(); | ||||||
| 
 | 
 | ||||||
| 		if (params.httpPort == -1) { | 		if (params.internalHttpPort == -1 && params.externalHttpPort == -1) { | ||||||
| 			logger.info("API server is disabled."); | 			logger.info("API server is disabled."); | ||||||
| 			return; | 			return; | ||||||
|  | 		} else if (params.internalHttpPort == -1) { | ||||||
|  | 			logger.info("Internal API server is disabled"); | ||||||
|  | 		} else if (params.externalHttpPort == -1) { | ||||||
|  | 			logger.info("External API server is disabled"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (params.internalHttpPort == params.externalHttpPort) { | ||||||
|  | 			logger.error( | ||||||
|  | 				"Internal and external port cannot be the same - not starting API server" | ||||||
|  | 			); | ||||||
|  | 			return; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		Spark.port(params.httpPort); | 		EmbeddedServers.add( | ||||||
|  | 			SPARK_EMBEDDED_SERVER_IDENTIFIER, | ||||||
|  | 			new EmbeddedJettyFactory( | ||||||
|  | 				new CustomJettyServerFactory( | ||||||
|  | 					params.internalHttpPort, | ||||||
|  | 					params.externalHttpPort | ||||||
|  | 				) | ||||||
|  | 			) | ||||||
|  | 		); | ||||||
|  | 
 | ||||||
|  | 		// use the embedded server factory added above, this is required so that we | ||||||
|  | 		// don't mess up the default factory which can and will be used for | ||||||
|  | 		// additional Spark services in testing | ||||||
|  | 		service.embeddedServerIdentifier(SPARK_EMBEDDED_SERVER_IDENTIFIER); | ||||||
|  | 
 | ||||||
|  | 		// Usually you would call this with an actual port and Spark would spin up a | ||||||
|  | 		// port on it. However, since we're putting our own connectors in so that we | ||||||
|  | 		// can use two ports and Spark has logic to use connectors that already exist | ||||||
|  | 		// so it doesn't matter what port we pass in here as long as it's not one of | ||||||
|  | 		// the actual ports we're using (Spark has some weird logic where it still | ||||||
|  | 		// tries to bind to the port). | ||||||
|  | 		// @see EmbeddedJettyServer | ||||||
|  | 		service.port(0); | ||||||
| 
 | 
 | ||||||
| 		// Configure API docs hosting | 		// Configure API docs hosting | ||||||
| 		Spark.staticFiles.location("/public"); | 		service.staticFiles.location("/public"); | ||||||
| 		Spark.get("/openapi.yaml", this::getOpenApiYaml); | 		service.get("/openapi.yaml", this::getOpenApiYaml); | ||||||
| 		Spark.get("/openapi.json", this::getOpenApiJson); | 		service.get("/openapi.json", this::getOpenApiJson); | ||||||
| 
 | 
 | ||||||
| 		// Install routes | 		// Install routes | ||||||
| 		Spark.before(this::beforeFilter); | 		service.before(this::beforeFilter); | ||||||
| 		Spark.after(this::afterFilter); | 		service.after(this::afterFilter); | ||||||
| 		Spark.options("/*", this::options); | 		service.options("/*", this::options); | ||||||
| 		Spark.get("/api/v1/system", new SystemEndpoint()); | 		service.get("/api/v1/system", new SystemEndpoint()); | ||||||
| 		Spark.post("/api/v1/system", new SetSystemEndpoint()); | 		service.post("/api/v1/system", new SetSystemEndpoint()); | ||||||
| 		Spark.get("/api/v1/provider", new ProviderEndpoint()); | 		service.get("/api/v1/provider", new ProviderEndpoint()); | ||||||
| 		Spark.get("/api/v1/algorithms", new AlgorithmsEndpoint()); | 		service.get("/api/v1/algorithms", new AlgorithmsEndpoint()); | ||||||
| 		Spark.put("/api/v1/runRRM", new RunRRMEndpoint()); | 		service.put("/api/v1/runRRM", new RunRRMEndpoint()); | ||||||
| 		Spark.get("/api/v1/getTopology", new GetTopologyEndpoint()); | 		service.get("/api/v1/getTopology", new GetTopologyEndpoint()); | ||||||
| 		Spark.post("/api/v1/setTopology", new SetTopologyEndpoint()); | 		service.post("/api/v1/setTopology", new SetTopologyEndpoint()); | ||||||
| 		Spark.get( | 		service.get( | ||||||
| 			"/api/v1/getDeviceLayeredConfig", | 			"/api/v1/getDeviceLayeredConfig", | ||||||
| 			new GetDeviceLayeredConfigEndpoint() | 			new GetDeviceLayeredConfigEndpoint() | ||||||
| 		); | 		); | ||||||
| 		Spark.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint()); | 		service.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint()); | ||||||
| 		Spark.post( | 		service.post( | ||||||
| 			"/api/v1/setDeviceNetworkConfig", | 			"/api/v1/setDeviceNetworkConfig", | ||||||
| 			new SetDeviceNetworkConfigEndpoint() | 			new SetDeviceNetworkConfigEndpoint() | ||||||
| 		); | 		); | ||||||
| 		Spark.post( | 		service.post( | ||||||
| 			"/api/v1/setDeviceZoneConfig", | 			"/api/v1/setDeviceZoneConfig", | ||||||
| 			new SetDeviceZoneConfigEndpoint() | 			new SetDeviceZoneConfigEndpoint() | ||||||
| 		); | 		); | ||||||
| 		Spark.post( | 		service.post( | ||||||
| 			"/api/v1/setDeviceApConfig", | 			"/api/v1/setDeviceApConfig", | ||||||
| 			new SetDeviceApConfigEndpoint() | 			new SetDeviceApConfigEndpoint() | ||||||
| 		); | 		); | ||||||
| 		Spark.post( | 		service.post( | ||||||
| 			"/api/v1/modifyDeviceApConfig", | 			"/api/v1/modifyDeviceApConfig", | ||||||
| 			new ModifyDeviceApConfigEndpoint() | 			new ModifyDeviceApConfigEndpoint() | ||||||
| 		); | 		); | ||||||
| 		Spark.get("/api/v1/currentModel", new GetCurrentModelEndpoint()); | 		service.get("/api/v1/currentModel", new GetCurrentModelEndpoint()); | ||||||
| 		Spark.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint()); | 		service.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint()); | ||||||
| 		Spark.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint()); | 		service.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint()); | ||||||
|  | 		service.get("/api/v1/memory", new MemoryEndpoint(this)); | ||||||
| 
 | 
 | ||||||
| 		logger.info("API server listening on HTTP port {}", params.httpPort); | 		logger.info( | ||||||
|  | 			"API server listening for HTTP internal on port {} and external on port {}", | ||||||
|  | 			params.internalHttpPort, | ||||||
|  | 			params.externalHttpPort | ||||||
|  | 		); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Stop the server. */ | 	/** Stop the server. */ | ||||||
| 	public void shutdown() { | 	public void shutdown() { | ||||||
| 		Spark.stop(); | 		service.stop(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Block until stop finishes. Just calls the method on the underlying service. | ||||||
|  | 	 */ | ||||||
|  | 	public void awaitStop() { | ||||||
|  | 		service.awaitStop(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Reconstructs a URL. */ | 	/** Reconstructs a URL. */ | ||||||
| @@ -269,16 +348,18 @@ public class ApiServer implements Runnable { | |||||||
| 	 * HTTP 403 response and return false. | 	 * HTTP 403 response and return false. | ||||||
| 	 */ | 	 */ | ||||||
| 	private boolean performOpenWifiAuth(Request request, Response response) { | 	private boolean performOpenWifiAuth(Request request, Response response) { | ||||||
| 		// TODO check if request came from internal endpoint | 		int port = request.port(); | ||||||
| 		boolean internal = true; | 		boolean internal = port > 0 && port == params.internalHttpPort; | ||||||
|  | 		if (internal) { | ||||||
| 			String internalName = request.headers("X-INTERNAL-NAME"); | 			String internalName = request.headers("X-INTERNAL-NAME"); | ||||||
| 		if (internal && internalName != null) { | 			if (internalName != null) { | ||||||
| 				// Internal request, validate "X-API-KEY" | 				// Internal request, validate "X-API-KEY" | ||||||
| 				String apiKey = request.headers("X-API-KEY"); | 				String apiKey = request.headers("X-API-KEY"); | ||||||
| 				if (apiKey != null && apiKey.equals(serviceKey)) { | 				if (apiKey != null && apiKey.equals(serviceKey)) { | ||||||
| 					// auth success | 					// auth success | ||||||
| 					return true; | 					return true; | ||||||
| 				} | 				} | ||||||
|  | 			} | ||||||
| 		} else { | 		} else { | ||||||
| 			// External request, validate token: | 			// External request, validate token: | ||||||
| 			//   Authorization: Bearer <token> | 			//   Authorization: Bearer <token> | ||||||
| @@ -297,17 +378,18 @@ public class ApiServer implements Runnable { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// auth failure | 		// auth failure | ||||||
| 		Spark.halt(403, "Forbidden"); | 		service.halt(403, "Forbidden"); | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * Validate an OpenWiFi token (external), caching successful lookups. | 	 * Validate an OpenWiFi token (external), caching successful lookups. This will | ||||||
|  | 	 * validate a USER token - subscriber token won't work and will fail (plus only | ||||||
|  | 	 * users should be dealing with RRM). | ||||||
| 	 * @return true if token is valid | 	 * @return true if token is valid | ||||||
| 	 */ | 	 */ | ||||||
| 	private boolean validateOpenWifiToken(String token) { | 	private boolean validateOpenWifiToken(String token) { | ||||||
| 		// The below only checks /api/v1/validateToken and caches it as necessary. | 		// The below only checks /api/v1/validateToken and caches it as necessary. | ||||||
| 		// TODO - /api/v1/validateSubToken still has to be implemented. |  | ||||||
| 		Long expiry = tokenCache.get(token); | 		Long expiry = tokenCache.get(token); | ||||||
| 		if (expiry == null) { | 		if (expiry == null) { | ||||||
| 			TokenValidationResult result = client.validateToken(token); | 			TokenValidationResult result = client.validateToken(token); | ||||||
| @@ -324,10 +406,11 @@ public class ApiServer implements Runnable { | |||||||
| 	private void beforeFilter(Request request, Response response) { | 	private void beforeFilter(Request request, Response response) { | ||||||
| 		// Log requests | 		// Log requests | ||||||
| 		logger.debug( | 		logger.debug( | ||||||
| 			"[{}] {} {}", | 			"[{}] {} {} on port {}", | ||||||
| 			request.ip(), | 			request.ip(), | ||||||
| 			request.requestMethod(), | 			request.requestMethod(), | ||||||
| 			getFullUrl(request.pathInfo(), request.queryString()) | 			getFullUrl(request.pathInfo(), request.queryString()), | ||||||
|  | 			request.port() | ||||||
| 		); | 		); | ||||||
| 
 | 
 | ||||||
| 		// Remove "Server: Jetty" header | 		// Remove "Server: Jetty" header | ||||||
| @@ -711,7 +794,8 @@ public class ApiServer implements Runnable { | |||||||
| 				modeler, | 				modeler, | ||||||
| 				venue, | 				venue, | ||||||
| 				mock, | 				mock, | ||||||
| 				true /* allowDefaultMode */ | 				true, /* allowDefaultMode */ | ||||||
|  | 				true /* updateImmediately */ | ||||||
| 			); | 			); | ||||||
| 			if (result.error != null) { | 			if (result.error != null) { | ||||||
| 				response.status(400); | 				response.status(400); | ||||||
| @@ -917,7 +1001,7 @@ public class ApiServer implements Runnable { | |||||||
| 				DeviceConfig networkConfig = | 				DeviceConfig networkConfig = | ||||||
| 					gson.fromJson(request.body(), DeviceConfig.class); | 					gson.fromJson(request.body(), DeviceConfig.class); | ||||||
| 				deviceDataManager.setDeviceNetworkConfig(networkConfig); | 				deviceDataManager.setDeviceNetworkConfig(networkConfig); | ||||||
| 				configManager.wakeUp(); | 				configManager.queueAllZonesAndWakeUp(); | ||||||
| 
 | 
 | ||||||
| 				// Revalidate data model | 				// Revalidate data model | ||||||
| 				modeler.revalidate(); | 				modeler.revalidate(); | ||||||
| @@ -981,7 +1065,7 @@ public class ApiServer implements Runnable { | |||||||
| 				DeviceConfig zoneConfig = | 				DeviceConfig zoneConfig = | ||||||
| 					gson.fromJson(request.body(), DeviceConfig.class); | 					gson.fromJson(request.body(), DeviceConfig.class); | ||||||
| 				deviceDataManager.setDeviceZoneConfig(zone, zoneConfig); | 				deviceDataManager.setDeviceZoneConfig(zone, zoneConfig); | ||||||
| 				configManager.wakeUp(); | 				configManager.queueZoneAndWakeUp(zone); | ||||||
| 
 | 
 | ||||||
| 				// Revalidate data model | 				// Revalidate data model | ||||||
| 				modeler.revalidate(); | 				modeler.revalidate(); | ||||||
| @@ -1044,7 +1128,10 @@ public class ApiServer implements Runnable { | |||||||
| 				DeviceConfig apConfig = | 				DeviceConfig apConfig = | ||||||
| 					gson.fromJson(request.body(), DeviceConfig.class); | 					gson.fromJson(request.body(), DeviceConfig.class); | ||||||
| 				deviceDataManager.setDeviceApConfig(serialNumber, apConfig); | 				deviceDataManager.setDeviceApConfig(serialNumber, apConfig); | ||||||
| 				configManager.wakeUp(); | 				// TODO enable updates to device(s), not just the entire zone | ||||||
|  | 				final String zone = | ||||||
|  | 					deviceDataManager.getDeviceZone(serialNumber); | ||||||
|  | 				configManager.queueZoneAndWakeUp(zone); | ||||||
| 
 | 
 | ||||||
| 				// Revalidate data model | 				// Revalidate data model | ||||||
| 				modeler.revalidate(); | 				modeler.revalidate(); | ||||||
| @@ -1117,7 +1204,10 @@ public class ApiServer implements Runnable { | |||||||
| 						.computeIfAbsent(serialNumber, k -> new DeviceConfig()) | 						.computeIfAbsent(serialNumber, k -> new DeviceConfig()) | ||||||
| 						.apply(apConfig); | 						.apply(apConfig); | ||||||
| 				}); | 				}); | ||||||
| 				configManager.wakeUp(); | 				final String zone = | ||||||
|  | 					deviceDataManager.getDeviceZone(serialNumber); | ||||||
|  | 				// TODO enable updates to device(s), not just the entire zone | ||||||
|  | 				configManager.queueZoneAndWakeUp(zone); | ||||||
| 
 | 
 | ||||||
| 				// Revalidate data model | 				// Revalidate data model | ||||||
| 				modeler.revalidate(); | 				modeler.revalidate(); | ||||||
| @@ -1260,7 +1350,8 @@ public class ApiServer implements Runnable { | |||||||
| 				modeler, | 				modeler, | ||||||
| 				zone, | 				zone, | ||||||
| 				dryRun, | 				dryRun, | ||||||
| 				false /* allowDefaultMode */ | 				false, /* allowDefaultMode */ | ||||||
|  | 				true /* updateImmediately */ | ||||||
| 			); | 			); | ||||||
| 			if (result.error != null) { | 			if (result.error != null) { | ||||||
| 				response.status(400); | 				response.status(400); | ||||||
| @@ -1271,6 +1362,91 @@ public class ApiServer implements Runnable { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Path("/api/v1/memory") | ||||||
|  | 	public class MemoryEndpoint implements Route { | ||||||
|  | 		private final ApiServer apiServer; | ||||||
|  | 
 | ||||||
|  | 		MemoryEndpoint(ApiServer server) { | ||||||
|  | 			this.apiServer = server; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		@Override | ||||||
|  | 		public String handle(Request request, Response response) { | ||||||
|  | 			String type = request.queryParamOrDefault("type", ""); | ||||||
|  | 			String view = request.queryParamOrDefault("view", "footprint"); | ||||||
|  | 
 | ||||||
|  | 			java.util.function.Function<GraphLayout, String> fn = (GraphLayout graph) -> { | ||||||
|  | 				return view.equals("footprint") ? graph.toFootprint() : graph.toPrintable(); | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
|  | 			String result; | ||||||
|  | 			switch (type) { | ||||||
|  | 				case "modeler.dataModel": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.dataModel.latestWifiScans": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestWifiScans)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.dataModel.latestStates": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestStates)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.dataModel.latestDeviceStatusRadios": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestDeviceStatusRadios)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.deviceDataManager": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.deviceDataManager.topology": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager.topology)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.deviceDataManager.deviceLayeredConfig": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager.deviceLayeredConfig)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.deviceDataManager.cachedDeviceConfigs": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.deviceDataManager.cachedDeviceConfigs)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "modeler.dataModel.latestDeviceCapabilities": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler.dataModel.latestDeviceCapabilities)); | ||||||
|  | 
 | ||||||
|  | 				case "modeler": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.modeler)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "configManager.deviceDataMap": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.configManager.deviceDataMap)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "configManager": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.configManager)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "scheduler": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.scheduler)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "deviceDataManager": | ||||||
|  | 					result = fn.apply(GraphLayout.parseInstance(apiServer.deviceDataManager)); | ||||||
|  | 					break; | ||||||
|  | 
 | ||||||
|  | 				case "": | ||||||
|  | 				default: | ||||||
|  | 					result = GraphLayout.parseInstance(apiServer).toFootprint(); | ||||||
|  | 					break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			logger.info("MEMORY RESPONSE: \n{}", result); | ||||||
|  | 			return result; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Path("/api/v1/optimizeTxPower") | 	@Path("/api/v1/optimizeTxPower") | ||||||
| 	public class OptimizeTxPowerEndpoint implements Route { | 	public class OptimizeTxPowerEndpoint implements Route { | ||||||
| 		// Hack for use in @ApiResponse -> @Content -> @Schema | 		// Hack for use in @ApiResponse -> @Content -> @Schema | ||||||
| @@ -1371,7 +1547,8 @@ public class ApiServer implements Runnable { | |||||||
| 				modeler, | 				modeler, | ||||||
| 				zone, | 				zone, | ||||||
| 				dryRun, | 				dryRun, | ||||||
| 				false /* allowDefaultMode */ | 				false, /* allowDefaultMode */ | ||||||
|  | 				true /* updateImmediately */ | ||||||
| 			); | 			); | ||||||
| 			if (result.error != null) { | 			if (result.error != null) { | ||||||
| 				response.status(400); | 				response.status(400); | ||||||
| @@ -6,25 +6,28 @@ | |||||||
|  * LICENSE file in the root directory of this source tree. |  * LICENSE file in the root directory of this source tree. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package com.facebook.openwifirrm.modules; | package com.facebook.openwifi.rrm.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; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceConfig; | import com.facebook.openwifi.cloudsdk.UCentralApConfiguration; | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
| import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ConfigManagerParams; | import com.facebook.openwifi.cloudsdk.UCentralUtils; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralApConfiguration; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralClient; | import com.facebook.openwifi.rrm.DeviceConfig; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus; | import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ConfigManagerParams; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Device configuration manager module. |  * Device configuration manager module. | ||||||
| @@ -43,7 +46,7 @@ public class ConfigManager implements Runnable { | |||||||
| 	private final UCentralClient client; | 	private final UCentralClient client; | ||||||
| 
 | 
 | ||||||
| 	/** Runtime per-device data. */ | 	/** Runtime per-device data. */ | ||||||
| 	private class DeviceData { | 	public class DeviceData { | ||||||
| 		/** Last received device config. */ | 		/** Last received device config. */ | ||||||
| 		public UCentralApConfiguration config; | 		public UCentralApConfiguration config; | ||||||
| 
 | 
 | ||||||
| @@ -52,7 +55,7 @@ public class ConfigManager implements Runnable { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Map from device serial number to runtime data. */ | 	/** Map from device serial number to runtime data. */ | ||||||
| 	private Map<String, DeviceData> deviceDataMap = new TreeMap<>(); | 	public Map<String, DeviceData> deviceDataMap = new TreeMap<>(); | ||||||
| 
 | 
 | ||||||
| 	/** The main thread reference (i.e. where {@link #run()} is invoked). */ | 	/** The main thread reference (i.e. where {@link #run()} is invoked). */ | ||||||
| 	private Thread mainThread; | 	private Thread mainThread; | ||||||
| @@ -63,8 +66,11 @@ public class ConfigManager implements Runnable { | |||||||
| 	/** Is the main thread sleeping? */ | 	/** Is the main thread sleeping? */ | ||||||
| 	private final AtomicBoolean sleepingFlag = new AtomicBoolean(false); | 	private final AtomicBoolean sleepingFlag = new AtomicBoolean(false); | ||||||
| 
 | 
 | ||||||
| 	/** Was a manual config update requested? */ | 	/** | ||||||
| 	private final AtomicBoolean eventFlag = new AtomicBoolean(false); | 	 * Thread-safe set of zones for which manual config updates have been | ||||||
|  | 	 * requested. | ||||||
|  | 	 */ | ||||||
|  | 	private Set<String> zonesToUpdate = ConcurrentHashMap.newKeySet(); | ||||||
| 
 | 
 | ||||||
| 	/** Config listener interface. */ | 	/** Config listener interface. */ | ||||||
| 	public interface ConfigListener { | 	public interface ConfigListener { | ||||||
| @@ -180,7 +186,10 @@ public class ConfigManager implements Runnable { | |||||||
| 		List<String> devicesNeedingUpdate = new ArrayList<>(); | 		List<String> devicesNeedingUpdate = new ArrayList<>(); | ||||||
| 		final long CONFIG_DEBOUNCE_INTERVAL_NS = | 		final long CONFIG_DEBOUNCE_INTERVAL_NS = | ||||||
| 			params.configDebounceIntervalSec * 1_000_000_000L; | 			params.configDebounceIntervalSec * 1_000_000_000L; | ||||||
| 		final boolean isEvent = eventFlag.getAndSet(false); | 		Set<String> zonesToUpdateCopy = new HashSet<>(zonesToUpdate); | ||||||
|  | 		// use removeAll() instead of clear() in case items are added between | ||||||
|  | 		// the previous line and the following line | ||||||
|  | 		zonesToUpdate.removeAll(zonesToUpdateCopy); | ||||||
| 		for (DeviceWithStatus device : devices) { | 		for (DeviceWithStatus device : devices) { | ||||||
| 			// Update config structure | 			// Update config structure | ||||||
| 			DeviceData data = deviceDataMap.computeIfAbsent( | 			DeviceData data = deviceDataMap.computeIfAbsent( | ||||||
| @@ -201,11 +210,13 @@ public class ConfigManager implements Runnable { | |||||||
| 			for (ConfigListener listener : configListeners.values()) { | 			for (ConfigListener listener : configListeners.values()) { | ||||||
| 				listener.receiveDeviceConfig(device.serialNumber, data.config); | 				listener.receiveDeviceConfig(device.serialNumber, data.config); | ||||||
| 			} | 			} | ||||||
| 
 | 			// Check if there are requested updates for this zone | ||||||
| 			// Check event flag | 			String deviceZone = | ||||||
|  | 				deviceDataManager.getDeviceZone(device.serialNumber); | ||||||
|  | 			boolean isEvent = zonesToUpdateCopy.contains(deviceZone); | ||||||
| 			if (params.configOnEventOnly && !isEvent) { | 			if (params.configOnEventOnly && !isEvent) { | ||||||
| 				logger.debug( | 				logger.debug( | ||||||
| 					"Skipping config for {} (event flag not set)", | 					"Skipping config for {} (zone not marked for updates)", | ||||||
| 					device.serialNumber | 					device.serialNumber | ||||||
| 				); | 				); | ||||||
| 				continue; | 				continue; | ||||||
| @@ -251,15 +262,16 @@ public class ConfigManager implements Runnable { | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		final boolean shouldUpdate = !zonesToUpdateCopy.isEmpty(); | ||||||
| 		// Send config changes to devices | 		// Send config changes to devices | ||||||
| 		if (!params.configEnabled) { | 		if (!params.configEnabled) { | ||||||
| 			logger.trace("Config changes are disabled."); | 			logger.trace("Config changes are disabled."); | ||||||
| 		} else if (devicesNeedingUpdate.isEmpty()) { | 		} else if (devicesNeedingUpdate.isEmpty()) { | ||||||
| 			logger.debug("No device configs to send."); | 			logger.debug("No device configs to send."); | ||||||
| 		} else if (params.configOnEventOnly && !isEvent) { | 		} else if (params.configOnEventOnly && !shouldUpdate) { | ||||||
| 			// shouldn't happen | 			// shouldn't happen | ||||||
| 			logger.error( | 			logger.error( | ||||||
| 				"ERROR!! {} device(s) queued for config update, but event flag not set", | 				"ERROR!! {} device(s) queued for config update, but no zones queued for update.", | ||||||
| 				devicesNeedingUpdate.size() | 				devicesNeedingUpdate.size() | ||||||
| 			); | 			); | ||||||
| 		} else { | 		} else { | ||||||
| @@ -364,9 +376,38 @@ public class ConfigManager implements Runnable { | |||||||
| 		return (configListeners.remove(id) != null); | 		return (configListeners.remove(id) != null); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Interrupt the main thread, possibly triggering an update immediately. */ | 	/** | ||||||
| 	public void wakeUp() { | 	 * Mark the zone to be updated, then interrupt the main thread to possibly | ||||||
| 		eventFlag.set(true); | 	 * trigger an update immediately. | ||||||
|  | 	 * | ||||||
|  | 	 * @param zone non-null zone (i.e., venue) | ||||||
|  | 	 */ | ||||||
|  | 	public void queueZoneAndWakeUp(String zone) { | ||||||
|  | 		if (zone == null) { | ||||||
|  | 			logger.debug("Zone to queue must be a non-null String."); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		zonesToUpdate.add(zone); | ||||||
|  | 		wakeUp(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Track all zones to be updated, then interrupt the main thread to possibly | ||||||
|  | 	 * trigger an update immediately. | ||||||
|  | 	 */ | ||||||
|  | 	public void queueAllZonesAndWakeUp() { | ||||||
|  | 		/* | ||||||
|  | 		 * Note, addAll is not atomic, but that is ok. This just means that it | ||||||
|  | 		 * is possible that some zones may get updated now by the main thread | ||||||
|  | 		 * while others get updated either when the main thread is woken up or | ||||||
|  | 		 * the next time the main thread does its periodic update. | ||||||
|  | 		 */ | ||||||
|  | 		zonesToUpdate.addAll(deviceDataManager.getZones()); | ||||||
|  | 		wakeUp(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** Interrupt the main thread to possibly trigger an update immediately. */ | ||||||
|  | 	private void wakeUp() { | ||||||
| 		if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) { | 		if (mainThread != null && mainThread.isAlive() && sleepingFlag.get()) { | ||||||
| 			wakeupFlag.set(true); | 			wakeupFlag.set(true); | ||||||
| 			mainThread.interrupt(); | 			mainThread.interrupt(); | ||||||
| @@ -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.modules; | package com.facebook.openwifi.rrm.modules; | ||||||
| 
 | 
 | ||||||
| import java.sql.SQLException; | import java.sql.SQLException; | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| @@ -23,22 +23,22 @@ import java.util.stream.Collectors; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceConfig; | import com.facebook.openwifi.cloudsdk.UCentralApConfiguration; | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
| import com.facebook.openwifirrm.RRMConfig.ModuleConfig.DataCollectorParams; | import com.facebook.openwifi.cloudsdk.UCentralUtils; | ||||||
| import com.facebook.openwifirrm.Utils; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| import com.facebook.openwifirrm.mysql.DatabaseManager; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer; | ||||||
| import com.facebook.openwifirrm.mysql.StateRecord; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralApConfiguration; | import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralClient; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord; | import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; | import com.facebook.openwifi.rrm.DeviceConfig; | ||||||
| import com.facebook.openwifirrm.ucentral.WifiScanEntry; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo; | import com.facebook.openwifi.rrm.Utils; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities; | import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.DataCollectorParams; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus; | import com.facebook.openwifi.rrm.mysql.DatabaseManager; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent; | import com.facebook.openwifi.rrm.mysql.StateRecord; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.JsonArray; | import com.google.gson.JsonArray; | ||||||
| import com.google.gson.JsonElement; | import com.google.gson.JsonElement; | ||||||
| @@ -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.modules; | package com.facebook.openwifi.rrm.modules; | ||||||
| 
 | 
 | ||||||
| import java.util.LinkedList; | import java.util.LinkedList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -20,21 +20,21 @@ import java.util.concurrent.LinkedBlockingQueue; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceConfig; | import com.facebook.openwifi.cloudsdk.UCentralApConfiguration; | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
| import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ModelerParams; | import com.facebook.openwifi.cloudsdk.UCentralUtils; | ||||||
| import com.facebook.openwifirrm.Utils; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralApConfiguration; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralClient; | import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer; | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; | import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus; | ||||||
| import com.facebook.openwifirrm.ucentral.WifiScanEntry; | import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities; | import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus; | import com.facebook.openwifi.rrm.DeviceConfig; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords; | import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ModelerParams; | ||||||
| import com.facebook.openwifirrm.ucentral.models.State; | import com.facebook.openwifi.rrm.Utils; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.JsonArray; | import com.google.gson.JsonArray; | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| @@ -50,7 +50,7 @@ public class Modeler implements Runnable { | |||||||
| 	private final ModelerParams params; | 	private final ModelerParams params; | ||||||
| 
 | 
 | ||||||
| 	/** The device data manager. */ | 	/** The device data manager. */ | ||||||
| 	private final DeviceDataManager deviceDataManager; | 	public final DeviceDataManager deviceDataManager; | ||||||
| 
 | 
 | ||||||
| 	/** The uCentral client instance. */ | 	/** The uCentral client instance. */ | ||||||
| 	private final UCentralClient client; | 	private final UCentralClient client; | ||||||
| @@ -74,7 +74,7 @@ public class Modeler implements Runnable { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** The blocking data queue. */ | 	/** The blocking data queue. */ | ||||||
| 	private final BlockingQueue<InputData> dataQueue = | 	public final BlockingQueue<InputData> dataQueue = | ||||||
| 		new LinkedBlockingQueue<>(); | 		new LinkedBlockingQueue<>(); | ||||||
| 
 | 
 | ||||||
| 	/** Data model representation. */ | 	/** Data model representation. */ | ||||||
| @@ -93,8 +93,9 @@ public class Modeler implements Runnable { | |||||||
| 		public Map<String, List<List<WifiScanEntry>>> latestWifiScans = | 		public Map<String, List<List<WifiScanEntry>>> latestWifiScans = | ||||||
| 			new ConcurrentHashMap<>(); | 			new ConcurrentHashMap<>(); | ||||||
| 
 | 
 | ||||||
| 		/** List of latest state per device. */ | 		/** List of latest states per device. */ | ||||||
| 		public Map<String, State> latestState = new ConcurrentHashMap<>(); | 		public Map<String, List<State>> latestStates = | ||||||
|  | 			new ConcurrentHashMap<>(); | ||||||
| 
 | 
 | ||||||
| 		/** List of radio info per device. */ | 		/** List of radio info per device. */ | ||||||
| 		public Map<String, JsonArray> latestDeviceStatusRadios = | 		public Map<String, JsonArray> latestDeviceStatusRadios = | ||||||
| @@ -267,7 +268,10 @@ public class Modeler implements Runnable { | |||||||
| 			if (state != null) { | 			if (state != null) { | ||||||
| 				try { | 				try { | ||||||
| 					State stateModel = gson.fromJson(state, State.class); | 					State stateModel = gson.fromJson(state, State.class); | ||||||
| 					dataModel.latestState.put(device.serialNumber, stateModel); | 					dataModel.latestStates.computeIfAbsent( | ||||||
|  | 						device.serialNumber, | ||||||
|  | 						k -> new LinkedList<>() | ||||||
|  | 					).add(stateModel); | ||||||
| 					logger.debug( | 					logger.debug( | ||||||
| 						"Device {}: added initial state from uCentralGw", | 						"Device {}: added initial state from uCentralGw", | ||||||
| 						device.serialNumber | 						device.serialNumber | ||||||
| @@ -299,8 +303,17 @@ public class Modeler implements Runnable { | |||||||
| 				if (state != null) { | 				if (state != null) { | ||||||
| 					try { | 					try { | ||||||
| 						State stateModel = gson.fromJson(state, State.class); | 						State stateModel = gson.fromJson(state, State.class); | ||||||
| 						dataModel.latestState | 						List<State> latestStatesList = dataModel.latestStates | ||||||
| 							.put(record.serialNumber, stateModel); | 							.computeIfAbsent( | ||||||
|  | 								record.serialNumber, | ||||||
|  | 								k -> new LinkedList<>() | ||||||
|  | 							); | ||||||
|  | 						while ( | ||||||
|  | 							latestStatesList.size() >= params.stateBufferSize | ||||||
|  | 						) { | ||||||
|  | 							latestStatesList.remove(0); | ||||||
|  | 						} | ||||||
|  | 						latestStatesList.add(stateModel); | ||||||
| 						stateUpdates.add(record.serialNumber); | 						stateUpdates.add(record.serialNumber); | ||||||
| 					} catch (JsonSyntaxException e) { | 					} catch (JsonSyntaxException e) { | ||||||
| 						logger.error( | 						logger.error( | ||||||
| @@ -423,7 +436,7 @@ public class Modeler implements Runnable { | |||||||
| 			logger.debug("Removed some wifi scan entries from data model"); | 			logger.debug("Removed some wifi scan entries from data model"); | ||||||
| 		} | 		} | ||||||
| 		if ( | 		if ( | ||||||
| 			dataModel.latestState.entrySet() | 			dataModel.latestStates.entrySet() | ||||||
| 				.removeIf(e -> !isRRMEnabled(e.getKey())) | 				.removeIf(e -> !isRRMEnabled(e.getKey())) | ||||||
| 		) { | 		) { | ||||||
| 			logger.debug("Removed some state entries from data model"); | 			logger.debug("Removed some state entries from data model"); | ||||||
| @@ -6,24 +6,33 @@ | |||||||
|  * LICENSE file in the root directory of this source tree. |  * LICENSE file in the root directory of this source tree. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package com.facebook.openwifirrm.modules; | package com.facebook.openwifi.rrm.modules; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.HashMap; | import java.util.HashMap; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.Map; | import java.util.Map; | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
|  | import java.util.concurrent.ConcurrentHashMap; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
| 
 | 
 | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.aggregators.Aggregator; | import com.facebook.openwifi.cloudsdk.AggregatedState; | ||||||
| import com.facebook.openwifirrm.aggregators.MeanAggregator; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| import com.facebook.openwifirrm.modules.Modeler.DataModel; | import com.facebook.openwifi.cloudsdk.ies.HTOperation; | ||||||
| import com.facebook.openwifirrm.ucentral.WifiScanEntry; | import com.facebook.openwifi.cloudsdk.ies.VHTOperation; | ||||||
| import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement; | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
| import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement; | import com.facebook.openwifi.cloudsdk.models.ap.State.Interface; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association; | ||||||
|  | import com.facebook.openwifi.rrm.aggregators.Aggregator; | ||||||
|  | import com.facebook.openwifi.rrm.aggregators.MeanAggregator; | ||||||
|  | import com.facebook.openwifi.rrm.modules.Modeler.DataModel; | ||||||
|  | import com.google.gson.JsonArray; | ||||||
|  | import com.google.gson.JsonElement; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Modeler utilities. |  * Modeler utilities. | ||||||
| @@ -239,9 +248,9 @@ public class ModelerUtils { | |||||||
| 		return Objects.equals(entry1.bssid, entry2.bssid) && | 		return Objects.equals(entry1.bssid, entry2.bssid) && | ||||||
| 			entry1.frequency == entry2.frequency && | 			entry1.frequency == entry2.frequency && | ||||||
| 			entry1.channel == entry2.channel && | 			entry1.channel == entry2.channel && | ||||||
| 			HTOperationElement | 			HTOperation | ||||||
| 				.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) && | 				.matchesHtForAggregation(entry1.ht_oper, entry2.ht_oper) && | ||||||
| 			VHTOperationElement | 			VHTOperation | ||||||
| 				.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper); | 				.matchesVhtForAggregation(entry1.vht_oper, entry2.vht_oper); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -291,7 +300,7 @@ public class ModelerUtils { | |||||||
| 	/** | 	/** | ||||||
| 	 * Compute aggregated wifiscans using a given reference time. | 	 * Compute aggregated wifiscans using a given reference time. | ||||||
| 	 * | 	 * | ||||||
| 	 * @see #getAggregatedWifiScans(com.facebook.openwifirrm.modules.Modeler.DataModel, | 	 * @see #getAggregatedWifiScans(com.facebook.openwifi.rrm.modules.Modeler.DataModel, | ||||||
| 	 *      long, Aggregator) | 	 *      long, Aggregator) | ||||||
| 	 */ | 	 */ | ||||||
| 	public static Map<String, Map<String, WifiScanEntry>> getAggregatedWifiScans( | 	public static Map<String, Map<String, WifiScanEntry>> getAggregatedWifiScans( | ||||||
| @@ -381,4 +390,196 @@ public class ModelerUtils { | |||||||
| 		} | 		} | ||||||
| 		return aggregatedWifiScans; | 		return aggregatedWifiScans; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * This method converts the input State info to an AggregatedState | ||||||
|  | 	 * and adds it to the bssidToAggregatedStates map. If the bssid/station | ||||||
|  | 	 * of the input State does not exist in the map, create a new | ||||||
|  | 	 * AggregatedState list. If the bssid/station of the input State exists, | ||||||
|  | 	 * then convert State to AggregatedState and check if there exits an | ||||||
|  | 	 * AggregatedState of the same radio. If there does, append the value | ||||||
|  | 	 * of aggregation field to the existing AggregatedState, if not, create | ||||||
|  | 	 * a new AggregatedState and add it to the list. | ||||||
|  | 	 * | ||||||
|  | 	 * @param bssidToAggregatedStates map from bssid/station to a list of AggregatedState | ||||||
|  | 	 * @param state the state that is to be added | ||||||
|  | 	 */ | ||||||
|  | 	static void addStateToAggregation( | ||||||
|  | 		Map<String, List<AggregatedState>> bssidToAggregatedStates, | ||||||
|  | 		State state | ||||||
|  | 	) { | ||||||
|  | 		for (Interface stateInterface : state.interfaces) { | ||||||
|  | 			if (stateInterface.ssids == null) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			for (SSID ssid : stateInterface.ssids) { | ||||||
|  | 				Map<String, Integer> radioInfo = new HashMap<>(); | ||||||
|  | 				radioInfo.put("channel", ssid.radio.get("channel").getAsInt()); | ||||||
|  | 				radioInfo.put( | ||||||
|  | 					"channel_width", | ||||||
|  | 					ssid.radio.get("channel_width").getAsInt() | ||||||
|  | 				); | ||||||
|  | 				radioInfo | ||||||
|  | 					.put("tx_power", ssid.radio.get("tx_power").getAsInt()); | ||||||
|  | 
 | ||||||
|  | 				for (Association association : ssid.associations) { | ||||||
|  | 					if (association == null) { | ||||||
|  | 						continue; | ||||||
|  | 					} | ||||||
|  | 					String key = getBssidStationKeyPair( | ||||||
|  | 						association.bssid, | ||||||
|  | 						association.station | ||||||
|  | 					); | ||||||
|  | 					List<AggregatedState> aggregatedStates = | ||||||
|  | 						bssidToAggregatedStates | ||||||
|  | 							.computeIfAbsent(key, k -> new ArrayList<>()); | ||||||
|  | 					AggregatedState aggState = | ||||||
|  | 						new AggregatedState(association, radioInfo); | ||||||
|  | 
 | ||||||
|  | 					/** | ||||||
|  | 					 * Indicate if the aggState can be merged into some old AggregatedState. | ||||||
|  | 					 * If true, it will be merged by appending its mcs/rssi field to the old one. | ||||||
|  | 					 * If false, it will be added to the list aggregatedStates. | ||||||
|  | 					*/ | ||||||
|  | 					boolean canBeMergedToOldAggregatedState = false; | ||||||
|  | 					for ( | ||||||
|  | 						AggregatedState oldAggregatedState : aggregatedStates | ||||||
|  | 					) { | ||||||
|  | 						if (oldAggregatedState.add(aggState)) { | ||||||
|  | 							canBeMergedToOldAggregatedState = true; | ||||||
|  | 							break; | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					if (!canBeMergedToOldAggregatedState) { | ||||||
|  | 						aggregatedStates.add(aggState); | ||||||
|  | 					} | ||||||
|  | 					bssidToAggregatedStates.put(key, aggregatedStates); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * This method aggregates States by bssid/station key pair and radio info. | ||||||
|  | 	 * if two States of the same bssid/station match in channel, channel width and tx_power | ||||||
|  | 	 * need to be aggregated to one {@code AggregatedState}. Currently only mcs and | ||||||
|  | 	 * rssi fields are being aggregated. They are of {@code List<Integer>} type in AggregatedState, | ||||||
|  | 	 * which list all the values over the time. | ||||||
|  | 	 * | ||||||
|  | 	 * @param dataModel the data model which includes the latest recorded States | ||||||
|  | 	 * @param obsoletionPeriodMs the maximum amount of time (in milliseconds) it | ||||||
|  | 	 *                           is worth aggregating over, starting from the | ||||||
|  | 	 *                           most recent States and working backwards in time. | ||||||
|  | 	 * 							 A State exactly {@code obsoletionPeriodMs} ms earlier | ||||||
|  | 	 * 							 than the most recent State is considered non-obsolete | ||||||
|  | 	 *                           (i.e., the "non-obsolete" window is inclusive). | ||||||
|  | 	 *                           Must be non-negative. | ||||||
|  | 	 * @param refTimeMs	the reference time were passed to make testing easier | ||||||
|  | 	 * @return map from serial number to a map from bssid_station String pair to a list of AggregatedState | ||||||
|  | 	 */ | ||||||
|  | 	public static Map<String, Map<String, List<AggregatedState>>> getAggregatedStates( | ||||||
|  | 		Modeler.DataModel dataModel, | ||||||
|  | 		long obsoletionPeriodMs, | ||||||
|  | 		long refTimeMs | ||||||
|  | 	) { | ||||||
|  | 		if (obsoletionPeriodMs < 0) { | ||||||
|  | 			throw new IllegalArgumentException( | ||||||
|  | 				"obsoletionPeriodMs must be non-negative." | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  | 		Map<String, Map<String, List<AggregatedState>>> aggregatedStates = | ||||||
|  | 			new HashMap<>(); | ||||||
|  | 
 | ||||||
|  | 		for ( | ||||||
|  | 			Map.Entry<String, List<State>> deviceToStateList : dataModel.latestStates | ||||||
|  | 				.entrySet() | ||||||
|  | 		) { | ||||||
|  | 			String serialNumber = deviceToStateList.getKey(); | ||||||
|  | 			List<State> states = deviceToStateList.getValue(); | ||||||
|  | 
 | ||||||
|  | 			if (states.isEmpty()) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			/** | ||||||
|  | 			 * Sort in reverse chronological order. Sorting is done just in case the | ||||||
|  | 			 * States in the original list are not chronological already - although | ||||||
|  | 			 * they are inserted chronologically, perhaps latency, synchronization, etc. | ||||||
|  | 			 */ | ||||||
|  | 			states.sort( | ||||||
|  | 				(state1, state2) -> -Long.compare(state1.unit.localtime, state2.unit.localtime) | ||||||
|  | 			); | ||||||
|  | 
 | ||||||
|  | 			Map<String, List<AggregatedState>> bssidToAggregatedStates = | ||||||
|  | 				aggregatedStates | ||||||
|  | 					.computeIfAbsent(serialNumber, k -> new HashMap<>()); | ||||||
|  | 
 | ||||||
|  | 			for (State state : states) { | ||||||
|  | 				if (refTimeMs - state.unit.localtime > obsoletionPeriodMs) { | ||||||
|  | 					// discard obsolete entries | ||||||
|  | 					break; | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				addStateToAggregation(bssidToAggregatedStates, state); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return aggregatedStates; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * This method gets the most recent State from latestStates per device. | ||||||
|  | 	 * | ||||||
|  | 	 * @param latestStates list of latest States per device | ||||||
|  | 	 * @return map from device String to latest State | ||||||
|  | 	 */ | ||||||
|  | 	public static Map<String, State> getLatestState( | ||||||
|  | 		Map<String, List<State>> latestStates | ||||||
|  | 	) { | ||||||
|  | 		Map<String, State> latestState = new ConcurrentHashMap<>(); | ||||||
|  | 		for ( | ||||||
|  | 			Map.Entry<String, List<State>> stateEntry : latestStates.entrySet() | ||||||
|  | 		) { | ||||||
|  | 			String key = stateEntry.getKey(); | ||||||
|  | 			List<State> value = stateEntry.getValue(); | ||||||
|  | 			if (value.isEmpty()) { | ||||||
|  | 				latestState.put(key, null); | ||||||
|  | 			} else { | ||||||
|  | 				latestState.put(key, value.get(value.size() - 1)); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return latestState; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** Create a key pair consisted of bssid and station string */ | ||||||
|  | 	public static String getBssidStationKeyPair(String bssid, String station) { | ||||||
|  | 		return String.format( | ||||||
|  | 			"bssid: %s, station: %s", | ||||||
|  | 			bssid, | ||||||
|  | 			station | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** Return the radio's band, or null if band cannot be found */ | ||||||
|  | 	public static String getBand( | ||||||
|  | 		State.Radio radio, | ||||||
|  | 		JsonObject deviceCapability | ||||||
|  | 	) { | ||||||
|  | 		if (radio.phy == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		JsonElement radioCapabilityElement = deviceCapability.get(radio.phy); | ||||||
|  | 		if (radioCapabilityElement == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		JsonObject radioCapability = radioCapabilityElement.getAsJsonObject(); | ||||||
|  | 		JsonElement bandsElement = radioCapability.get("band"); | ||||||
|  | 		if (bandsElement == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		JsonArray bands = bandsElement.getAsJsonArray(); | ||||||
|  | 		if (bands.isEmpty()) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		return bands.get(0).getAsString(); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| @@ -6,8 +6,9 @@ | |||||||
|  * LICENSE file in the root directory of this source tree. |  * LICENSE file in the root directory of this source tree. | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| package com.facebook.openwifirrm.modules; | package com.facebook.openwifi.rrm.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; | ||||||
| @@ -17,19 +18,19 @@ import java.util.stream.Collectors; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceConfig; | import com.facebook.openwifi.cloudsdk.UCentralClient; | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.models.prov.InventoryTag; | ||||||
| import com.facebook.openwifirrm.DeviceTopology; | import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList; | ||||||
| import com.facebook.openwifirrm.RRMAlgorithm; | import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails; | ||||||
| import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ProvMonitorParams; | import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList; | ||||||
| import com.facebook.openwifirrm.RRMSchedule; | import com.facebook.openwifi.cloudsdk.models.prov.Venue; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralClient; | import com.facebook.openwifi.cloudsdk.models.prov.VenueList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.InventoryTag; | import com.facebook.openwifi.rrm.DeviceConfig; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails; | import com.facebook.openwifi.rrm.DeviceTopology; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList; | import com.facebook.openwifi.rrm.RRMAlgorithm; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.Venue; | import com.facebook.openwifi.rrm.RRMSchedule; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.VenueList; | import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ProvMonitorParams; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * owprov monitor module. |  * owprov monitor module. | ||||||
| @@ -159,12 +160,21 @@ public class ProvMonitor implements Runnable { | |||||||
| 			return null; | 			return null; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		RRMSchedule schedule = new RRMSchedule(); | 		String[] crons = RRMScheduler | ||||||
| 		schedule.cron = RRMScheduler |  | ||||||
| 			.parseIntoQuartzCron(details.rrm.schedule); | 			.parseIntoQuartzCron(details.rrm.schedule); | ||||||
| 		if (schedule.cron == null || schedule.cron.isEmpty()) { | 		if (crons == null || crons.length == 0) { | ||||||
| 			return null; | 			return null; | ||||||
| 		} | 		} | ||||||
|  | 		// if ANY crons are invalid throw it out since it doesn't make sense to | ||||||
|  | 		// schedule partial jobs | ||||||
|  | 		for (String cron : crons) { | ||||||
|  | 			if (cron == null || cron.isEmpty()) { | ||||||
|  | 				return null; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		RRMSchedule schedule = new RRMSchedule(); | ||||||
|  | 		schedule.crons = Arrays.asList(crons); | ||||||
| 
 | 
 | ||||||
| 		if (details.rrm.algorithms != null) { | 		if (details.rrm.algorithms != null) { | ||||||
| 			schedule.algorithms = | 			schedule.algorithms = | ||||||
| @@ -175,6 +185,7 @@ public class ProvMonitor implements Runnable { | |||||||
| 					) | 					) | ||||||
| 					.collect(Collectors.toList()); | 					.collect(Collectors.toList()); | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		return schedule; | 		return schedule; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -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.modules; | package com.facebook.openwifi.rrm.modules; | ||||||
| 
 | 
 | ||||||
|  | import java.text.ParseException; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| import java.util.Properties; | import java.util.Properties; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
| import java.text.ParseException; |  | ||||||
| 
 | 
 | ||||||
| import org.quartz.CronScheduleBuilder; |  | ||||||
| import org.quartz.CronExpression; | import org.quartz.CronExpression; | ||||||
|  | import org.quartz.CronScheduleBuilder; | ||||||
| import org.quartz.Job; | import org.quartz.Job; | ||||||
| import org.quartz.JobBuilder; | import org.quartz.JobBuilder; | ||||||
| import org.quartz.JobDetail; | import org.quartz.JobDetail; | ||||||
| @@ -32,10 +32,11 @@ import org.quartz.impl.StdSchedulerFactory; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceConfig; | import com.facebook.openwifi.rrm.DeviceConfig; | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.RRMAlgorithm; | import com.facebook.openwifi.rrm.RRMAlgorithm; | ||||||
| import com.facebook.openwifirrm.RRMConfig.ModuleConfig.RRMSchedulerParams; | import com.facebook.openwifi.rrm.RRMSchedule; | ||||||
|  | import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.RRMSchedulerParams; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.GsonBuilder; | import com.google.gson.GsonBuilder; | ||||||
| 
 | 
 | ||||||
| @@ -74,15 +75,21 @@ public class RRMScheduler { | |||||||
| 	/** The scheduler instance. */ | 	/** The scheduler instance. */ | ||||||
| 	private Scheduler scheduler; | 	private Scheduler scheduler; | ||||||
| 
 | 
 | ||||||
| 	/** The zones with active triggers scheduled. */ | 	/** | ||||||
| 	private Set<String> scheduledZones; | 	 * The job keys with active triggers scheduled. Job keys take the format of | ||||||
|  | 	 * {@code <zone>:<index>} | ||||||
|  | 	 * | ||||||
|  | 	 * @see #parseIntoQuartzCron(String) | ||||||
|  | 	 * */ | ||||||
|  | 	private Set<String> scheduledJobKeys; | ||||||
| 
 | 
 | ||||||
| 	/** RRM job. */ | 	/** RRM job. */ | ||||||
| 	public static class RRMJob implements Job { | 	public static class RRMJob implements Job { | ||||||
| 		@Override | 		@Override | ||||||
| 		public void execute(JobExecutionContext context) | 		public void execute(JobExecutionContext context) | ||||||
| 			throws JobExecutionException { | 			throws JobExecutionException { | ||||||
| 			String zone = context.getTrigger().getKey().getName(); | 			String jobKey = context.getTrigger().getKey().getName(); | ||||||
|  | 			String zone = jobKey.split(":")[0]; | ||||||
| 			logger.debug("Executing job for zone: {}", zone); | 			logger.debug("Executing job for zone: {}", zone); | ||||||
| 			try { | 			try { | ||||||
| 				SchedulerContext schedulerContext = | 				SchedulerContext schedulerContext = | ||||||
| @@ -107,13 +114,14 @@ public class RRMScheduler { | |||||||
| 	 * @param linuxCron Linux cron with seconds | 	 * @param linuxCron Linux cron with seconds | ||||||
| 	 *        (seconds minutes hours day_of_month month day_of_week [year]) | 	 *        (seconds minutes hours day_of_month month day_of_week [year]) | ||||||
| 	 * | 	 * | ||||||
| 	 * @throws IllegalArgumentException when a linux cron cannot be parsed | 	 * @throws IllegalArgumentException when a linux cron cannot be parsed into a | ||||||
| 	 *         into a valid Quartz spec | 	 *         valid Quartz spec | ||||||
| 	 * @return String a Quartz supported cron | 	 * @return String[] an array of length 1 or 2 of Quartz supported cron that's | ||||||
|  | 	 *         equivalent to the original linux cron | ||||||
| 	 */ | 	 */ | ||||||
| 	public static String parseIntoQuartzCron(String linuxCron) { | 	public static String[] parseIntoQuartzCron(String linuxCron) { | ||||||
| 		if (CronExpression.isValidExpression(linuxCron)) { | 		if (CronExpression.isValidExpression(linuxCron)) { | ||||||
| 			return linuxCron; | 			return new String[] { linuxCron }; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		String[] split = linuxCron.split(" "); | 		String[] split = linuxCron.split(" "); | ||||||
| @@ -144,15 +152,36 @@ public class RRMScheduler { | |||||||
| 			// if first case failed and only day of week is *, set to ? | 			// if first case failed and only day of week is *, set to ? | ||||||
| 			split[DAY_OF_WEEK_INDEX] = "?"; | 			split[DAY_OF_WEEK_INDEX] = "?"; | ||||||
| 		} else { | 		} else { | ||||||
| 			// Quartz does not support both values being set, so return null | 			// Quartz does not support both values being set but the standard says that | ||||||
|  | 			// if both are specified then it becomes OR of the two fields. Which means | ||||||
|  | 			// that we can split it into two separate crons and have it work the same way | ||||||
|  | 			split[DAY_OF_MONTH_INDEX] = "?"; | ||||||
|  | 			String dayOfWeekCron = String.join(" ", split); | ||||||
|  | 
 | ||||||
|  | 			split[DAY_OF_MONTH_INDEX] = dayOfMonth; | ||||||
|  | 			split[DAY_OF_WEEK_INDEX] = "?"; | ||||||
|  | 			String dayOfMonthCron = String.join(" ", split); | ||||||
|  | 
 | ||||||
|  | 			if ( | ||||||
|  | 				!CronExpression.isValidExpression(dayOfWeekCron) || | ||||||
|  | 					!CronExpression.isValidExpression(dayOfMonthCron) | ||||||
|  | 			) { | ||||||
|  | 				logger.error( | ||||||
|  | 					"Unable to parse cron {} into valid crons", | ||||||
|  | 					linuxCron | ||||||
|  | 				); | ||||||
| 				return null; | 				return null; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			return new String[] { dayOfWeekCron, dayOfMonthCron }; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		String quartzCron = String.join(" ", split); | 		String quartzCron = String.join(" ", split); | ||||||
| 		if (!CronExpression.isValidExpression(quartzCron)) { | 		if (!CronExpression.isValidExpression(quartzCron)) { | ||||||
| 			return null; | 			return null; | ||||||
| 		} | 		} | ||||||
| 		return quartzCron; | 
 | ||||||
|  | 		return new String[] { quartzCron }; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Constructor. */ | 	/** Constructor. */ | ||||||
| @@ -194,7 +223,7 @@ public class RRMScheduler { | |||||||
| 			// Schedule job and triggers | 			// Schedule job and triggers | ||||||
| 			scheduler.addJob(job, false); | 			scheduler.addJob(job, false); | ||||||
| 			syncTriggers(); | 			syncTriggers(); | ||||||
| 			logger.info("Scheduled {} RRM trigger(s)", scheduledZones.size()); | 			logger.info("Scheduled {} RRM trigger(s)", scheduledJobKeys.size()); | ||||||
| 
 | 
 | ||||||
| 			// Start scheduler | 			// Start scheduler | ||||||
| 			scheduler.start(); | 			scheduler.start(); | ||||||
| @@ -218,33 +247,41 @@ public class RRMScheduler { | |||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * Synchronize triggers to the current topology, adding/updating/deleting | 	 * Synchronize triggers to the current topology, adding/updating/deleting | ||||||
| 	 * them as necessary. This updates {@link #scheduledZones}. | 	 * them as necessary. This updates {@link #scheduledJobKeys}. | ||||||
| 	 */ | 	 */ | ||||||
| 	public void syncTriggers() { | 	public void syncTriggers() { | ||||||
| 		Set<String> scheduled = ConcurrentHashMap.newKeySet(); | 		Set<String> scheduled = ConcurrentHashMap.newKeySet(); | ||||||
| 		Set<String> prevScheduled = new HashSet<>(); | 		Set<String> prevScheduled = new HashSet<>(); | ||||||
| 		if (scheduledZones != null) { | 		if (scheduledJobKeys != null) { | ||||||
| 			prevScheduled.addAll(scheduledZones); | 			prevScheduled.addAll(scheduledJobKeys); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Add new triggers | 		// Add new triggers | ||||||
| 		for (String zone : deviceDataManager.getZones()) { | 		for (String zone : deviceDataManager.getZones()) { | ||||||
| 			DeviceConfig config = deviceDataManager.getZoneConfig(zone); | 			DeviceConfig config = deviceDataManager.getZoneConfig(zone); | ||||||
|  | 			RRMSchedule schedule = config.schedule; | ||||||
| 			if ( | 			if ( | ||||||
| 				config.schedule == null || | 				schedule == null || schedule.crons == null || | ||||||
| 					config.schedule.cron == null || | 					schedule.crons.isEmpty() | ||||||
| 					config.schedule.cron.isEmpty() |  | ||||||
| 			) { | 			) { | ||||||
| 				continue; // RRM not scheduled | 				continue; // RRM not scheduled | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 			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 { | 				try { | ||||||
| 				CronExpression.validateExpression(config.schedule.cron); | 					CronExpression.validateExpression(cron); | ||||||
| 				} catch (ParseException e) { | 				} catch (ParseException e) { | ||||||
| 					logger.error( | 					logger.error( | ||||||
| 						String.format( | 						String.format( | ||||||
| 							"Invalid cron expression (%s) for zone %s", | 							"Invalid cron expression (%s) for zone %s", | ||||||
| 						config.schedule.cron, | 							cron, | ||||||
| 							zone | 							zone | ||||||
| 						), | 						), | ||||||
| 						e | 						e | ||||||
| @@ -253,50 +290,55 @@ public class RRMScheduler { | |||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				// Create trigger | 				// Create trigger | ||||||
|  | 				String jobKey = String.format("%s:%d", zone, i); | ||||||
| 				Trigger trigger = TriggerBuilder.newTrigger() | 				Trigger trigger = TriggerBuilder.newTrigger() | ||||||
| 				.withIdentity(zone) | 					.withIdentity(jobKey) | ||||||
| 					.forJob(job) | 					.forJob(job) | ||||||
| 					.withSchedule( | 					.withSchedule( | ||||||
| 					CronScheduleBuilder.cronSchedule(config.schedule.cron) | 						CronScheduleBuilder.cronSchedule(cron) | ||||||
| 					) | 					) | ||||||
| 					.build(); | 					.build(); | ||||||
|  | 
 | ||||||
| 				try { | 				try { | ||||||
| 				if (!prevScheduled.contains(zone)) { | 					if (!prevScheduled.contains(jobKey)) { | ||||||
| 						scheduler.scheduleJob(trigger); | 						scheduler.scheduleJob(trigger); | ||||||
| 					} else { | 					} else { | ||||||
| 						scheduler.rescheduleJob(trigger.getKey(), trigger); | 						scheduler.rescheduleJob(trigger.getKey(), trigger); | ||||||
| 					} | 					} | ||||||
| 				} catch (SchedulerException e) { | 				} catch (SchedulerException e) { | ||||||
| 					logger.error( | 					logger.error( | ||||||
| 					"Failed to schedule RRM trigger for zone: " + zone, | 						"Failed to schedule RRM trigger for job key: " + jobKey, | ||||||
| 						e | 						e | ||||||
| 					); | 					); | ||||||
| 					continue; | 					continue; | ||||||
| 				} | 				} | ||||||
| 			scheduled.add(zone); | 
 | ||||||
|  | 				scheduled.add(jobKey); | ||||||
| 				logger.debug( | 				logger.debug( | ||||||
| 				"Scheduled/updated RRM for zone '{}' at: < {} >", | 					"Scheduled/updated RRM for job key '{}' at: < {} >", | ||||||
| 				zone, | 					jobKey, | ||||||
| 				config.schedule.cron | 					cron | ||||||
| 				); | 				); | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		// Remove old triggers | 		// Remove old triggers | ||||||
| 		prevScheduled.removeAll(scheduled); | 		prevScheduled.removeAll(scheduled); | ||||||
| 		for (String zone : prevScheduled) { | 		for (String jobKey : prevScheduled) { | ||||||
| 			try { | 			try { | ||||||
| 				scheduler.unscheduleJob(TriggerKey.triggerKey(zone)); | 				scheduler.unscheduleJob(TriggerKey.triggerKey(jobKey)); | ||||||
| 			} catch (SchedulerException e) { | 			} catch (SchedulerException e) { | ||||||
| 				logger.error( | 				logger.error( | ||||||
| 					"Failed to remove RRM trigger for zone: " + zone, | 					"Failed to remove RRM trigger for jobKey: " + jobKey, | ||||||
| 					e | 					e | ||||||
| 				); | 				); | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 			logger.debug("Removed RRM trigger for zone '{}'", zone); | 			logger.debug("Removed RRM trigger for jobKey '{}'", jobKey); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		this.scheduledZones = scheduled; | 		this.scheduledJobKeys = scheduled; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Run RRM algorithms for the given zone. */ | 	/** Run RRM algorithms for the given zone. */ | ||||||
| @@ -305,16 +347,19 @@ public class RRMScheduler { | |||||||
| 
 | 
 | ||||||
| 		// Get algorithms from zone config | 		// Get algorithms from zone config | ||||||
| 		DeviceConfig config = deviceDataManager.getZoneConfig(zone); | 		DeviceConfig config = deviceDataManager.getZoneConfig(zone); | ||||||
| 		if (config.schedule == null) { | 		RRMSchedule schedule = config.schedule; | ||||||
|  | 		if (schedule == null) { | ||||||
| 			logger.error("RRM schedule missing for zone '{}', aborting!", zone); | 			logger.error("RRM schedule missing for zone '{}', aborting!", zone); | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
|  | 
 | ||||||
| 		if ( | 		if ( | ||||||
| 			config.schedule.algorithms == null || | 			schedule.algorithms == null || | ||||||
| 				config.schedule.algorithms.isEmpty() | 				schedule.algorithms.isEmpty() | ||||||
| 		) { | 		) { | ||||||
| 			logger.debug("Using default RRM algorithms for zone '{}'", zone); | 			logger | ||||||
| 			config.schedule.algorithms = Arrays.asList( | 				.debug("Using default RRM algorithms for zone '{}'", zone); | ||||||
|  | 			schedule.algorithms = Arrays.asList( | ||||||
| 				new RRMAlgorithm( | 				new RRMAlgorithm( | ||||||
| 					RRMAlgorithm.AlgorithmType.OptimizeChannel.name() | 					RRMAlgorithm.AlgorithmType.OptimizeChannel.name() | ||||||
| 				), | 				), | ||||||
| @@ -325,14 +370,15 @@ public class RRMScheduler { | |||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		// Execute algorithms | 		// Execute algorithms | ||||||
| 		for (RRMAlgorithm algo : config.schedule.algorithms) { | 		for (RRMAlgorithm algo : schedule.algorithms) { | ||||||
| 			RRMAlgorithm.AlgorithmResult result = algo.run( | 			RRMAlgorithm.AlgorithmResult result = algo.run( | ||||||
| 				deviceDataManager, | 				deviceDataManager, | ||||||
| 				configManager, | 				configManager, | ||||||
| 				modeler, | 				modeler, | ||||||
| 				zone, | 				zone, | ||||||
| 				params.dryRun, | 				params.dryRun, | ||||||
| 				true /* allowDefaultMode */ | 				true, /* allowDefaultMode */ | ||||||
|  | 				false /* updateImmediately */ | ||||||
| 			); | 			); | ||||||
| 			logger.info( | 			logger.info( | ||||||
| 				"'{}' result for zone '{}': {}", | 				"'{}' result for zone '{}': {}", | ||||||
| @@ -341,5 +387,6 @@ public class RRMScheduler { | |||||||
| 				gson.toJson(result) | 				gson.toJson(result) | ||||||
| 			); | 			); | ||||||
| 		} | 		} | ||||||
|  | 		configManager.queueZoneAndWakeUp(zone); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -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.mysql; | package com.facebook.openwifi.rrm.mysql; | ||||||
| 
 | 
 | ||||||
| import java.sql.Connection; | import java.sql.Connection; | ||||||
| import java.sql.DriverManager; | import java.sql.DriverManager; | ||||||
| @@ -26,9 +26,9 @@ import java.util.stream.Collectors; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.Utils; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| import com.facebook.openwifirrm.ucentral.WifiScanEntry; | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
| import com.facebook.openwifirrm.ucentral.models.State; | import com.facebook.openwifi.rrm.Utils; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.JsonArray; | import com.google.gson.JsonArray; | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
| @@ -360,7 +360,7 @@ public class DatabaseManager { | |||||||
| 	/** Convert a list of state records to a State object. */ | 	/** Convert a list of state records to a State object. */ | ||||||
| 	private State toState(List<StateRecord> records, long ts) { | 	private State toState(List<StateRecord> records, long ts) { | ||||||
| 		State state = new State(); | 		State state = new State(); | ||||||
| 		state.unit = state.new Unit(); | 		state.unit = new State.Unit(); | ||||||
| 		state.unit.localtime = ts; | 		state.unit.localtime = ts; | ||||||
| 
 | 
 | ||||||
| 		// Parse each record | 		// Parse each record | ||||||
| @@ -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.mysql; | package com.facebook.openwifi.rrm.mysql; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Representation of a record in the "state" table. |  * Representation of a record in the "state" table. | ||||||
| @@ -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.optimizers.channel; | package com.facebook.openwifi.rrm.optimizers.channel; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.Arrays; | import java.util.Arrays; | ||||||
| @@ -18,16 +18,18 @@ import java.util.Map; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceConfig; | import com.facebook.openwifi.cloudsdk.UCentralConstants; | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.UCentralUtils; | ||||||
| import com.facebook.openwifirrm.modules.ConfigManager; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| import com.facebook.openwifirrm.modules.Modeler.DataModel; | import com.facebook.openwifi.cloudsdk.ies.HTOperation; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralConstants; | import com.facebook.openwifi.cloudsdk.ies.VHTOperation; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
| import com.facebook.openwifirrm.ucentral.WifiScanEntry; | import com.facebook.openwifi.rrm.DeviceConfig; | ||||||
| import com.facebook.openwifirrm.ucentral.models.State; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.ucentral.operationelement.HTOperationElement; | import com.facebook.openwifi.rrm.modules.ConfigManager; | ||||||
| import com.facebook.openwifirrm.ucentral.operationelement.VHTOperationElement; | import com.facebook.openwifi.rrm.modules.Modeler.DataModel; | ||||||
|  | import com.facebook.openwifi.rrm.modules.ModelerUtils; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Channel optimizer base class. |  * Channel optimizer base class. | ||||||
| @@ -39,24 +41,6 @@ public abstract class ChannelOptimizer { | |||||||
| 	/** Minimum supported channel width (MHz), inclusive. */ | 	/** Minimum supported channel width (MHz), inclusive. */ | ||||||
| 	public static final int MIN_CHANNEL_WIDTH = 20; | 	public static final int MIN_CHANNEL_WIDTH = 20; | ||||||
| 
 | 
 | ||||||
| 	/** List of available channels per band for use. */ |  | ||||||
| 	public static final Map<String, List<Integer>> AVAILABLE_CHANNELS_BAND = |  | ||||||
| 		new HashMap<>(); |  | ||||||
| 	static { |  | ||||||
| 		AVAILABLE_CHANNELS_BAND.put( |  | ||||||
| 			UCentralConstants.BAND_5G, |  | ||||||
| 			Collections.unmodifiableList( |  | ||||||
| 				Arrays.asList(36, 40, 44, 48, 149, 153, 157, 161, 165) |  | ||||||
| 			) |  | ||||||
| 		); |  | ||||||
| 		AVAILABLE_CHANNELS_BAND.put( |  | ||||||
| 			UCentralConstants.BAND_2G, |  | ||||||
| 			Collections.unmodifiableList( |  | ||||||
| 				Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) |  | ||||||
| 			) |  | ||||||
| 		); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	/** Map of channel width (MHz) to available (primary) channels */ | 	/** Map of channel width (MHz) to available (primary) channels */ | ||||||
| 	protected static final Map<Integer, List<Integer>> AVAILABLE_CHANNELS_WIDTH = | 	protected static final Map<Integer, List<Integer>> AVAILABLE_CHANNELS_WIDTH = | ||||||
| 		new HashMap<>(); | 		new HashMap<>(); | ||||||
| @@ -154,7 +138,7 @@ public abstract class ChannelOptimizer { | |||||||
| 		// Remove model entries not in the given zone | 		// Remove model entries not in the given zone | ||||||
| 		this.model.latestWifiScans.keySet() | 		this.model.latestWifiScans.keySet() | ||||||
| 			.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)); | 			.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)); | ||||||
| 		this.model.latestState.keySet() | 		this.model.latestStates.keySet() | ||||||
| 			.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)); | 			.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)); | ||||||
| 		this.model.latestDeviceStatusRadios.keySet() | 		this.model.latestDeviceStatusRadios.keySet() | ||||||
| 			.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)); | 			.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber)); | ||||||
| @@ -197,7 +181,7 @@ public abstract class ChannelOptimizer { | |||||||
| 		String vhtOper | 		String vhtOper | ||||||
| 	) { | 	) { | ||||||
| 		if ( | 		if ( | ||||||
| 			AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G) | 			UCentralUtils.AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G) | ||||||
| 				.contains(channel) | 				.contains(channel) | ||||||
| 		) { | 		) { | ||||||
| 			// 2.4G, it only supports 20 MHz | 			// 2.4G, it only supports 20 MHz | ||||||
| @@ -208,13 +192,13 @@ public abstract class ChannelOptimizer { | |||||||
| 			return MIN_CHANNEL_WIDTH; | 			return MIN_CHANNEL_WIDTH; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		HTOperationElement htOperObj = new HTOperationElement(htOper); | 		HTOperation htOperObj = new HTOperation(htOper); | ||||||
| 		if (vhtOper == null) { | 		if (vhtOper == null) { | ||||||
| 			// HT mode only supports 20/40 MHz | 			// HT mode only supports 20/40 MHz | ||||||
| 			return htOperObj.staChannelWidth ? 40 : 20; | 			return htOperObj.staChannelWidth ? 40 : 20; | ||||||
| 		} else { | 		} else { | ||||||
| 			// VHT/HE mode supports 20/40/160/80+80 MHz | 			// VHT/HE mode supports 20/40/160/80+80 MHz | ||||||
| 			VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper); | 			VHTOperation vhtOperObj = new VHTOperation(vhtOper); | ||||||
| 			if (!htOperObj.staChannelWidth && vhtOperObj.channelWidth == 0) { | 			if (!htOperObj.staChannelWidth && vhtOperObj.channelWidth == 0) { | ||||||
| 				return 20; | 				return 20; | ||||||
| 			} else if ( | 			} else if ( | ||||||
| @@ -234,8 +218,9 @@ public abstract class ChannelOptimizer { | |||||||
| 				// the difference of 8 means it is consecutive | 				// the difference of 8 means it is consecutive | ||||||
| 				int channelDiff = | 				int channelDiff = | ||||||
| 					Math.abs(vhtOperObj.channel1 - vhtOperObj.channel2); | 					Math.abs(vhtOperObj.channel1 - vhtOperObj.channel2); | ||||||
| 				// the "8080" below does not mean 8080 MHz wide, it refers to 80+80 MHz channel | 				// TODO it will currently return just 80 for 80p80 - it should be dealt | ||||||
| 				return channelDiff == 8 ? 160 : 8080; | 				// with properly. | ||||||
|  | 				return channelDiff == 8 ? 160 : 80; | ||||||
| 			} else { | 			} else { | ||||||
| 				return MIN_CHANNEL_WIDTH; | 				return MIN_CHANNEL_WIDTH; | ||||||
| 			} | 			} | ||||||
| @@ -315,7 +300,11 @@ public abstract class ChannelOptimizer { | |||||||
| 			List<WifiScanEntry> scanRespsFiltered = | 			List<WifiScanEntry> scanRespsFiltered = | ||||||
| 				new ArrayList<WifiScanEntry>(); | 				new ArrayList<WifiScanEntry>(); | ||||||
| 			for (WifiScanEntry entry : scanResps) { | 			for (WifiScanEntry entry : scanResps) { | ||||||
| 				if (UCentralUtils.isChannelInBand(entry.channel, band)) { | 				final String entryBand = UCentralUtils | ||||||
|  | 					.freqToBand(entry.frequency); | ||||||
|  | 				if (entryBand == null || !entryBand.equals(band)) { | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
| 				int channelWidth = getChannelWidthFromWiFiScan( | 				int channelWidth = getChannelWidthFromWiFiScan( | ||||||
| 					entry.channel, | 					entry.channel, | ||||||
| 					entry.ht_oper, | 					entry.ht_oper, | ||||||
| @@ -335,7 +324,6 @@ public abstract class ChannelOptimizer { | |||||||
| 					scanRespsFiltered.add(newEntry); | 					scanRespsFiltered.add(newEntry); | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 			} |  | ||||||
| 
 | 
 | ||||||
| 			if (scanRespsFiltered.size() == 0) { | 			if (scanRespsFiltered.size() == 0) { | ||||||
| 				// 3. Filter out APs with empty scan results (on a particular band) | 				// 3. Filter out APs with empty scan results (on a particular band) | ||||||
| @@ -361,6 +349,7 @@ public abstract class ChannelOptimizer { | |||||||
| 	 * @param band the operational band (e.g., "2G") | 	 * @param band the operational band (e.g., "2G") | ||||||
| 	 * @param serialNumber the device's serial number | 	 * @param serialNumber the device's serial number | ||||||
| 	 * @param state the latest state of all the devices | 	 * @param state the latest state of all the devices | ||||||
|  | 	 * @param latestDeviceCapabilities latest device capabilities | ||||||
| 	 * @return the current channel and channel width (MHz) of the device in the | 	 * @return the current channel and channel width (MHz) of the device in the | ||||||
| 	 * given band; returns a current channel of 0 if no channel in the given | 	 * given band; returns a current channel of 0 if no channel in the given | ||||||
| 	 * band is found. | 	 * band is found. | ||||||
| @@ -368,7 +357,8 @@ public abstract class ChannelOptimizer { | |||||||
| 	protected static int[] getCurrentChannel( | 	protected static int[] getCurrentChannel( | ||||||
| 		String band, | 		String band, | ||||||
| 		String serialNumber, | 		String serialNumber, | ||||||
| 		State state | 		State state, | ||||||
|  | 		Map<String, JsonObject> latestDeviceCapabilities | ||||||
| 	) { | 	) { | ||||||
| 		int currentChannel = 0; | 		int currentChannel = 0; | ||||||
| 		int currentChannelWidth = MIN_CHANNEL_WIDTH; | 		int currentChannelWidth = MIN_CHANNEL_WIDTH; | ||||||
| @@ -378,14 +368,28 @@ public abstract class ChannelOptimizer { | |||||||
| 			radioIndex < state.radios.length; | 			radioIndex < state.radios.length; | ||||||
| 			radioIndex++ | 			radioIndex++ | ||||||
| 		) { | 		) { | ||||||
| 			int tempChannel = state.radios[radioIndex].channel; | 			State.Radio radio = state.radios[radioIndex]; | ||||||
| 			if (UCentralUtils.isChannelInBand(tempChannel, band)) { | 			// check if radio is in band of interest | ||||||
|  | 			JsonObject deviceCapability = | ||||||
|  | 				latestDeviceCapabilities.get(serialNumber); | ||||||
|  | 			if (deviceCapability == null) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			final String radioBand = ModelerUtils.getBand( | ||||||
|  | 				radio, | ||||||
|  | 				deviceCapability | ||||||
|  | 			); | ||||||
|  | 			if (radioBand == null || !radioBand.equals(band)) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			int tempChannel = radio.channel; | ||||||
| 			currentChannel = tempChannel; | 			currentChannel = tempChannel; | ||||||
| 			// treat as two separate 80MHz channel and only assign to one | 			// treat as two separate 80MHz channel and only assign to one | ||||||
| 			// TODO: support 80p80 properly | 			// TODO: support 80p80 properly | ||||||
| 			Integer parsedChannelWidth = UCentralUtils | 			Integer parsedChannelWidth = UCentralUtils | ||||||
| 				.parseChannelWidth( | 				.parseChannelWidth( | ||||||
| 						state.radios[radioIndex].channel_width, | 					radio.channel_width, | ||||||
| 					true | 					true | ||||||
| 				); | 				); | ||||||
| 			if (parsedChannelWidth != null) { | 			if (parsedChannelWidth != null) { | ||||||
| @@ -395,11 +399,10 @@ public abstract class ChannelOptimizer { | |||||||
| 
 | 
 | ||||||
| 			logger.error( | 			logger.error( | ||||||
| 				"Invalid channel width {}", | 				"Invalid channel width {}", | ||||||
| 					state.radios[radioIndex].channel_width | 				radio.channel_width | ||||||
| 			); | 			); | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
| 		return new int[] { currentChannel, currentChannelWidth }; | 		return new int[] { currentChannel, currentChannelWidth }; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -638,14 +641,13 @@ public abstract class ChannelOptimizer { | |||||||
| 	public abstract Map<String, Map<String, Integer>> computeChannelMap(); | 	public abstract Map<String, Map<String, Integer>> computeChannelMap(); | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * Program the given channel map into the AP config and notify the config | 	 * Program the given channel map into the AP config. | ||||||
| 	 * manager. |  | ||||||
| 	 * | 	 * | ||||||
| 	 * @param deviceDataManager the DeviceDataManager instance | 	 * @param deviceDataManager the DeviceDataManager instance | ||||||
| 	 * @param configManager the ConfigManager instance | 	 * @param configManager the ConfigManager instance | ||||||
| 	 * @param channelMap the map of devices (by serial number) to radio to channel | 	 * @param channelMap the map of devices (by serial number) to radio to channel | ||||||
| 	 */ | 	 */ | ||||||
| 	public void applyConfig( | 	public void updateDeviceApConfig( | ||||||
| 		DeviceDataManager deviceDataManager, | 		DeviceDataManager deviceDataManager, | ||||||
| 		ConfigManager configManager, | 		ConfigManager configManager, | ||||||
| 		Map<String, Map<String, Integer>> channelMap | 		Map<String, Map<String, Integer>> channelMap | ||||||
| @@ -663,8 +665,5 @@ public abstract class ChannelOptimizer { | |||||||
| 				deviceConfig.autoChannels = entry.getValue(); | 				deviceConfig.autoChannels = entry.getValue(); | ||||||
| 			} | 			} | ||||||
| 		}); | 		}); | ||||||
| 
 |  | ||||||
| 		// Trigger config update now |  | ||||||
| 		configManager.wakeUp(); |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @@ -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.optimizers.channel; | package com.facebook.openwifi.rrm.optimizers.channel; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
| @@ -20,12 +20,13 @@ import java.util.stream.Collectors; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.UCentralConstants; | ||||||
| import com.facebook.openwifirrm.modules.Modeler.DataModel; | import com.facebook.openwifi.cloudsdk.UCentralUtils; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralConstants; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
| import com.facebook.openwifirrm.ucentral.WifiScanEntry; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.ucentral.models.State; | import com.facebook.openwifi.rrm.modules.Modeler.DataModel; | ||||||
|  | import com.facebook.openwifi.rrm.modules.ModelerUtils; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Least used channel optimizer. |  * Least used channel optimizer. | ||||||
| @@ -89,13 +90,13 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer { | |||||||
| 	protected static Map<Integer, Integer> getOccupiedOverlapChannels( | 	protected static Map<Integer, Integer> getOccupiedOverlapChannels( | ||||||
| 		Map<Integer, Integer> occupiedChannels | 		Map<Integer, Integer> occupiedChannels | ||||||
| 	) { | 	) { | ||||||
| 		int maxChannel = | 		final int maxChannel = | ||||||
| 			UCentralUtils.UPPER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G); | 			UCentralUtils.getUpperChannelLimit(UCentralConstants.BAND_2G); | ||||||
| 		int minChannel = | 		final int minChannel = | ||||||
| 			UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G); | 			UCentralUtils.getLowerChannelLimit(UCentralConstants.BAND_2G); | ||||||
| 		Map<Integer, Integer> occupiedOverlapChannels = new TreeMap<>(); | 		Map<Integer, Integer> occupiedOverlapChannels = new TreeMap<>(); | ||||||
| 		for ( | 		for ( | ||||||
| 			int overlapChannel : AVAILABLE_CHANNELS_BAND | 			int overlapChannel : UCentralUtils.AVAILABLE_CHANNELS_BAND | ||||||
| 				.get(UCentralConstants.BAND_2G) | 				.get(UCentralConstants.BAND_2G) | ||||||
| 		) { | 		) { | ||||||
| 			int occupancy = 0; | 			int occupancy = 0; | ||||||
| @@ -337,11 +338,13 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer { | |||||||
| 			UCentralUtils.getDeviceAvailableChannels( | 			UCentralUtils.getDeviceAvailableChannels( | ||||||
| 				model.latestDeviceStatusRadios, | 				model.latestDeviceStatusRadios, | ||||||
| 				model.latestDeviceCapabilities, | 				model.latestDeviceCapabilities, | ||||||
| 				AVAILABLE_CHANNELS_BAND | 				UCentralUtils.AVAILABLE_CHANNELS_BAND | ||||||
| 			); | 			); | ||||||
| 
 | 
 | ||||||
|  | 		Map<String, State> latestState = | ||||||
|  | 			ModelerUtils.getLatestState(model.latestStates); | ||||||
| 		Map<String, String> bssidsMap = | 		Map<String, String> bssidsMap = | ||||||
| 			UCentralUtils.getBssidsMap(model.latestState); | 			UCentralUtils.getBssidsMap(latestState); | ||||||
| 
 | 
 | ||||||
| 		for (String band : bandsMap.keySet()) { | 		for (String band : bandsMap.keySet()) { | ||||||
| 			// Performance metrics | 			// Performance metrics | ||||||
| @@ -369,11 +372,12 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer { | |||||||
| 					availableChannelsList == null || | 					availableChannelsList == null || | ||||||
| 						availableChannelsList.isEmpty() | 						availableChannelsList.isEmpty() | ||||||
| 				) { | 				) { | ||||||
| 					availableChannelsList = AVAILABLE_CHANNELS_BAND.get(band); | 					availableChannelsList = | ||||||
|  | 						UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band); | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				// Get current channel of the device | 				// Get current channel of the device | ||||||
| 				State state = model.latestState.get(serialNumber); | 				State state = latestState.get(serialNumber); | ||||||
| 				if (state == null) { | 				if (state == null) { | ||||||
| 					logger.debug( | 					logger.debug( | ||||||
| 						"Device {}: No state found, skipping...", | 						"Device {}: No state found, skipping...", | ||||||
| @@ -389,7 +393,12 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer { | |||||||
| 					continue; | 					continue; | ||||||
| 				} | 				} | ||||||
| 				int[] currentChannelInfo = | 				int[] currentChannelInfo = | ||||||
| 					getCurrentChannel(band, serialNumber, state); | 					getCurrentChannel( | ||||||
|  | 						band, | ||||||
|  | 						serialNumber, | ||||||
|  | 						state, | ||||||
|  | 						model.latestDeviceCapabilities | ||||||
|  | 					); | ||||||
| 				int currentChannel = currentChannelInfo[0]; | 				int currentChannel = currentChannelInfo[0]; | ||||||
| 				// Filter out APs if the radios in the state do not contain a | 				// Filter out APs if the radios in the state do not contain a | ||||||
| 				// channel in a band given by the state. This can happen when | 				// channel in a band given by the state. This can happen when | ||||||
| @@ -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.optimizers.channel; | package com.facebook.openwifi.rrm.optimizers.channel; | ||||||
| 
 | 
 | ||||||
| import java.util.ArrayList; | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| @@ -19,11 +19,12 @@ import java.util.TreeSet; | |||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| 
 | 
 | ||||||
| import com.facebook.openwifirrm.DeviceDataManager; | import com.facebook.openwifi.cloudsdk.UCentralUtils; | ||||||
| import com.facebook.openwifirrm.modules.Modeler.DataModel; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| import com.facebook.openwifirrm.ucentral.UCentralUtils; | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
| import com.facebook.openwifirrm.ucentral.WifiScanEntry; | import com.facebook.openwifi.rrm.DeviceDataManager; | ||||||
| import com.facebook.openwifirrm.ucentral.models.State; | import com.facebook.openwifi.rrm.modules.Modeler.DataModel; | ||||||
|  | import com.facebook.openwifi.rrm.modules.ModelerUtils; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Random channel initializer. |  * Random channel initializer. | ||||||
| @@ -125,11 +126,13 @@ public class RandomChannelInitializer extends ChannelOptimizer { | |||||||
| 			UCentralUtils.getDeviceAvailableChannels( | 			UCentralUtils.getDeviceAvailableChannels( | ||||||
| 				model.latestDeviceStatusRadios, | 				model.latestDeviceStatusRadios, | ||||||
| 				model.latestDeviceCapabilities, | 				model.latestDeviceCapabilities, | ||||||
| 				AVAILABLE_CHANNELS_BAND | 				UCentralUtils.AVAILABLE_CHANNELS_BAND | ||||||
| 			); | 			); | ||||||
| 
 | 
 | ||||||
|  | 		Map<String, State> latestState = | ||||||
|  | 			ModelerUtils.getLatestState(model.latestStates); | ||||||
| 		Map<String, String> bssidsMap = | 		Map<String, String> bssidsMap = | ||||||
| 			UCentralUtils.getBssidsMap(model.latestState); | 			UCentralUtils.getBssidsMap(latestState); | ||||||
| 
 | 
 | ||||||
| 		for (Map.Entry<String, List<String>> entry : bandsMap.entrySet()) { | 		for (Map.Entry<String, List<String>> entry : bandsMap.entrySet()) { | ||||||
| 			// Performance metrics | 			// Performance metrics | ||||||
| @@ -149,7 +152,7 @@ public class RandomChannelInitializer extends ChannelOptimizer { | |||||||
| 			// to get the valid result for single channel assignment | 			// to get the valid result for single channel assignment | ||||||
| 			// If the intersection is empty, then turn back to the default channels list | 			// If the intersection is empty, then turn back to the default channels list | ||||||
| 			List<Integer> availableChannelsList = new ArrayList<>( | 			List<Integer> availableChannelsList = new ArrayList<>( | ||||||
| 				AVAILABLE_CHANNELS_BAND.get(band) | 				UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band) | ||||||
| 			); | 			); | ||||||
| 			for (String serialNumber : entry.getValue()) { | 			for (String serialNumber : entry.getValue()) { | ||||||
| 				List<Integer> deviceChannelsList = deviceAvailableChannels | 				List<Integer> deviceChannelsList = deviceAvailableChannels | ||||||
| @@ -158,14 +161,16 @@ public class RandomChannelInitializer extends ChannelOptimizer { | |||||||
| 				if ( | 				if ( | ||||||
| 					deviceChannelsList == null || deviceChannelsList.isEmpty() | 					deviceChannelsList == null || deviceChannelsList.isEmpty() | ||||||
| 				) { | 				) { | ||||||
| 					deviceChannelsList = AVAILABLE_CHANNELS_BAND.get(band); | 					deviceChannelsList = | ||||||
|  | 						UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band); | ||||||
| 				} | 				} | ||||||
| 				availableChannelsList.retainAll(deviceChannelsList); | 				availableChannelsList.retainAll(deviceChannelsList); | ||||||
| 			} | 			} | ||||||
| 			if ( | 			if ( | ||||||
| 				availableChannelsList == null || availableChannelsList.isEmpty() | 				availableChannelsList == null || availableChannelsList.isEmpty() | ||||||
| 			) { | 			) { | ||||||
| 				availableChannelsList = AVAILABLE_CHANNELS_BAND.get(band); | 				availableChannelsList = | ||||||
|  | 					UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band); | ||||||
| 				logger.debug( | 				logger.debug( | ||||||
| 					"The intersection of the device channels lists is empty!!! " + | 					"The intersection of the device channels lists is empty!!! " + | ||||||
| 						"Fall back to the default channels list" | 						"Fall back to the default channels list" | ||||||
| @@ -183,7 +188,7 @@ public class RandomChannelInitializer extends ChannelOptimizer { | |||||||
| 						? rng.nextInt(availableChannelsList.size()) : defaultChannelIndex | 						? rng.nextInt(availableChannelsList.size()) : defaultChannelIndex | ||||||
| 				); | 				); | ||||||
| 
 | 
 | ||||||
| 				State state = model.latestState.get(serialNumber); | 				State state = latestState.get(serialNumber); | ||||||
| 				if (state == null) { | 				if (state == null) { | ||||||
| 					logger.debug( | 					logger.debug( | ||||||
| 						"Device {}: No state found, skipping...", | 						"Device {}: No state found, skipping...", | ||||||
| @@ -199,7 +204,12 @@ public class RandomChannelInitializer extends ChannelOptimizer { | |||||||
| 					continue; | 					continue; | ||||||
| 				} | 				} | ||||||
| 				int[] currentChannelInfo = | 				int[] currentChannelInfo = | ||||||
| 					getCurrentChannel(band, serialNumber, state); | 					getCurrentChannel( | ||||||
|  | 						band, | ||||||
|  | 						serialNumber, | ||||||
|  | 						state, | ||||||
|  | 						model.latestDeviceCapabilities | ||||||
|  | 					); | ||||||
| 				int currentChannel = currentChannelInfo[0]; | 				int currentChannel = currentChannelInfo[0]; | ||||||
| 				int currentChannelWidth = currentChannelInfo[1]; | 				int currentChannelWidth = currentChannelInfo[1]; | ||||||
| 				if (currentChannel == 0) { | 				if (currentChannel == 0) { | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user