mirror of
				https://github.com/Telecominfraproject/wlan-cloud-rrm.git
				synced 2025-11-04 04:27:46 +00:00 
			
		
		
		
	Compare commits
	
		
			29 Commits
		
	
	
		
			v2.7.0
			...
			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):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,23 +426,63 @@ 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). */
 | 
				
			||||||
 | 
						public static String freqToBand(int freqMHz) {
 | 
				
			||||||
 | 
							if (2412 <= freqMHz && freqMHz <= 2484) {
 | 
				
			||||||
 | 
								return "2G";
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return "5G";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Given the channel, gets the band by checking lower bound and upper bound
 | 
						 * Tries to parse channel width, if it encounters an error it will return null.
 | 
				
			||||||
	 * of each band
 | 
						 * It can handle 80p80 in two ways. First it can just treat it as 160. Second,
 | 
				
			||||||
 | 
						 * it can just apply to the first 80 channel and ignore the second. This is
 | 
				
			||||||
 | 
						 * controlled by treatSeparate.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param channel channel number
 | 
						 * @param channelWidthStr the channel width
 | 
				
			||||||
	 * @return band if the channel can be mapped to a valid band; null otherwise
 | 
						 * @param treatSeparate treats each band separately
 | 
				
			||||||
 | 
						 * @return channel width in MHz
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static String getBandFromChannel(int channel) {
 | 
						public static Integer parseChannelWidth(
 | 
				
			||||||
		for (String band : UCentralConstants.BANDS) {
 | 
							String channelWidthStr,
 | 
				
			||||||
			if (isChannelInBand(channel, band)) {
 | 
							boolean treatSeparate
 | 
				
			||||||
				return band;
 | 
						) {
 | 
				
			||||||
			}
 | 
							// 80p80 is the only case where it can't be parsed into an integer
 | 
				
			||||||
 | 
							if (channelWidthStr.equals("80p80")) {
 | 
				
			||||||
 | 
								return treatSeparate ? 80 : 160;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								return Integer.parseInt(channelWidthStr);
 | 
				
			||||||
 | 
							} catch (NumberFormatException e) {
 | 
				
			||||||
			return null;
 | 
								return null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Tries to parse the index from the reference string in the JSON returned from
 | 
				
			||||||
 | 
						 * other services. Note that this only returns the index, the caller is
 | 
				
			||||||
 | 
						 * responsible for making sure that the correct field is passed in and the
 | 
				
			||||||
 | 
						 * index is used in the correct fields. If there's an error parsing, it will
 | 
				
			||||||
 | 
						 * return null.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param reference The reference string, keyed under "$ref"
 | 
				
			||||||
 | 
						 * @return the index of the reference or null if an error occurred.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static Integer parseReferenceIndex(String reference) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								return Integer.parseInt(
 | 
				
			||||||
 | 
									reference,
 | 
				
			||||||
 | 
									reference.lastIndexOf("/") + 1,
 | 
				
			||||||
 | 
									reference.length(),
 | 
				
			||||||
 | 
									10
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
							} catch (NumberFormatException e) {
 | 
				
			||||||
 | 
								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.
 | 
				
			||||||
@@ -30,15 +30,23 @@ public class VHTOperationElement {
 | 
				
			|||||||
	 * 160 MHz wide channel, this parameter is the channel number of the 80MHz
 | 
						 * 160 MHz wide channel, this parameter is the channel number of the 80MHz
 | 
				
			||||||
	 * channel that contains the primary channel. For a 80+80 MHz wide channel, this
 | 
						 * channel that contains the primary channel. For a 80+80 MHz wide channel, this
 | 
				
			||||||
	 * parameter is the channel number of the primary channel.
 | 
						 * parameter is the channel number of the primary channel.
 | 
				
			||||||
 | 
						 * <p>
 | 
				
			||||||
 | 
						 * This field is an unsigned byte in the specification (i.e., with values
 | 
				
			||||||
 | 
						 * between 0 and 255). But because Java only supports signed bytes, a short
 | 
				
			||||||
 | 
						 * data type is used to store the value.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public final byte channel1;
 | 
						public final short channel1;
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * This should be zero unless the channel is 160MHz or 80+80 MHz wide. If the
 | 
						 * This should be zero unless the channel is 160MHz or 80+80 MHz wide. If the
 | 
				
			||||||
	 * channel is 160 MHz wide, this parameter is the channel number of the 160 MHz
 | 
						 * channel is 160 MHz wide, this parameter is the channel number of the 160 MHz
 | 
				
			||||||
	 * wide channel. If the channel is 80+80 MHz wide, this parameter is the channel
 | 
						 * wide channel. If the channel is 80+80 MHz wide, this parameter is the channel
 | 
				
			||||||
	 * index of the secondary 80 MHz wide channel.
 | 
						 * index of the secondary 80 MHz wide channel.
 | 
				
			||||||
 | 
						 * <p>
 | 
				
			||||||
 | 
						 * This field is an unsigned byte in the specification (i.e., with values
 | 
				
			||||||
 | 
						 * between 0 and 255). But because Java only supports signed bytes, a short
 | 
				
			||||||
 | 
						 * data type is used to store the value.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public final byte channel2;
 | 
						public final short channel2;
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * An 8-element array where each element is between 0 and 4 inclusive. MCS means
 | 
						 * An 8-element array where each element is between 0 and 4 inclusive. MCS means
 | 
				
			||||||
	 * Modulation and Coding Scheme. NSS means Number of Spatial Streams. There can
 | 
						 * Modulation and Coding Scheme. NSS means Number of Spatial Streams. There can
 | 
				
			||||||
@@ -57,11 +65,11 @@ 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 = bytes[1];
 | 
							this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value
 | 
				
			||||||
		this.channel2 = bytes[2];
 | 
							this.channel2 = (short) (bytes[2] & 0xff); // read as unsigned value
 | 
				
			||||||
		byte[] vhtMcsForNss = new byte[8];
 | 
							byte[] vhtMcsForNss = new byte[8];
 | 
				
			||||||
		vhtMcsForNss[0] = (byte) (bytes[3] >>> 6);
 | 
							vhtMcsForNss[0] = (byte) (bytes[3] >>> 6);
 | 
				
			||||||
		vhtMcsForNss[1] = (byte) ((bytes[3] & 0b00110000) >>> 4);
 | 
							vhtMcsForNss[1] = (byte) ((bytes[3] & 0b00110000) >>> 4);
 | 
				
			||||||
@@ -81,10 +89,10 @@ 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,
 | 
				
			||||||
		byte channel1,
 | 
							short channel1,
 | 
				
			||||||
		byte channel2,
 | 
							short channel2,
 | 
				
			||||||
		byte[] vhtMcsForNss
 | 
							byte[] vhtMcsForNss
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		/*
 | 
							/*
 | 
				
			||||||
@@ -106,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;
 | 
				
			||||||
@@ -134,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);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -160,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;
 | 
				
			||||||
@@ -112,8 +112,21 @@ public class State {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public Unit unit;
 | 
						public Unit unit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static class Radio {
 | 
				
			||||||
 | 
							public long active_ms;
 | 
				
			||||||
 | 
							public long busy_ms;
 | 
				
			||||||
 | 
							public int channel;
 | 
				
			||||||
 | 
							public String channel_width;
 | 
				
			||||||
 | 
							public long noise;
 | 
				
			||||||
 | 
							public String phy;
 | 
				
			||||||
 | 
							public long receive_ms;
 | 
				
			||||||
 | 
							public long transmit_ms;
 | 
				
			||||||
 | 
							public int tx_power;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Radio[] radios;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO
 | 
						// TODO
 | 
				
			||||||
	public JsonObject[] radios;
 | 
					 | 
				
			||||||
	@SerializedName("link-state") public JsonObject linkState;
 | 
						@SerializedName("link-state") public JsonObject linkState;
 | 
				
			||||||
	public JsonObject gps;
 | 
						public JsonObject gps;
 | 
				
			||||||
	public JsonObject poe;
 | 
						public JsonObject poe;
 | 
				
			||||||
@@ -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
 | 
				
			||||||
		byte expectedChannel1 = 36;
 | 
							short expectedChannel1 = 36;
 | 
				
			||||||
		byte 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,27 @@ 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,
 | 
				
			||||||
 | 
								expectedChannel1,
 | 
				
			||||||
 | 
								expectedChannel2,
 | 
				
			||||||
 | 
								expectedVhtMcsForNss
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
							assertEquals(expectedVhtOperObj, vhtOperObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// test with channel number >= 128 (channel fields should be unsigned)
 | 
				
			||||||
 | 
							vhtOper = "AJUAAAA=";
 | 
				
			||||||
 | 
							vhtOperObj = new VHTOperation(vhtOper);
 | 
				
			||||||
 | 
							expectedChannelWidthIndicator = 0;
 | 
				
			||||||
 | 
							expectedChannel1 = 149;
 | 
				
			||||||
 | 
							expectedChannel2 = 0;
 | 
				
			||||||
 | 
							expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
 | 
				
			||||||
 | 
							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,11 +93,12 @@ 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> latestDeviceStatus =
 | 
							public Map<String, JsonArray> latestDeviceStatusRadios =
 | 
				
			||||||
			new ConcurrentHashMap<>();
 | 
								new ConcurrentHashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/** List of capabilities per device. */
 | 
							/** List of capabilities per device. */
 | 
				
			||||||
@@ -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(
 | 
				
			||||||
@@ -379,7 +392,7 @@ public class Modeler implements Runnable {
 | 
				
			|||||||
		// Get old vs new radios info and store the new radios info
 | 
							// Get old vs new radios info and store the new radios info
 | 
				
			||||||
		JsonArray newRadioList = config.getRadioConfigList();
 | 
							JsonArray newRadioList = config.getRadioConfigList();
 | 
				
			||||||
		Set<String> newRadioBandsSet = config.getRadioBandsSet(newRadioList);
 | 
							Set<String> newRadioBandsSet = config.getRadioBandsSet(newRadioList);
 | 
				
			||||||
		JsonArray oldRadioList = dataModel.latestDeviceStatus
 | 
							JsonArray oldRadioList = dataModel.latestDeviceStatusRadios
 | 
				
			||||||
			.put(serialNumber, newRadioList);
 | 
								.put(serialNumber, newRadioList);
 | 
				
			||||||
		Set<String> oldRadioBandsSet = config.getRadioBandsSet(oldRadioList);
 | 
							Set<String> oldRadioBandsSet = config.getRadioBandsSet(oldRadioList);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -423,13 +436,13 @@ 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");
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (
 | 
							if (
 | 
				
			||||||
			dataModel.latestDeviceStatus.entrySet()
 | 
								dataModel.latestDeviceStatusRadios.entrySet()
 | 
				
			||||||
				.removeIf(e -> !isRRMEnabled(e.getKey()))
 | 
									.removeIf(e -> !isRRMEnabled(e.getKey()))
 | 
				
			||||||
		) {
 | 
							) {
 | 
				
			||||||
			logger.debug("Removed some status entries from data model");
 | 
								logger.debug("Removed some status 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
 | 
				
			||||||
@@ -454,9 +454,10 @@ public class DatabaseManager {
 | 
				
			|||||||
			.map(o -> gson.fromJson(o, State.Interface.class))
 | 
								.map(o -> gson.fromJson(o, State.Interface.class))
 | 
				
			||||||
			.collect(Collectors.toList())
 | 
								.collect(Collectors.toList())
 | 
				
			||||||
			.toArray(new State.Interface[0]);
 | 
								.toArray(new State.Interface[0]);
 | 
				
			||||||
		state.radios = new JsonObject[radios.lastKey() + 1];
 | 
							state.radios = new State.Radio[radios.lastKey() + 1];
 | 
				
			||||||
		for (Map.Entry<Integer, JsonObject> entry : radios.entrySet()) {
 | 
							for (Map.Entry<Integer, JsonObject> entry : radios.entrySet()) {
 | 
				
			||||||
			state.radios[entry.getKey()] = entry.getValue();
 | 
								State.Radio radio = new State.Radio();
 | 
				
			||||||
 | 
								state.radios[entry.getKey()] = radio;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return state;
 | 
							return state;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -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,9 +138,9 @@ 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.latestDeviceStatus.keySet()
 | 
							this.model.latestDeviceStatusRadios.keySet()
 | 
				
			||||||
			.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
 | 
								.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
 | 
				
			||||||
		this.model.latestDeviceCapabilities.keySet()
 | 
							this.model.latestDeviceCapabilities.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,16 +368,40 @@ public abstract class ChannelOptimizer {
 | 
				
			|||||||
			radioIndex < state.radios.length;
 | 
								radioIndex < state.radios.length;
 | 
				
			||||||
			radioIndex++
 | 
								radioIndex++
 | 
				
			||||||
		) {
 | 
							) {
 | 
				
			||||||
			int tempChannel = state.radios[radioIndex]
 | 
								State.Radio radio = state.radios[radioIndex];
 | 
				
			||||||
				.get("channel")
 | 
								// check if radio is in band of interest
 | 
				
			||||||
				.getAsInt();
 | 
								JsonObject deviceCapability =
 | 
				
			||||||
			if (UCentralUtils.isChannelInBand(tempChannel, band)) {
 | 
									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;
 | 
				
			||||||
				currentChannelWidth = state.radios[radioIndex]
 | 
								// treat as two separate 80MHz channel and only assign to one
 | 
				
			||||||
					.get("channel_width")
 | 
								// TODO: support 80p80 properly
 | 
				
			||||||
					.getAsInt();
 | 
								Integer parsedChannelWidth = UCentralUtils
 | 
				
			||||||
 | 
									.parseChannelWidth(
 | 
				
			||||||
 | 
										radio.channel_width,
 | 
				
			||||||
 | 
										true
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
								if (parsedChannelWidth != null) {
 | 
				
			||||||
 | 
									currentChannelWidth = parsedChannelWidth;
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								logger.error(
 | 
				
			||||||
 | 
									"Invalid channel width {}",
 | 
				
			||||||
 | 
									radio.channel_width
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								continue;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return new int[] { currentChannel, currentChannelWidth };
 | 
							return new int[] { currentChannel, currentChannelWidth };
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -627,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
 | 
				
			||||||
@@ -652,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;
 | 
				
			||||||
@@ -331,17 +332,19 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
 | 
				
			|||||||
	public Map<String, Map<String, Integer>> computeChannelMap() {
 | 
						public Map<String, Map<String, Integer>> computeChannelMap() {
 | 
				
			||||||
		Map<String, Map<String, Integer>> channelMap = new TreeMap<>();
 | 
							Map<String, Map<String, Integer>> channelMap = new TreeMap<>();
 | 
				
			||||||
		Map<String, List<String>> bandsMap = UCentralUtils
 | 
							Map<String, List<String>> bandsMap = UCentralUtils
 | 
				
			||||||
			.getBandsMap(model.latestDeviceStatus);
 | 
								.getBandsMap(model.latestDeviceStatusRadios);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
 | 
							Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
 | 
				
			||||||
			UCentralUtils.getDeviceAvailableChannels(
 | 
								UCentralUtils.getDeviceAvailableChannels(
 | 
				
			||||||
				model.latestDeviceStatus,
 | 
									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.
 | 
				
			||||||
@@ -119,17 +120,19 @@ public class RandomChannelInitializer extends ChannelOptimizer {
 | 
				
			|||||||
	public Map<String, Map<String, Integer>> computeChannelMap() {
 | 
						public Map<String, Map<String, Integer>> computeChannelMap() {
 | 
				
			||||||
		Map<String, Map<String, Integer>> channelMap = new TreeMap<>();
 | 
							Map<String, Map<String, Integer>> channelMap = new TreeMap<>();
 | 
				
			||||||
		Map<String, List<String>> bandsMap =
 | 
							Map<String, List<String>> bandsMap =
 | 
				
			||||||
			UCentralUtils.getBandsMap(model.latestDeviceStatus);
 | 
								UCentralUtils.getBandsMap(model.latestDeviceStatusRadios);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
 | 
							Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
 | 
				
			||||||
			UCentralUtils.getDeviceAvailableChannels(
 | 
								UCentralUtils.getDeviceAvailableChannels(
 | 
				
			||||||
				model.latestDeviceStatus,
 | 
									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