mirror of
				https://github.com/Telecominfraproject/wlan-cloud-rrm.git
				synced 2025-10-31 02:28:15 +00:00 
			
		
		
		
	Compare commits
	
		
			48 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 9e22796758 | ||
|   | f1c488eac8 | ||
|   | 066c523df5 | ||
|   | ba8c156e72 | ||
|   | d73eb23920 | ||
|   | ac5a1c8887 | ||
|   | ea3a13e98c | ||
|   | c1511e8e91 | ||
|   | 05c36a535f | ||
|   | c94c31cb63 | ||
|   | 404934eda9 | ||
|   | 80626388c8 | ||
|   | a79359c69d | ||
|   | a638d70fd6 | ||
|   | e3705699b4 | ||
|   | 35eddf73cf | ||
|   | f031027684 | ||
|   | a54f9a48be | ||
|   | c22ebeea31 | ||
|   | e1b9052ecc | ||
|   | 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 | 
							
								
								
									
										4
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -26,7 +26,7 @@ jobs: | |||||||
|       DOCKER_REGISTRY_USERNAME: ucentral |       DOCKER_REGISTRY_USERNAME: ucentral | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout actions repo |     - name: Checkout actions repo | ||||||
|       uses: actions/checkout@v2 |       uses: actions/checkout@v3 | ||||||
|       with: |       with: | ||||||
|         repository: Telecominfraproject/.github |         repository: Telecominfraproject/.github | ||||||
|         path: github |         path: github | ||||||
| @@ -57,7 +57,7 @@ jobs: | |||||||
|       - docker |       - docker | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout actions repo |     - name: Checkout actions repo | ||||||
|       uses: actions/checkout@v2 |       uses: actions/checkout@v3 | ||||||
|       with: |       with: | ||||||
|         repository: Telecominfraproject/.github |         repository: Telecominfraproject/.github | ||||||
|         path: github |         path: github | ||||||
|   | |||||||
							
								
								
									
										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: | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,7 +11,7 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout actions repo |       - name: Checkout actions repo | ||||||
|         uses: actions/checkout@v2 |         uses: actions/checkout@v3 | ||||||
|         with: |         with: | ||||||
|           repository: Telecominfraproject/.github |           repository: Telecominfraproject/.github | ||||||
|           path: github |           path: github | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,7 +17,7 @@ jobs: | |||||||
|       HELM_REPO_USERNAME: ucentral |       HELM_REPO_USERNAME: ucentral | ||||||
|     steps: |     steps: | ||||||
|       - name: Checkout uCentral assembly chart repo |       - name: Checkout uCentral assembly chart repo | ||||||
|         uses: actions/checkout@v2 |         uses: actions/checkout@v3 | ||||||
|         with: |         with: | ||||||
|           path: wlan-cloud-rrm |           path: wlan-cloud-rrm | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										22
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,12 +1,15 @@ | |||||||
| /target | /target/ | ||||||
| /*.log* | */target/ | ||||||
| /device_config.json |  | ||||||
| /settings.json | # owrrm specific | ||||||
| /topology.json | *.log* | ||||||
|  | device_config.json | ||||||
|  | settings.json | ||||||
|  | topology.json | ||||||
|  |  | ||||||
| # Eclipse | # Eclipse | ||||||
| /.settings/ | .settings/ | ||||||
| /bin/ | bin/ | ||||||
| .metadata | .metadata | ||||||
| .classpath | .classpath | ||||||
| .project | .project | ||||||
| @@ -17,3 +20,8 @@ | |||||||
| *.iml | *.iml | ||||||
| *.iws | *.iws | ||||||
| *.ipr | *.ipr | ||||||
|  |  | ||||||
|  | # Miscellaneous files thzt should not be checked in | ||||||
|  | temp/ | ||||||
|  |  | ||||||
|  | .DS_Store | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,20 +1,22 @@ | |||||||
| 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 ibm-semeru-runtimes:open-11-jre | ||||||
| RUN apt-get update && apt-get install -y gettext-base wget | RUN apt-get update && apt-get install -y gettext-base | ||||||
| RUN wget https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentral-deploy/main/docker-compose/certs/restapi-ca.pem \ | ADD https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentral-deploy/main/docker-compose/certs/restapi-ca.pem \ | ||||||
|     -O /usr/local/share/ca-certificates/restapi-ca-selfsigned.pem |   /usr/local/share/ca-certificates/restapi-ca-selfsigned.pem | ||||||
| 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 runner.sh / | ||||||
| EXPOSE 16789 | COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/ | ||||||
|  | EXPOSE 16789 16790 | ||||||
| ENTRYPOINT ["/docker-entrypoint.sh"] | ENTRYPOINT ["/docker-entrypoint.sh"] | ||||||
| CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \ | ENV JVM_IMPL=openj9 | ||||||
|      "-jar", "/usr/local/bin/openwifi-rrm.jar", \ | CMD ["/runner.sh", "java", "/usr/local/bin/openwifi-rrm.jar", \ | ||||||
|      "run", "--config-env", \ |     "run", \ | ||||||
|      "-t", "/owrrm-data/topology.json", \ |     "--config-env", \ | ||||||
|      "-d", "/owrrm-data/device_config.json"] |     "-t", "/owrrm-data/topology.json", \ | ||||||
|  |     "-d", "/owrrm-data/device_config.json"] | ||||||
|   | |||||||
							
								
								
									
										28
									
								
								Dockerfile-hotspot
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								Dockerfile-hotspot
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | FROM maven:3-eclipse-temurin-11 as build | ||||||
|  | WORKDIR /usr/src/java | ||||||
|  | COPY . . | ||||||
|  | RUN mvn clean package -pl owrrm -am -DappendVersionString="$(./scripts/get_build_version.sh)" | ||||||
|  |  | ||||||
|  | FROM eclipse-temurin:11-jre-jammy | ||||||
|  | RUN apt-get update && apt-get install -y gettext-base | ||||||
|  | ADD https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentral-deploy/main/docker-compose/certs/restapi-ca.pem \ | ||||||
|  |   /usr/local/share/ca-certificates/restapi-ca-selfsigned.pem | ||||||
|  | RUN mkdir /owrrm-data | ||||||
|  | WORKDIR /usr/src/java | ||||||
|  | COPY docker-entrypoint.sh / | ||||||
|  | COPY runner.sh / | ||||||
|  | COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/ | ||||||
|  |  | ||||||
|  | # generate Application CDS | ||||||
|  | RUN java -Xshare:off -XX:DumpLoadedClassList=static-cds.lst -jar /usr/local/bin/openwifi-rrm.jar --help  && \ | ||||||
|  |     java -Xshare:dump -XX:SharedClassListFile=static-cds.lst -XX:SharedArchiveFile=static-cds.jsa -jar /usr/local/bin/openwifi-rrm.jar | ||||||
|  |  | ||||||
|  | EXPOSE 16789 16790 | ||||||
|  | ENTRYPOINT ["/docker-entrypoint.sh"] | ||||||
|  | ENV JVM_IMPL=hotspot | ||||||
|  | ENV EXTRA_JVM_FLAGS="-XX:SharedArchiveFile=static-cds.jsa -Xshare:auto" | ||||||
|  | CMD ["/runner.sh", "java", "/usr/local/bin/openwifi-rrm.jar", \ | ||||||
|  |     "run", \ | ||||||
|  |     "--config-env", \ | ||||||
|  |     "-t", "/owrrm-data/topology.json", \ | ||||||
|  |     "-d", "/owrrm-data/device_config.json"] | ||||||
							
								
								
									
										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,253 @@ | |||||||
|  | /* | ||||||
|  |  * 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.Objects; | ||||||
|  |  | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.Counters; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Aggregation model for State aggregation. Only contains info useful for | ||||||
|  |  * analysis. | ||||||
|  |  */ | ||||||
|  | public class AggregatedState { | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Radio information with channel, channel_width and tx_power. | ||||||
|  | 	 */ | ||||||
|  | 	public static class RadioConfig { | ||||||
|  | 		public int channel; | ||||||
|  | 		public int channelWidth; | ||||||
|  | 		public int txPower; | ||||||
|  | 		public String phy; | ||||||
|  |  | ||||||
|  | 		/** Default constructor with no args */ | ||||||
|  | 		private RadioConfig() {} | ||||||
|  |  | ||||||
|  | 		/** Constructor with args */ | ||||||
|  | 		public RadioConfig(JsonObject radio) { | ||||||
|  | 			this.channel = radio.get("channel").getAsInt(); | ||||||
|  | 			this.channelWidth = radio.get("channel_width").getAsInt(); | ||||||
|  | 			this.txPower = radio.get("tx_power").getAsInt(); | ||||||
|  | 			this.phy = radio.get("phy").getAsString(); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		public RadioConfig(int channel, int channelWidth, int txPower) { | ||||||
|  | 			this.channel = channel; | ||||||
|  | 			this.channelWidth = channelWidth; | ||||||
|  | 			this.txPower = txPower; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@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; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			RadioConfig other = (RadioConfig) obj; | ||||||
|  | 			return channel == other.channel && | ||||||
|  | 				channelWidth == other.channelWidth && txPower == other.txPower; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Data model to keep raw data from {@link State.Interface.SSID.Association}, | ||||||
|  | 	 * {@link State.Radio} and {@link State.Interface.Counters}. | ||||||
|  | 	 */ | ||||||
|  | 	public static class AssociationInfo { | ||||||
|  | 		/** Rate information with aggregated fields. */ | ||||||
|  | 		public static class Rate { | ||||||
|  | 			/** | ||||||
|  | 			 * Aggregated fields bitRate | ||||||
|  | 			 */ | ||||||
|  | 			public long bitRate; | ||||||
|  |  | ||||||
|  | 			/** | ||||||
|  | 			 * Aggregated fields chWidth | ||||||
|  | 			 */ | ||||||
|  | 			public int chWidth; | ||||||
|  |  | ||||||
|  | 			/** | ||||||
|  | 			 * Aggregated fields mcs | ||||||
|  | 			 */ | ||||||
|  | 			public int mcs; | ||||||
|  |  | ||||||
|  | 			/** Constructor with no args */ | ||||||
|  | 			private Rate() {} | ||||||
|  |  | ||||||
|  | 			/** Constructor with args */ | ||||||
|  | 			private Rate(long bitRate, int chWidth, int mcs) { | ||||||
|  | 				this.bitRate = bitRate; | ||||||
|  | 				this.chWidth = chWidth; | ||||||
|  | 				this.mcs = mcs; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		public long connected; | ||||||
|  | 		public long inactive; | ||||||
|  | 		public int rssi; | ||||||
|  | 		public long rxBytes; | ||||||
|  | 		public long rxPackets; | ||||||
|  | 		public Rate rxRate; | ||||||
|  | 		public long txBytes; | ||||||
|  | 		public long txDuration; | ||||||
|  | 		public long txFailed; | ||||||
|  | 		public long txPackets; | ||||||
|  | 		public Rate txRate; | ||||||
|  | 		public long txRetries; | ||||||
|  | 		public int ackSignal; | ||||||
|  | 		public int ackSignalAvg; | ||||||
|  | 		public long txPacketsCounters; | ||||||
|  | 		public long txErrorsCounters; | ||||||
|  | 		public long txDroppedCounters; | ||||||
|  | 		public long activeMsRadio; | ||||||
|  | 		public long busyMsRadio; | ||||||
|  | 		public long noiseRadio; | ||||||
|  | 		public long receiveMsRadio; | ||||||
|  | 		public long transmitMsRadio; | ||||||
|  |  | ||||||
|  | 		/** unix time in ms */ | ||||||
|  | 		public long timestamp; | ||||||
|  |  | ||||||
|  | 		/** Default Constructor. */ | ||||||
|  | 		public AssociationInfo() {} | ||||||
|  |  | ||||||
|  | 		/** Constructor with only rssi(for test purpose) */ | ||||||
|  | 		public AssociationInfo(int rssi) { | ||||||
|  | 			this.rssi = rssi; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** Constructor with args */ | ||||||
|  | 		public AssociationInfo( | ||||||
|  | 			Association association, | ||||||
|  | 			Counters counters, | ||||||
|  | 			JsonObject radio, | ||||||
|  | 			long timestamp | ||||||
|  | 		) { | ||||||
|  | 			// Association info | ||||||
|  | 			connected = association.connected; | ||||||
|  | 			inactive = association.inactive; | ||||||
|  | 			rssi = association.rssi; | ||||||
|  | 			rxBytes = association.rx_bytes; | ||||||
|  | 			rxPackets = association.rx_packets; | ||||||
|  | 			if (association.rx_rate != null) { | ||||||
|  | 				rxRate = new Rate( | ||||||
|  | 					association.rx_rate.bitrate, | ||||||
|  | 					association.rx_rate.chwidth, | ||||||
|  | 					association.rx_rate.mcs | ||||||
|  | 				); | ||||||
|  | 			} else { | ||||||
|  | 				rxRate = new Rate(); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			txBytes = association.tx_bytes; | ||||||
|  | 			txPackets = association.tx_packets; | ||||||
|  |  | ||||||
|  | 			if (association.tx_rate != null) { | ||||||
|  | 				txRate = new Rate( | ||||||
|  | 					association.tx_rate.bitrate, | ||||||
|  | 					association.tx_rate.chwidth, | ||||||
|  | 					association.tx_rate.mcs | ||||||
|  | 				); | ||||||
|  | 			} else { | ||||||
|  | 				txRate = new Rate(); | ||||||
|  | 			} | ||||||
|  | 			txRetries = association.tx_retries; | ||||||
|  | 			ackSignal = association.ack_signal; | ||||||
|  | 			ackSignalAvg = association.ack_signal_avg; | ||||||
|  |  | ||||||
|  | 			//Counters info | ||||||
|  | 			txPacketsCounters = counters.tx_packets; | ||||||
|  | 			txErrorsCounters = counters.tx_errors; | ||||||
|  | 			txDroppedCounters = counters.tx_dropped; | ||||||
|  |  | ||||||
|  | 			// Radio info | ||||||
|  | 			activeMsRadio = radio.get("active_ms").getAsLong(); | ||||||
|  | 			busyMsRadio = radio.get("busy_ms").getAsLong(); | ||||||
|  | 			transmitMsRadio = radio.get("transmit_ms").getAsLong(); | ||||||
|  | 			receiveMsRadio = radio.get("receive_ms").getAsLong(); | ||||||
|  | 			noiseRadio = radio.get("noise").getAsLong(); | ||||||
|  |  | ||||||
|  | 			this.timestamp = timestamp; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	// Aggregate AssociationInfo over bssid, station and RadioConfig. | ||||||
|  | 	public String bssid; | ||||||
|  | 	public String station; | ||||||
|  | 	public RadioConfig radioConfig; | ||||||
|  |  | ||||||
|  | 	// Store a list of AssociationInfo of the same link and radio configuration. */ | ||||||
|  | 	public List<AssociationInfo> associationInfoList; | ||||||
|  |  | ||||||
|  | 	/** Constructor with no args. For test purpose. */ | ||||||
|  | 	public AggregatedState() { | ||||||
|  | 		this.associationInfoList = new ArrayList<>(); | ||||||
|  | 		this.radioConfig = new RadioConfig(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Construct from Association, Counters, Radio and time stamp */ | ||||||
|  | 	public AggregatedState( | ||||||
|  | 		Association association, | ||||||
|  | 		Counters counters, | ||||||
|  | 		JsonObject radio, | ||||||
|  | 		long timestamp | ||||||
|  | 	) { | ||||||
|  | 		this.bssid = association.bssid; | ||||||
|  | 		this.station = association.station; | ||||||
|  | 		this.associationInfoList = new ArrayList<>(); | ||||||
|  | 		associationInfoList | ||||||
|  | 			.add(new AssociationInfo(association, counters, radio, timestamp)); | ||||||
|  | 		this.radioConfig = new RadioConfig(radio); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 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(radioConfig, state.radioConfig); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 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)) { | ||||||
|  | 			associationInfoList.addAll(state.associationInfoList); | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,125 @@ | |||||||
|  | /* | ||||||
|  |  * 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 com.google.gson.JsonElement; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Utility functions for dealing with IEs | ||||||
|  |  */ | ||||||
|  | public abstract class IEUtils { | ||||||
|  | 	/** | ||||||
|  | 	 * Try to get a json object as a byte | ||||||
|  | 	 * | ||||||
|  | 	 * @param contents the JSON object to try to parse | ||||||
|  | 	 * @param fieldName the field name | ||||||
|  | 	 * @return the field as a byte or null | ||||||
|  | 	 */ | ||||||
|  | 	public static Byte parseOptionalByteField( | ||||||
|  | 		JsonObject contents, | ||||||
|  | 		String fieldName | ||||||
|  | 	) { | ||||||
|  | 		JsonElement element = contents.get(fieldName); | ||||||
|  | 		if (element == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		return element.getAsByte(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Try to get a json object as a short | ||||||
|  | 	 * | ||||||
|  | 	 * @param contents the JSON object to try to parse | ||||||
|  | 	 * @param fieldName the field name | ||||||
|  | 	 * @return the field as a short or null | ||||||
|  | 	 */ | ||||||
|  | 	public static Short parseOptionalShortField( | ||||||
|  | 		JsonObject contents, | ||||||
|  | 		String fieldName | ||||||
|  | 	) { | ||||||
|  | 		JsonElement element = contents.get(fieldName); | ||||||
|  | 		if (element == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		return element.getAsShort(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Try to get a json object as a int | ||||||
|  | 	 * | ||||||
|  | 	 * @param contents the JSON object to try to parse | ||||||
|  | 	 * @param fieldName the field name | ||||||
|  | 	 * @return the field as a int or null | ||||||
|  | 	 */ | ||||||
|  | 	public static Integer parseOptionalIntField( | ||||||
|  | 		JsonObject contents, | ||||||
|  | 		String fieldName | ||||||
|  | 	) { | ||||||
|  | 		JsonElement element = contents.get(fieldName); | ||||||
|  | 		if (element == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		return element.getAsInt(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Try to get a json object as a int | ||||||
|  | 	 * | ||||||
|  | 	 * @param contents the JSON object to try to parse | ||||||
|  | 	 * @param fieldName the field name | ||||||
|  | 	 * @return the field as a int (0 if key not present) | ||||||
|  | 	 */ | ||||||
|  | 	public static Integer parseIntField( | ||||||
|  | 		JsonObject contents, | ||||||
|  | 		String fieldName | ||||||
|  | 	) { | ||||||
|  | 		JsonElement element = contents.get(fieldName); | ||||||
|  | 		if (element == null) { | ||||||
|  | 			return 0; | ||||||
|  | 		} | ||||||
|  | 		return element.getAsInt(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Try to get a json object as a string | ||||||
|  | 	 * | ||||||
|  | 	 * @param contents the JSON object to try to parse | ||||||
|  | 	 * @param fieldName the field name | ||||||
|  | 	 * @return the field as a string or null | ||||||
|  | 	 */ | ||||||
|  | 	public static String parseOptionalStringField( | ||||||
|  | 		JsonObject contents, | ||||||
|  | 		String fieldName | ||||||
|  | 	) { | ||||||
|  | 		JsonElement element = contents.get(fieldName); | ||||||
|  | 		if (element == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		return element.getAsString(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Try to get a json object as a boolean when represented as a number (0, 1) | ||||||
|  | 	 * | ||||||
|  | 	 * @param contents the JSON object to try to parse | ||||||
|  | 	 * @param fieldName the field name | ||||||
|  | 	 * @return the field as a boolean (false if key not present) | ||||||
|  | 	 */ | ||||||
|  | 	public static boolean parseBooleanNumberField( | ||||||
|  | 		JsonObject contents, | ||||||
|  | 		String fieldName | ||||||
|  | 	) { | ||||||
|  | 		JsonElement element = contents.get(fieldName); | ||||||
|  | 		if (element == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		return element.getAsInt() > 0; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | /* | ||||||
|  |  * 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.RMEnabledCapabilities; | ||||||
|  | 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; | ||||||
|  | 	public RMEnabledCapabilities rmEnabledCapabilities; | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash( | ||||||
|  | 			country, | ||||||
|  | 			localPowerConstraint, | ||||||
|  | 			qbssLoad, | ||||||
|  | 			rmEnabledCapabilities, | ||||||
|  | 			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(rmEnabledCapabilities, other.rmEnabledCapabilities) && | ||||||
|  | 			Objects.equals(txPwrInfo, other.txPwrInfo); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,19 @@ | |||||||
|  | /* | ||||||
|  |  * 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 com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
|  |  | ||||||
|  | public class StateInfo extends State { | ||||||
|  | 	/** | ||||||
|  | 	 * Unix time in milliseconds (ms). This is added it because State.unit.localtime is an unknown | ||||||
|  | 	 * time reference. | ||||||
|  | 	 */ | ||||||
|  | 	public long timestamp; | ||||||
|  | } | ||||||
| @@ -6,15 +6,20 @@ | |||||||
|  * 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.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
| import java.util.HashSet; | import java.util.HashSet; | ||||||
|  | import java.util.List; | ||||||
| import java.util.Set; | import java.util.Set; | ||||||
| 
 | 
 | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.UCentralSchema; | ||||||
| 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; | ||||||
| import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||||||
|  | import com.google.gson.reflect.TypeToken; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Wrapper around uCentral AP configuration. |  * Wrapper around uCentral AP configuration. | ||||||
| @@ -51,33 +56,33 @@ public class UCentralApConfiguration { | |||||||
| 		return config.getAsJsonArray("radios").size(); | 		return config.getAsJsonArray("radios").size(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Return all info in the radio config (or an empty array if none). */ | 	/** Return all info in the radio config (or an empty list if none). */ | ||||||
| 	public JsonArray getRadioConfigList() { | 	public List<UCentralSchema.Radio> getRadioConfigList() { | ||||||
| 		if (!config.has("radios") || !config.get("radios").isJsonArray()) { | 		if (config.has("radios") && config.get("radios").isJsonArray()) { | ||||||
| 			return new JsonArray(); | 			List<UCentralSchema.Radio> radios = new Gson().fromJson( | ||||||
|  | 				config.getAsJsonArray("radios"), | ||||||
|  | 				new TypeToken<ArrayList<UCentralSchema.Radio>>() {}.getType() | ||||||
|  | 			); | ||||||
|  | 			if (radios != null) { | ||||||
|  | 				return radios; | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 		return config.getAsJsonArray("radios"); | 		return Collections.emptyList(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Return all the operational bands of an AP (from the radio config) */ | 	/** Return all the operational bands of an AP (from the radio config) */ | ||||||
| 	public Set<String> getRadioBandsSet(JsonArray radioConfigList) { | 	public Set<String> getRadioBandsSet( | ||||||
|  | 		List<UCentralSchema.Radio> radioConfigList | ||||||
|  | 	) { | ||||||
| 		Set<String> radioBandsSet = new HashSet<>(); | 		Set<String> radioBandsSet = new HashSet<>(); | ||||||
| 		if (radioConfigList == null) { | 		if (radioConfigList == null) { | ||||||
| 			return radioBandsSet; | 			return radioBandsSet; | ||||||
| 		} | 		} | ||||||
| 		for ( | 		for (UCentralSchema.Radio radio : radioConfigList) { | ||||||
| 			int radioIndex = 0; radioIndex < radioConfigList.size(); | 			if (radio == null || radio.band == null) { | ||||||
| 			radioIndex++ |  | ||||||
| 		) { |  | ||||||
| 			JsonElement e = radioConfigList.get(radioIndex); |  | ||||||
| 			if (!e.isJsonObject()) { |  | ||||||
| 				continue; | 				continue; | ||||||
| 			} | 			} | ||||||
| 			JsonObject radioObject = e.getAsJsonObject(); | 			radioBandsSet.add(radio.band); | ||||||
| 			if (!radioObject.has("band")) { |  | ||||||
| 				continue; |  | ||||||
| 			} |  | ||||||
| 			radioBandsSet.add(radioObject.get("band").getAsString()); |  | ||||||
| 		} | 		} | ||||||
| 		return radioBandsSet; | 		return radioBandsSet; | ||||||
| 	} | 	} | ||||||
| @@ -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,22 @@ 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.ScriptRequest; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent; | import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords; | import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults; | import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult; | import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult; | ||||||
| import com.facebook.openwifirrm.ucentral.gw.models.WifiScanRequest; | import com.facebook.openwifi.cloudsdk.models.gw.WifiScanRequest; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.EntityList; | import com.facebook.openwifi.cloudsdk.models.prov.EntityList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList; | import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails; | import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList; | import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList; | ||||||
| import com.facebook.openwifirrm.ucentral.prov.models.VenueList; | import com.facebook.openwifi.cloudsdk.models.prov.VenueList; | ||||||
| import com.google.gson.Gson; | import com.google.gson.Gson; | ||||||
| import com.google.gson.JsonSyntaxException; | import com.google.gson.JsonSyntaxException; | ||||||
| 
 | 
 | ||||||
| @@ -127,8 +127,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 +153,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 +163,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 +317,8 @@ public class UCentralClient { | |||||||
| 			endpoint, | 			endpoint, | ||||||
| 			service, | 			service, | ||||||
| 			parameters, | 			parameters, | ||||||
| 			socketParams.connectTimeoutMs, | 			connectTimeoutMs, | ||||||
| 			socketParams.socketTimeoutMs | 			socketTimeoutMs | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -349,8 +361,8 @@ public class UCentralClient { | |||||||
| 			endpoint, | 			endpoint, | ||||||
| 			service, | 			service, | ||||||
| 			body, | 			body, | ||||||
| 			socketParams.connectTimeoutMs, | 			connectTimeoutMs, | ||||||
| 			socketParams.socketTimeoutMs | 			socketTimeoutMs | ||||||
| 		); | 		); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @@ -454,8 +466,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()); | ||||||
| @@ -551,6 +563,71 @@ public class UCentralClient { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Run a shell script on a device and return the result, or null upon error. | ||||||
|  | 	 * | ||||||
|  | 	 * @see #runScript(String, String, int) | ||||||
|  | 	 */ | ||||||
|  | 	public CommandInfo runScript(String serialNumber, String script) { | ||||||
|  | 		return runScript(serialNumber, script, 30); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Run a shell script on a device and return the result, or null upon error. | ||||||
|  | 	 * | ||||||
|  | 	 * @see #runScript(String, String, int, String) | ||||||
|  | 	 */ | ||||||
|  | 	public CommandInfo runScript( | ||||||
|  | 		String serialNumber, | ||||||
|  | 		String script, | ||||||
|  | 		int timeoutSec | ||||||
|  | 	) { | ||||||
|  | 		return runScript(serialNumber, script, timeoutSec, "shell"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * Run a script on a device and return the result, or null upon error. | ||||||
|  | 	 * | ||||||
|  | 	 * @param serialNumber the device | ||||||
|  | 	 * @param script the script contents | ||||||
|  | 	 * @param timeoutSec the timeout in seconds | ||||||
|  | 	 * @param type the script type (either "shell" or "ucode") | ||||||
|  | 	 * | ||||||
|  | 	 * @see UCentralUtils#getScriptOutput(CommandInfo) | ||||||
|  | 	 */ | ||||||
|  | 	public CommandInfo runScript( | ||||||
|  | 		String serialNumber, | ||||||
|  | 		String script, | ||||||
|  | 		int timeoutSec, | ||||||
|  | 		String type | ||||||
|  | 	) { | ||||||
|  | 		ScriptRequest req = new ScriptRequest(); | ||||||
|  | 		req.serialNumber = serialNumber; | ||||||
|  | 		req.timeout = timeoutSec; | ||||||
|  | 		req.type = type; | ||||||
|  | 		req.script = script; | ||||||
|  | 		req.scriptId = "1"; // ?? | ||||||
|  | 		HttpResponse<String> response = httpPost( | ||||||
|  | 			String.format("device/%s/script", serialNumber), | ||||||
|  | 			OWGW_SERVICE, | ||||||
|  | 			req | ||||||
|  | 		); | ||||||
|  | 		if (!response.isSuccess()) { | ||||||
|  | 			logger.error("Error: {}", response.getBody()); | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		try { | ||||||
|  | 			return gson.fromJson(response.getBody(), CommandInfo.class); | ||||||
|  | 		} catch (JsonSyntaxException e) { | ||||||
|  | 			String errMsg = String.format( | ||||||
|  | 				"Failed to deserialize to CommandInfo: %s", | ||||||
|  | 				response.getBody() | ||||||
|  | 			); | ||||||
|  | 			logger.error(errMsg, e); | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	/** Retrieve a list of inventory from owprov. */ | 	/** Retrieve a list of inventory from owprov. */ | ||||||
| 	public InventoryTagList getProvInventory() { | 	public InventoryTagList getProvInventory() { | ||||||
| 		HttpResponse<String> response = httpGet("inventory", OWPROV_SERVICE); | 		HttpResponse<String> response = httpGet("inventory", OWPROV_SERVICE); | ||||||
| @@ -613,6 +690,19 @@ public class UCentralClient { | |||||||
| 		try { | 		try { | ||||||
| 			return gson.fromJson(response.getBody(), RRMDetails.class); | 			return gson.fromJson(response.getBody(), RRMDetails.class); | ||||||
| 		} catch (JsonSyntaxException e) { | 		} catch (JsonSyntaxException e) { | ||||||
|  | 			// catch strings like "no", "inherit", "invalid" (???) | ||||||
|  | 			JSONObject respBody; | ||||||
|  | 			try { | ||||||
|  | 				respBody = new JSONObject(response.getBody()); | ||||||
|  | 				respBody.getString("rrm"); | ||||||
|  | 				logger.error( | ||||||
|  | 					"RRMDetails returned unexpected string body: {}", | ||||||
|  | 					respBody | ||||||
|  | 				); | ||||||
|  | 				return null; | ||||||
|  | 			} catch (JSONException e2) { /* ignore and fall through */} | ||||||
|  | 
 | ||||||
|  | 			// otherwise, log as a deserialization error | ||||||
| 			String errMsg = String.format( | 			String errMsg = String.format( | ||||||
| 				"Failed to deserialize to RRMDetails: %s", | 				"Failed to deserialize to RRMDetails: %s", | ||||||
| 				response.getBody() | 				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)); | ||||||
| 
 | 
 | ||||||
| @@ -0,0 +1,590 @@ | |||||||
|  | /* | ||||||
|  |  * 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.nio.charset.StandardCharsets; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Base64; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.HashMap; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Map.Entry; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.zip.DataFormatException; | ||||||
|  | import java.util.zip.Inflater; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | 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.RMEnabledCapabilities; | ||||||
|  | import com.facebook.openwifi.cloudsdk.ies.TxPwrInfo; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.Capabilities; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.State; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.ap.UCentralSchema; | ||||||
|  | import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo; | ||||||
|  | import com.google.gson.Gson; | ||||||
|  | import com.google.gson.JsonArray; | ||||||
|  | import com.google.gson.JsonElement; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * uCentral utility methods/structures. | ||||||
|  |  */ | ||||||
|  | public class UCentralUtils { | ||||||
|  | 	private static final Logger logger = | ||||||
|  | 		LoggerFactory.getLogger(UCentralUtils.class); | ||||||
|  |  | ||||||
|  | 	/** Information Element (IE) content field key */ | ||||||
|  | 	private static final String IE_CONTENT_FIELD_KEY = "content"; | ||||||
|  |  | ||||||
|  | 	/** The Gson instance. */ | ||||||
|  | 	private static final Gson gson = new Gson(); | ||||||
|  |  | ||||||
|  | 	/** Map from band to ordered (increasing) list of available channels */ | ||||||
|  | 	public static final Map<String, List<Integer>> AVAILABLE_CHANNELS_BAND = | ||||||
|  | 		Collections | ||||||
|  | 			.unmodifiableMap(buildBandToChannelsMap()); | ||||||
|  |  | ||||||
|  | 	// This class should not be instantiated. | ||||||
|  | 	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. | ||||||
|  | 	 * | ||||||
|  | 	 * @param result      result of the wifiscan | ||||||
|  | 	 * @param timestampMs Unix time in ms | ||||||
|  | 	 * @return list of wifiscan entries, or null if any parsing/deserialization | ||||||
|  | 	 *         error occurred. | ||||||
|  | 	 */ | ||||||
|  | 	public static List<WifiScanEntry> parseWifiScanEntries( | ||||||
|  | 		JsonObject result, | ||||||
|  | 		long timestampMs | ||||||
|  | 	) { | ||||||
|  | 		List<WifiScanEntry> entries = new ArrayList<>(); | ||||||
|  | 		try { | ||||||
|  | 			JsonArray scanInfo = result | ||||||
|  | 				.getAsJsonObject("status") | ||||||
|  | 				.getAsJsonArray("scan"); | ||||||
|  | 			for (JsonElement e : scanInfo) { | ||||||
|  | 				WifiScanEntry entry = gson.fromJson(e, WifiScanEntry.class); | ||||||
|  | 				entry.unixTimeMs = timestampMs; | ||||||
|  | 				extractIEs(e, entry); | ||||||
|  | 				entries.add(entry); | ||||||
|  | 			} | ||||||
|  | 		} catch (Exception e) { | ||||||
|  | 			logger.debug("Exception when parsing wifiscan entries", e); | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		return entries; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Extract desired information elements (IEs) from the wifiscan entry. | ||||||
|  | 	 * Modifies {@code entry} argument. Skips invalid IEs (IEs with missing | ||||||
|  | 	 * fields). | ||||||
|  | 	 */ | ||||||
|  | 	private static void extractIEs( | ||||||
|  | 		JsonElement entryJsonElement, | ||||||
|  | 		WifiScanEntry entry | ||||||
|  | 	) { | ||||||
|  | 		JsonElement iesJsonElement = | ||||||
|  | 			entryJsonElement.getAsJsonObject().get("ies"); | ||||||
|  | 		if (iesJsonElement == null) { | ||||||
|  | 			logger.debug("Wifiscan entry does not contain 'ies' field."); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		JsonArray iesJsonArray = iesJsonElement.getAsJsonArray(); | ||||||
|  | 		InformationElements ieContainer = new InformationElements(); | ||||||
|  | 		for (JsonElement ieJsonElement : iesJsonArray) { | ||||||
|  | 			JsonElement typeElement = | ||||||
|  | 				ieJsonElement.getAsJsonObject().get("type"); | ||||||
|  | 			if (typeElement == null) { // shouldn't happen | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			if (!ieJsonElement.isJsonObject()) { | ||||||
|  | 				// the IEs we are interested in are Json objects | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			JsonObject ie = ieJsonElement.getAsJsonObject(); | ||||||
|  | 			JsonElement contentsJsonElement = ie.get(IE_CONTENT_FIELD_KEY); | ||||||
|  | 			if ( | ||||||
|  | 				contentsJsonElement == null || | ||||||
|  | 					!contentsJsonElement.isJsonObject() | ||||||
|  | 			) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			JsonObject contents = contentsJsonElement.getAsJsonObject(); | ||||||
|  | 			try { | ||||||
|  | 				switch (typeElement.getAsInt()) { | ||||||
|  | 				case Country.TYPE: | ||||||
|  | 					ieContainer.country = Country.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				case QbssLoad.TYPE: | ||||||
|  | 					ieContainer.qbssLoad = QbssLoad.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				case LocalPowerConstraint.TYPE: | ||||||
|  | 					ieContainer.localPowerConstraint = | ||||||
|  | 						LocalPowerConstraint.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				case TxPwrInfo.TYPE: | ||||||
|  | 					ieContainer.txPwrInfo = TxPwrInfo.parse(contents); | ||||||
|  | 					break; | ||||||
|  | 				case RMEnabledCapabilities.TYPE: | ||||||
|  | 					ieContainer.rmEnabledCapabilities = | ||||||
|  | 						RMEnabledCapabilities.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. | ||||||
|  | 	 * | ||||||
|  | 	 * Returns true if changed, or false if unchanged for any reason. | ||||||
|  | 	 */ | ||||||
|  | 	public static boolean setRadioConfigField( | ||||||
|  | 		String serialNumber, | ||||||
|  | 		UCentralApConfiguration config, | ||||||
|  | 		String fieldName, | ||||||
|  | 		Map<String, Integer> newValueList | ||||||
|  | 	) { | ||||||
|  | 		boolean wasModified = false; | ||||||
|  | 		int radioCount = config.getRadioCount(); | ||||||
|  |  | ||||||
|  | 		// Iterate all the radios of an AP to find the corresponding band | ||||||
|  | 		for (int radioIndex = 0; radioIndex < radioCount; radioIndex++) { | ||||||
|  | 			JsonObject radioConfig = config.getRadioConfig(radioIndex); | ||||||
|  | 			if (radioConfig == null) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			String operationalBand = radioConfig.get("band").getAsString(); | ||||||
|  | 			if (!newValueList.containsKey(operationalBand)) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// If the field doesn't exist in config, we generate the fieldName and | ||||||
|  | 			// assign the new value to it. | ||||||
|  | 			int newValue = newValueList.get(operationalBand); | ||||||
|  | 			if (!radioConfig.has(fieldName)) { | ||||||
|  | 				radioConfig.addProperty(fieldName, newValue); | ||||||
|  | 				config.setRadioConfig(radioIndex, radioConfig); | ||||||
|  | 				logger.info( | ||||||
|  | 					"Device {}: setting {} {} to {} (was empty)", | ||||||
|  | 					serialNumber, | ||||||
|  | 					operationalBand, | ||||||
|  | 					fieldName, | ||||||
|  | 					newValue | ||||||
|  | 				); | ||||||
|  | 				wasModified = true; | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Compare vs. existing value. | ||||||
|  | 			// not all values are int so override those values | ||||||
|  | 			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( | ||||||
|  | 					"Device {}: {} {} is already {}", | ||||||
|  | 					serialNumber, | ||||||
|  | 					operationalBand, | ||||||
|  | 					fieldName, | ||||||
|  | 					newValue | ||||||
|  | 				); | ||||||
|  | 			} else { | ||||||
|  | 				// Update to new value | ||||||
|  | 				radioConfig.addProperty(fieldName, newValue); | ||||||
|  | 				config.setRadioConfig(radioIndex, radioConfig); | ||||||
|  | 				logger.info( | ||||||
|  | 					"Device {}: setting {} {} to {} (was {})", | ||||||
|  | 					serialNumber, | ||||||
|  | 					operationalBand, | ||||||
|  | 					fieldName, | ||||||
|  | 					newValue, | ||||||
|  | 					currentValue != null ? currentValue : fieldValue.toString() | ||||||
|  | 				); | ||||||
|  | 				wasModified = true; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return wasModified; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Get the APs on a band who participate in an optimization algorithm. | ||||||
|  | 	 * Get the info from the configuration field in deviceStatus | ||||||
|  | 	 * (Since the State doesn't explicitly show the "band" info) | ||||||
|  | 	 * | ||||||
|  | 	 * Returns the results map | ||||||
|  | 	 */ | ||||||
|  | 	public static Map<String, List<String>> getBandsMap( | ||||||
|  | 		Map<String, List<UCentralSchema.Radio>> deviceStatus | ||||||
|  | 	) { | ||||||
|  | 		Map<String, List<String>> bandsMap = new HashMap<>(); | ||||||
|  | 		for ( | ||||||
|  | 			Map.Entry<String, List<UCentralSchema.Radio>> entry : deviceStatus | ||||||
|  | 				.entrySet() | ||||||
|  | 		) { | ||||||
|  | 			String serialNumber = entry.getKey(); | ||||||
|  | 			for (UCentralSchema.Radio radio : entry.getValue()) { | ||||||
|  | 				bandsMap | ||||||
|  | 					.computeIfAbsent(radio.band, k -> new ArrayList<>()) | ||||||
|  | 					.add(serialNumber); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return bandsMap; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Get the capabilities of the APs who participate in an optimization algorithm. | ||||||
|  | 	 * | ||||||
|  | 	 * @param deviceStatus map of {device, status} | ||||||
|  | 	 * @param deviceCapabilities map of {device, capabilities info} | ||||||
|  | 	 * @param defaultAvailableChannels map of {band, list of available channels} | ||||||
|  | 	 * | ||||||
|  | 	 * @return the results map of {band, {device, list of available channels}} | ||||||
|  | 	 */ | ||||||
|  | 	public static Map<String, Map<String, List<Integer>>> getDeviceAvailableChannels( | ||||||
|  | 		Map<String, List<UCentralSchema.Radio>> deviceStatus, | ||||||
|  | 		Map<String, Map<String, Capabilities.Phy>> deviceCapabilities, | ||||||
|  | 		Map<String, List<Integer>> defaultAvailableChannels | ||||||
|  | 	) { | ||||||
|  | 		Map<String, Map<String, List<Integer>>> deviceAvailableChannels = | ||||||
|  | 			new HashMap<>(); | ||||||
|  |  | ||||||
|  | 		for ( | ||||||
|  | 			Map.Entry<String, List<UCentralSchema.Radio>> entry : deviceStatus | ||||||
|  | 				.entrySet() | ||||||
|  | 		) { | ||||||
|  | 			String serialNumber = entry.getKey(); | ||||||
|  | 			for (UCentralSchema.Radio radio : entry.getValue()) { | ||||||
|  | 				Map<String, Capabilities.Phy> capabilitiesPhyMap = | ||||||
|  | 					deviceCapabilities.get(serialNumber); | ||||||
|  | 				List<Integer> availableChannels = new ArrayList<>(); | ||||||
|  | 				if (capabilitiesPhyMap == null) { | ||||||
|  | 					availableChannels | ||||||
|  | 						.addAll(defaultAvailableChannels.get(radio.band)); | ||||||
|  | 				} else { | ||||||
|  | 					Set<Entry<String, Capabilities.Phy>> entrySet = | ||||||
|  | 						capabilitiesPhyMap | ||||||
|  | 							.entrySet(); | ||||||
|  | 					for (Map.Entry<String, Capabilities.Phy> f : entrySet) { | ||||||
|  | 						Capabilities.Phy phy = f.getValue(); | ||||||
|  | 						String bandInsideObject = phy.band.toString(); | ||||||
|  | 						if (bandInsideObject.equals(radio.band)) { | ||||||
|  | 							// (TODO) Remove the following dfsChannels code block | ||||||
|  | 							// when the DFS channels are available | ||||||
|  | 							Set<Integer> dfsChannels = new HashSet<>(); | ||||||
|  | 							try { | ||||||
|  | 								int[] channelInfo = phy.dfs_channels; | ||||||
|  | 								for (int d : channelInfo) { | ||||||
|  | 									dfsChannels.add(d); | ||||||
|  | 								} | ||||||
|  | 							} catch (Exception d) {} | ||||||
|  | 							try { | ||||||
|  | 								int[] channelInfo = phy.channels; | ||||||
|  | 								for (int channel : channelInfo) { | ||||||
|  | 									if (!dfsChannels.contains(channel)) { | ||||||
|  | 										availableChannels.add(channel); | ||||||
|  | 									} | ||||||
|  | 								} | ||||||
|  | 							} catch (Exception c) { | ||||||
|  | 								availableChannels | ||||||
|  | 									.addAll( | ||||||
|  | 										defaultAvailableChannels.get(radio.band) | ||||||
|  | 									); | ||||||
|  | 							} | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				deviceAvailableChannels.computeIfAbsent( | ||||||
|  | 					radio.band, | ||||||
|  | 					k -> new HashMap<>() | ||||||
|  | 				) | ||||||
|  | 					.put( | ||||||
|  | 						serialNumber, | ||||||
|  | 						availableChannels | ||||||
|  | 					); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return deviceAvailableChannels; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Get the mapping between bssids and APs. | ||||||
|  | 	 * Get the info from the State data | ||||||
|  | 	 * | ||||||
|  | 	 * Returns the results map | ||||||
|  | 	 */ | ||||||
|  | 	public static Map<String, String> getBssidsMap( | ||||||
|  | 		Map<String, ? extends State> latestState | ||||||
|  | 	) { | ||||||
|  | 		Map<String, String> bssidMap = new HashMap<>(); | ||||||
|  | 		for (Entry<String, ? extends State> e : latestState.entrySet()) { | ||||||
|  | 			State state = e.getValue(); | ||||||
|  | 			for ( | ||||||
|  | 				int interfaceIndex = 0; | ||||||
|  | 				interfaceIndex < state.interfaces.length; | ||||||
|  | 				interfaceIndex++ | ||||||
|  | 			) { | ||||||
|  | 				if (state.interfaces[interfaceIndex].ssids == null) { | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  | 				for ( | ||||||
|  | 					int ssidIndex = 0; | ||||||
|  | 					ssidIndex < state.interfaces[interfaceIndex].ssids.length; | ||||||
|  | 					ssidIndex++ | ||||||
|  | 				) { | ||||||
|  | 					bssidMap.put( | ||||||
|  | 						state.interfaces[interfaceIndex].ssids[ssidIndex].bssid, | ||||||
|  | 						e.getKey() | ||||||
|  | 					); | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		return bssidMap; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Determines if the given channel is in the given band. | ||||||
|  | 	 * | ||||||
|  | 	 * @param channel channel number | ||||||
|  | 	 * @param band    "2G" or "5G" | ||||||
|  | 	 * @return true if the given channel is in the given band; false otherwise | ||||||
|  | 	 */ | ||||||
|  | 	public static boolean isChannelInBand(int channel, String band) { | ||||||
|  | 		return AVAILABLE_CHANNELS_BAND.get(band).contains(channel); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Return which band contains the given frequency (MHz). */ | ||||||
|  | 	public static String freqToBand(int freqMHz) { | ||||||
|  | 		if (2412 <= freqMHz && freqMHz <= 2484) { | ||||||
|  | 			return "2G"; | ||||||
|  | 		} else { | ||||||
|  | 			return "5G"; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Tries to parse channel width, if it encounters an error it will return null. | ||||||
|  | 	 * 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 channelWidthStr the channel width | ||||||
|  | 	 * @param treatSeparate treats each band separately | ||||||
|  | 	 * @return channel width in MHz | ||||||
|  | 	 */ | ||||||
|  | 	public static Integer parseChannelWidth( | ||||||
|  | 		String channelWidthStr, | ||||||
|  | 		boolean treatSeparate | ||||||
|  | 	) { | ||||||
|  | 		// 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; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * 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; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Return a map of Wi-Fi client (STA) MAC addresses to the Client structure | ||||||
|  | 	 * found for that interface. This does NOT support clients connected on | ||||||
|  | 	 * multiple interfaces simultaneously. | ||||||
|  | 	 */ | ||||||
|  | 	public static Map<String, State.Interface.Client> getWifiClientInfo( | ||||||
|  | 		State state | ||||||
|  | 	) { | ||||||
|  | 		Map<String, State.Interface.Client> ret = new HashMap<>(); | ||||||
|  |  | ||||||
|  | 		// Aggregate over all interfaces | ||||||
|  | 		for (State.Interface iface : state.interfaces) { | ||||||
|  | 			if (iface.ssids == null || iface.clients == null) { | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Convert client array to map (for faster lookups) | ||||||
|  | 			Map<String, State.Interface.Client> ifaceMap = new HashMap<>(); | ||||||
|  | 			for (State.Interface.Client client : iface.clients) { | ||||||
|  | 				ifaceMap.put(client.mac, client); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			// Loop over all SSIDs and connected clients | ||||||
|  | 			for (State.Interface.SSID ssid : iface.ssids) { | ||||||
|  | 				if (ssid.associations == null) { | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  | 				for ( | ||||||
|  | 					State.Interface.SSID.Association association : ssid.associations | ||||||
|  | 				) { | ||||||
|  | 					State.Interface.Client client = | ||||||
|  | 						ifaceMap.get(association.station); | ||||||
|  | 					if (client != null) { | ||||||
|  | 						ret.put(association.station, client); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return ret; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Decompress (inflate) a UTF-8 string using ZLIB. | ||||||
|  | 	 * | ||||||
|  | 	 * @param compressed the compressed string | ||||||
|  | 	 * @param uncompressedSize the uncompressed size (must be known) | ||||||
|  | 	 */ | ||||||
|  | 	private static String inflate(String compressed, int uncompressedSize) | ||||||
|  | 		throws DataFormatException { | ||||||
|  | 		if (compressed == null) { | ||||||
|  | 			throw new NullPointerException("Null compressed string"); | ||||||
|  | 		} | ||||||
|  | 		if (uncompressedSize < 0) { | ||||||
|  | 			throw new IllegalArgumentException("Invalid size"); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		byte[] input = compressed.getBytes(StandardCharsets.UTF_8); | ||||||
|  | 		byte[] output = new byte[uncompressedSize]; | ||||||
|  |  | ||||||
|  | 		Inflater inflater = new Inflater(); | ||||||
|  | 		inflater.setInput(input, 0, input.length); | ||||||
|  | 		inflater.inflate(output); | ||||||
|  | 		inflater.end(); | ||||||
|  |  | ||||||
|  | 		return new String(output, StandardCharsets.UTF_8); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Given the result of the "script" API, return the actual script output | ||||||
|  | 	 * (decoded/decompressed if needed), or null if the script returned an | ||||||
|  | 	 * error. | ||||||
|  | 	 * | ||||||
|  | 	 * @see UCentralClient#runScript(String, String, int, String) | ||||||
|  | 	 */ | ||||||
|  | 	public static String getScriptOutput(CommandInfo info) { | ||||||
|  | 		if (info == null || info.results == null) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		if (!info.results.has("status")) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		JsonObject status = info.results.get("status").getAsJsonObject(); | ||||||
|  | 		if (!status.has("error")) { | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		int errorCode = status.get("error").getAsInt(); | ||||||
|  | 		if (errorCode != 0) { | ||||||
|  | 			logger.error("Script failed with code {}", errorCode); | ||||||
|  | 			return null; | ||||||
|  | 		} | ||||||
|  | 		if (status.has("result")) { | ||||||
|  | 			// Raw result | ||||||
|  | 			return status.get("result").getAsString(); | ||||||
|  | 		} else if (status.has("result_64") && status.has("result_sz")) { | ||||||
|  | 			// Base64+compressed result | ||||||
|  | 			// NOTE: untested, not actually implemented on ucentral-client? | ||||||
|  | 			try { | ||||||
|  | 				String encoded = status.get("result_64").getAsString(); | ||||||
|  | 				int uncompressedSize = status.get("result_sz").getAsInt(); | ||||||
|  | 				String decoded = new String( | ||||||
|  | 					Base64.getDecoder().decode(encoded), | ||||||
|  | 					StandardCharsets.UTF_8 | ||||||
|  | 				); | ||||||
|  | 				return inflate(decoded, uncompressedSize); | ||||||
|  | 			} catch (Exception e) { | ||||||
|  | 				logger.error("Failed to decode or inflate script result", 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,155 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called "BSS AC Access Delay" in | ||||||
|  |  * 802.11 specs (section 9.4.2.43). Refer to the specification for more details. | ||||||
|  |  * Language in javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class BssAcAccessDelay { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 68; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Subfield that goes into Access Category Access Delay field in BSS AC | ||||||
|  | 	 * Access Delay. For information on what the values mean, check section | ||||||
|  | 	 * 9.4.2.43 | ||||||
|  | 	 */ | ||||||
|  | 	public static class AccessCategoryAccessDelay { | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned 8 bits that represents a scaled representation of best effort AC | ||||||
|  | 		 * access delay | ||||||
|  | 		 */ | ||||||
|  | 		public final short averageAccessDelayForBestEffort; | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned 8 bits that represents a scaled representation of background AC | ||||||
|  | 		 * access delay | ||||||
|  | 		 */ | ||||||
|  | 		public final short averageAccessDelayForBackground; | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned 8 bits that represents a scaled representation of video AC access | ||||||
|  | 		 * delay | ||||||
|  | 		 */ | ||||||
|  | 		public final short averageAccessDelayForVideo; | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned 8 bits that represents a scaled representation of voice AC access | ||||||
|  | 		 * delay | ||||||
|  | 		 */ | ||||||
|  | 		public final short averageAccessDelayForVoice; | ||||||
|  |  | ||||||
|  | 		/** Constructor */ | ||||||
|  | 		public AccessCategoryAccessDelay( | ||||||
|  | 			short averageAccessDelayForBestEffort, | ||||||
|  | 			short averageAccessDelayForBackground, | ||||||
|  | 			short averageAccessDelayForVideo, | ||||||
|  | 			short averageAccessDelayForVoice | ||||||
|  | 		) { | ||||||
|  | 			this.averageAccessDelayForBestEffort = | ||||||
|  | 				averageAccessDelayForBestEffort; | ||||||
|  | 			this.averageAccessDelayForBackground = | ||||||
|  | 				averageAccessDelayForBackground; | ||||||
|  | 			this.averageAccessDelayForVideo = averageAccessDelayForVideo; | ||||||
|  | 			this.averageAccessDelayForVoice = averageAccessDelayForVoice; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** Parse AccessCategoryAccessDelay from JSON object */ | ||||||
|  | 		// TODO rename fields as necessary - we don't know how the data format yet | ||||||
|  | 		public static AccessCategoryAccessDelay parse(JsonObject contents) { | ||||||
|  | 			return new AccessCategoryAccessDelay( | ||||||
|  | 				contents.get("Average Access Delay For Best Effort").getAsShort(), | ||||||
|  | 				contents.get("Average Access Delay For Background").getAsShort(), | ||||||
|  | 				contents.get("Average Access Delay For Video").getAsShort(), | ||||||
|  | 				contents.get("Average Access Delay For Voice").getAsShort() | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public int hashCode() { | ||||||
|  | 			return Objects.hash( | ||||||
|  | 				averageAccessDelayForBestEffort, | ||||||
|  | 				averageAccessDelayForBestEffort, | ||||||
|  | 				averageAccessDelayForVideo, | ||||||
|  | 				averageAccessDelayForVoice | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public boolean equals(Object obj) { | ||||||
|  | 			if (obj == null) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (this == obj) { | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (getClass() != obj.getClass()) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			AccessCategoryAccessDelay other = (AccessCategoryAccessDelay) obj; | ||||||
|  | 			return averageAccessDelayForBestEffort == | ||||||
|  | 				other.averageAccessDelayForBestEffort && | ||||||
|  | 				averageAccessDelayForBackground == | ||||||
|  | 					other.averageAccessDelayForBackground && | ||||||
|  | 				averageAccessDelayForVideo == | ||||||
|  | 					other.averageAccessDelayForVideo && | ||||||
|  | 				averageAccessDelayForVoice == other.averageAccessDelayForVoice; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** 32 bits - Holds AccessCategoryAccessDelay subfield */ | ||||||
|  | 	public final AccessCategoryAccessDelay accessCategoryAccessDelay; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public BssAcAccessDelay( | ||||||
|  | 		AccessCategoryAccessDelay accessCategoryAccessDelay | ||||||
|  | 	) { | ||||||
|  | 		this.accessCategoryAccessDelay = accessCategoryAccessDelay; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse BssAcAccessDelay from JSON object */ | ||||||
|  | 	// TODO rename fields as necessary - we don't know how the data format yet | ||||||
|  | 	public static BssAcAccessDelay parse(JsonObject contents) { | ||||||
|  | 		return new BssAcAccessDelay( | ||||||
|  | 			AccessCategoryAccessDelay.parse( | ||||||
|  | 				contents.get("AP Average Access Delay").getAsJsonObject() | ||||||
|  | 			) | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(accessCategoryAccessDelay); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		BssAcAccessDelay other = (BssAcAccessDelay) obj; | ||||||
|  | 		return accessCategoryAccessDelay == other.accessCategoryAccessDelay; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,68 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called "BSS Average Access Delay" in | ||||||
|  |  * 802.11 specs (section 9.4.2.38). Refer to the specification for more details. | ||||||
|  |  * Language in javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class BssAvgAccessDelay { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 63; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits representing a scaled average medium access delay for all DCF | ||||||
|  | 	 * and EDCAF frames transmitted, measured from the time it's ready for | ||||||
|  | 	 * transmission to actual transmission start time. | ||||||
|  | 	 */ | ||||||
|  | 	public final short apAvgAccessDelay; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public BssAvgAccessDelay(short apAvgAccessDelay) { | ||||||
|  | 		this.apAvgAccessDelay = apAvgAccessDelay; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse BssAvgAccessDelay from JSON object */ | ||||||
|  | 	// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 	// present, we have no idea what the format looks like | ||||||
|  | 	public static BssAvgAccessDelay parse(JsonObject contents) { | ||||||
|  | 		return new BssAvgAccessDelay( | ||||||
|  | 			contents.get("AP Average Access Delay").getAsShort() | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(apAvgAccessDelay); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		BssAvgAccessDelay other = (BssAvgAccessDelay) obj; | ||||||
|  | 		return apAvgAccessDelay == other.apAvgAccessDelay; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,87 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called "20/40 | ||||||
|  |  * BSS Intolerant Channel Report" in 802.11 specs (section 9.4.2.57). Refer to | ||||||
|  |  * the specification for more details. Language in javadocs is taken from the | ||||||
|  |  * specification. | ||||||
|  |  */ | ||||||
|  | public class BssIntolerantChannelReport { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 73; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits representing the operating class in which the channel list | ||||||
|  | 	 * is valid | ||||||
|  | 	 */ | ||||||
|  | 	public final short operatingClass; | ||||||
|  | 	/** List of unsigned 8 bits, representing the channel numbers */ | ||||||
|  | 	public final List<Short> channelList; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public BssIntolerantChannelReport( | ||||||
|  | 		short operatingClass, | ||||||
|  | 		List<Short> channelList | ||||||
|  | 	) { | ||||||
|  | 		this.operatingClass = operatingClass; | ||||||
|  | 		this.channelList = Collections.unmodifiableList(channelList); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse BssIntolerantChannelReport from JSON object */ | ||||||
|  | 	// TODO rename fields as necessary - we don't know how the data format yet | ||||||
|  | 	public static BssIntolerantChannelReport parse(JsonObject contents) { | ||||||
|  | 		List<Short> channelList = new ArrayList<>(); | ||||||
|  | 		JsonElement channelListJson = contents.get("Channel List"); | ||||||
|  | 		if (channelListJson != null) { | ||||||
|  | 			for (JsonElement elem : channelListJson.getAsJsonArray()) { | ||||||
|  | 				channelList.add(elem.getAsShort()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return new BssIntolerantChannelReport( | ||||||
|  | 			contents.get("Operating Class").getAsShort(), | ||||||
|  | 			channelList | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(operatingClass, channelList); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		BssIntolerantChannelReport other = (BssIntolerantChannelReport) obj; | ||||||
|  | 		return operatingClass == other.operatingClass && | ||||||
|  | 			channelList.equals(other.channelList); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -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.ies; | ||||||
|  |  | ||||||
|  | import java.util.Objects; | ||||||
|  |  | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called | ||||||
|  |  * "Collocated Interference Report" in 802.11 specs (section 9.4.2.84). Refer to | ||||||
|  |  * the specification for more details. Language in javadocs is taken from the | ||||||
|  |  * specification. | ||||||
|  |  */ | ||||||
|  | public class CollocatedInterferenceReport { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 96; | ||||||
|  |  | ||||||
|  | 	public static class InterferenceAccuracyAndIndex { | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned int (4 bits) representing expected accuracy of the estimate of | ||||||
|  | 		 * interference in dB with 95% confidence interval | ||||||
|  | 		 */ | ||||||
|  | 		public final byte expectedAccuracy; | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned int (4 bits) indicating the interference index that is unique for | ||||||
|  | 		 * each type of interference source | ||||||
|  | 		 */ | ||||||
|  | 		public final byte interferenceIndex; | ||||||
|  |  | ||||||
|  | 		/** Constructor */ | ||||||
|  | 		public InterferenceAccuracyAndIndex( | ||||||
|  | 			byte expectedAccuracy, | ||||||
|  | 			byte interferenceIndex | ||||||
|  | 		) { | ||||||
|  | 			this.expectedAccuracy = expectedAccuracy; | ||||||
|  | 			this.interferenceIndex = interferenceIndex; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** Parse InterferenceAccuracyAndIndex from JSON object */ | ||||||
|  | 		// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 		// present, we have no idea what the format looks like | ||||||
|  | 		public static InterferenceAccuracyAndIndex parse(JsonObject contents) { | ||||||
|  | 			return new InterferenceAccuracyAndIndex( | ||||||
|  | 				contents.get("Expected Accuracy").getAsByte(), | ||||||
|  | 				contents.get("Interference Index").getAsByte() | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public int hashCode() { | ||||||
|  | 			return Objects.hash(expectedAccuracy, interferenceIndex); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public boolean equals(Object obj) { | ||||||
|  | 			if (obj == null) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (this == obj) { | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (getClass() != obj.getClass()) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			InterferenceAccuracyAndIndex other = | ||||||
|  | 				(InterferenceAccuracyAndIndex) obj; | ||||||
|  | 			return expectedAccuracy == other.expectedAccuracy && | ||||||
|  | 				interferenceIndex == other.interferenceIndex; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Unsigned 8 bits representing when the report is generated */ | ||||||
|  | 	public final short reportPeriod; | ||||||
|  | 	/** | ||||||
|  | 	 * signed 8 bits representing the maximum level of the collocated | ||||||
|  | 	 * interference power in units of dBm over all receive chains averaged over a | ||||||
|  | 	 * 4 microsecond period during an interference period and across interference | ||||||
|  | 	 * bandwidth | ||||||
|  | 	 */ | ||||||
|  | 	public final byte interferenceLevel; | ||||||
|  | 	/** Subfield for interference level accuracy and index - 8 bits */ | ||||||
|  | 	public final InterferenceAccuracyAndIndex interferenceAccuracyAndIndex; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 32 bits representing the interval between two successibe periods | ||||||
|  | 	 * of interference in microseconds | ||||||
|  | 	 */ | ||||||
|  | 	public final long interferenceInterval; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 32 bits representing the duration of each period of interference in | ||||||
|  | 	 * microseconds | ||||||
|  | 	 */ | ||||||
|  | 	public final long interferenceBurstLength; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 32 bits contains the least significant 4 octets (i.e., B0–B31) of | ||||||
|  | 	 * the TSF timer at the start of the interference burst. When either the | ||||||
|  | 	 * Interference Interval or the Interference Burst Length fields are set to | ||||||
|  | 	 * 2^32 – 1, this field indicates the average duty cycle | ||||||
|  | 	 */ | ||||||
|  | 	public final long interferenceStartTimeDutyCycle; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 32 bits representing indicates the center frequency of interference | ||||||
|  | 	 * in units of 5 kHz | ||||||
|  | 	 */ | ||||||
|  | 	public final long interferenceCenterFrequency; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 16 bits representing the bandwidth in units of 5 kHz at the –3 dB | ||||||
|  | 	 * roll-off point of the interference signal | ||||||
|  | 	 */ | ||||||
|  | 	public final short interferenceBandwidth; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public CollocatedInterferenceReport( | ||||||
|  | 		short reportPeriod, | ||||||
|  | 		byte interferenceLevel, | ||||||
|  | 		InterferenceAccuracyAndIndex interferenceAccuracyAndIndex, | ||||||
|  | 		long interferenceInterval, | ||||||
|  | 		long interferenceBurstLength, | ||||||
|  | 		long interferenceStartTimeDutyCycle, | ||||||
|  | 		long interferenceCenterFrequency, | ||||||
|  | 		short interferenceBandwidth | ||||||
|  | 	) { | ||||||
|  | 		this.reportPeriod = reportPeriod; | ||||||
|  | 		this.interferenceLevel = interferenceLevel; | ||||||
|  | 		this.interferenceAccuracyAndIndex = interferenceAccuracyAndIndex; | ||||||
|  | 		this.interferenceInterval = interferenceInterval; | ||||||
|  | 		this.interferenceBurstLength = interferenceBurstLength; | ||||||
|  | 		this.interferenceStartTimeDutyCycle = interferenceStartTimeDutyCycle; | ||||||
|  | 		this.interferenceCenterFrequency = interferenceCenterFrequency; | ||||||
|  | 		this.interferenceBandwidth = interferenceBandwidth; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse CollocatedInterferenceReport from JSON object */ | ||||||
|  | 	// TODO rename fields as necessary - we don't know how the data format yet | ||||||
|  | 	public static CollocatedInterferenceReport parse(JsonObject contents) { | ||||||
|  | 		return new CollocatedInterferenceReport( | ||||||
|  | 			contents.get("Report Period").getAsShort(), | ||||||
|  | 			contents.get("Intereference Level").getAsByte(), | ||||||
|  | 			InterferenceAccuracyAndIndex | ||||||
|  | 				.parse( | ||||||
|  | 					contents.get("Interference Level Accuracy/Inteference Index").getAsJsonObject() | ||||||
|  | 				), | ||||||
|  | 			contents.get("Interference Interval").getAsLong(), | ||||||
|  | 			contents.get("Interference Burst Length").getAsLong(), | ||||||
|  | 			contents.get("Interference Start Time/Duty Cycle").getAsLong(), | ||||||
|  | 			contents.get("Interference Center Frequency").getAsLong(), | ||||||
|  | 			contents.get("Interference Bandwidth").getAsShort() | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash( | ||||||
|  | 			reportPeriod, | ||||||
|  | 			interferenceLevel, | ||||||
|  | 			interferenceAccuracyAndIndex, | ||||||
|  | 			interferenceInterval, | ||||||
|  | 			interferenceBurstLength, | ||||||
|  | 			interferenceStartTimeDutyCycle, | ||||||
|  | 			interferenceCenterFrequency, | ||||||
|  | 			interferenceBandwidth | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		CollocatedInterferenceReport other = (CollocatedInterferenceReport) obj; | ||||||
|  | 		return reportPeriod == other.reportPeriod && | ||||||
|  | 			interferenceLevel == other.interferenceLevel && | ||||||
|  | 			interferenceAccuracyAndIndex | ||||||
|  | 				.equals(other.interferenceAccuracyAndIndex) && | ||||||
|  | 			interferenceInterval == other.interferenceInterval && | ||||||
|  | 			interferenceBurstLength == other.interferenceBurstLength && | ||||||
|  | 			interferenceStartTimeDutyCycle == | ||||||
|  | 				other.interferenceStartTimeDutyCycle && | ||||||
|  | 			interferenceCenterFrequency == other.interferenceCenterFrequency && | ||||||
|  | 			interferenceBandwidth == other.interferenceBandwidth; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,153 @@ | |||||||
|  | /* | ||||||
|  |  * 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 (section 9.4.2.8) for more details. | ||||||
|  |  * Language in javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class Country { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 7; | ||||||
|  |  | ||||||
|  | 	/** Constraints for a subset of channels in the AP's country */ | ||||||
|  | 	public static class CountryInfo { | ||||||
|  | 		/** | ||||||
|  | 		 * 8 bits unsigned - the lowest channel number in the CountryInfo. | ||||||
|  | 		 */ | ||||||
|  | 		public final short firstChannelNumber; | ||||||
|  | 		/** | ||||||
|  | 		 * 8 bits unsigned - The maximum power, in dBm, allowed to be transmitted. | ||||||
|  | 		 */ | ||||||
|  | 		public final short maximumTransmitPowerLevel; | ||||||
|  | 		/** | ||||||
|  | 		 * 8 bits unsigned - 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 short numberOfChannels; | ||||||
|  |  | ||||||
|  | 		/** Constructor. */ | ||||||
|  | 		public CountryInfo( | ||||||
|  | 			short firstChannelNumber, | ||||||
|  | 			short maximumTransmitPowerLevel, | ||||||
|  | 			short numberOfChannels | ||||||
|  | 		) { | ||||||
|  | 			this.firstChannelNumber = firstChannelNumber; | ||||||
|  | 			this.maximumTransmitPowerLevel = maximumTransmitPowerLevel; | ||||||
|  | 			this.numberOfChannels = numberOfChannels; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** Parse CountryInfo from the appropriate Json object. */ | ||||||
|  | 		public static CountryInfo parse(JsonObject contents) { | ||||||
|  | 			final short firstChannelNumber = | ||||||
|  | 				contents.get("First Channel Number").getAsShort(); | ||||||
|  | 			final short maximumTransmitPowerLevel = contents | ||||||
|  | 				.get("Maximum Transmit Power Level (in dBm)") | ||||||
|  | 				.getAsShort(); | ||||||
|  | 			final short numberOfChannels = | ||||||
|  | 				contents.get("Number of Channels").getAsShort(); | ||||||
|  | 			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; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Country */ | ||||||
|  | 	public final String country; | ||||||
|  | 	/** | ||||||
|  | 	 * 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( | ||||||
|  | 		String country, | ||||||
|  | 		List<CountryInfo> countryInfos | ||||||
|  | 	) { | ||||||
|  | 		this.country = country; | ||||||
|  | 		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( | ||||||
|  | 			contents.get("Code").getAsString(), | ||||||
|  | 			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); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -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; | ||||||
| @@ -15,9 +15,12 @@ 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). Refer to the 802.11 | ||||||
|  |  * specification (section 9.4.2.56)). | ||||||
|  */ |  */ | ||||||
| public class HTOperationElement { | public class HTOperation { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 61; | ||||||
| 
 | 
 | ||||||
| 	/** Channel number of the primary channel. */ | 	/** Channel number of the primary channel. */ | ||||||
| 	public final byte primaryChannel; | 	public final byte primaryChannel; | ||||||
| @@ -78,7 +81,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 +117,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 +144,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 +185,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 +214,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 +251,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,65 @@ | |||||||
|  | /* | ||||||
|  |  * 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 (section 9.4.2.13). Refer to the specification for more details. | ||||||
|  |  * Language in javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class LocalPowerConstraint { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 32; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - 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 short localPowerConstraint; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public LocalPowerConstraint(short localPowerConstraint) { | ||||||
|  | 		this.localPowerConstraint = localPowerConstraint; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse LocalPowerConstraint IE from appropriate Json object. */ | ||||||
|  | 	public static LocalPowerConstraint parse(JsonObject contents) { | ||||||
|  | 		final short localPowerConstraint = | ||||||
|  | 			contents.get("Local Power Constraint").getAsShort(); | ||||||
|  | 		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; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,318 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called | ||||||
|  |  * "Neighbor Report" in 802.11 specs (section 9.4.2.36). Refer to the | ||||||
|  |  * specification for more details. Language in javadocs is taken from the | ||||||
|  |  * specification. | ||||||
|  |  */ | ||||||
|  | public class NeighborReport { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 52; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The BSSID Information field can be used to help determine neighbor service | ||||||
|  | 	 * set transition candidates | ||||||
|  | 	 */ | ||||||
|  | 	public static class BssidInfo { | ||||||
|  | 		/** | ||||||
|  | 		 * The capability subelement containing selected capability information for | ||||||
|  | 		 * the AP indicated by this BSSID. | ||||||
|  | 		 */ | ||||||
|  | 		public static class Capabilities { | ||||||
|  | 			/** dot11SpectrumManagementRequired */ | ||||||
|  | 			public final boolean spectrumManagement; | ||||||
|  | 			/** dot11QosOptionImplemented */ | ||||||
|  | 			public final boolean qos; | ||||||
|  | 			/** dot11APSDOptionImplemented */ | ||||||
|  | 			public final boolean apsd; | ||||||
|  | 			/** dot11RadioMeasurementActivated */ | ||||||
|  | 			public final boolean radioMeasurement; | ||||||
|  |  | ||||||
|  | 			/** Constructor */ | ||||||
|  | 			public Capabilities( | ||||||
|  | 				boolean spectrumManagement, | ||||||
|  | 				boolean qos, | ||||||
|  | 				boolean apsd, | ||||||
|  | 				boolean radioMeasurement | ||||||
|  | 			) { | ||||||
|  | 				this.spectrumManagement = spectrumManagement; | ||||||
|  | 				this.qos = qos; | ||||||
|  | 				this.apsd = apsd; | ||||||
|  | 				this.radioMeasurement = radioMeasurement; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/** Parse Capabilities from JSON object */ | ||||||
|  | 			// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 			// present, we have no idea what the format looks like | ||||||
|  | 			public static Capabilities parse(JsonObject contents) { | ||||||
|  | 				return new Capabilities( | ||||||
|  | 					contents.get("Spectrum Management").getAsBoolean(), | ||||||
|  | 					contents.get("QoS").getAsBoolean(), | ||||||
|  | 					contents.get("APSD").getAsBoolean(), | ||||||
|  | 					contents.get("Radio Management").getAsBoolean() | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@Override | ||||||
|  | 			public int hashCode() { | ||||||
|  | 				return Objects | ||||||
|  | 					.hash(spectrumManagement, qos, apsd, radioMeasurement); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@Override | ||||||
|  | 			public boolean equals(Object obj) { | ||||||
|  | 				if (obj == null) { | ||||||
|  | 					return false; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (this == obj) { | ||||||
|  | 					return true; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (getClass() != obj.getClass()) { | ||||||
|  | 					return false; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				Capabilities other = (Capabilities) obj; | ||||||
|  | 				return spectrumManagement == other.spectrumManagement && | ||||||
|  | 					qos == other.qos && apsd == other.apsd && | ||||||
|  | 					radioMeasurement == other.radioMeasurement; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * 2 unsigned bits - whether the AP identified by this BSSID is reachable by | ||||||
|  | 		 * the STA that requested the neighbor report | ||||||
|  | 		 */ | ||||||
|  | 		public final byte apReachability; | ||||||
|  | 		/** | ||||||
|  | 		 * If true, indicates that the AP identified by this BSSID supports the same | ||||||
|  | 		 * security provisioning as used by the STA in its current association. If the | ||||||
|  | 		 * bit is false, it indicates either that the AP does not support the same | ||||||
|  | 		 * security provisioning or that the security information is not available at | ||||||
|  | 		 * this time. | ||||||
|  | 		 */ | ||||||
|  | 		public final boolean security; | ||||||
|  | 		/** | ||||||
|  | 		 * Indicates the AP indicated by this BSSID has the same authenticator as | ||||||
|  | 		 * the AP sending the report. If this bit is false, it indicates a distinct | ||||||
|  | 		 * authenticator or the information is not available. | ||||||
|  | 		 */ | ||||||
|  | 		public final boolean keyScope; | ||||||
|  | 		/** | ||||||
|  | 		 * @see Capabilities | ||||||
|  | 		 */ | ||||||
|  | 		public final Capabilities capabilities; | ||||||
|  | 		/** | ||||||
|  | 		 * Set to true to indicate that the AP represented by this BSSID is including | ||||||
|  | 		 * an MDE in its Beacon frames and that the contents of that MDE are identical | ||||||
|  | 		 * to the MDE advertised by the AP sending the report | ||||||
|  | 		 */ | ||||||
|  | 		public final boolean mobilityDomain; | ||||||
|  | 		/** | ||||||
|  | 		 * High throughput or not, if true the contents of the HT Capabilities in the | ||||||
|  | 		 * Beacon frame should be identical to the HT Capabilities advertised by the | ||||||
|  | 		 * AP sending the report | ||||||
|  | 		 */ | ||||||
|  | 		public final boolean highThroughput; | ||||||
|  | 		/** | ||||||
|  | 		 * Very High throughput or not, if true the contents of the VHT Capabilities | ||||||
|  | 		 * in the Beacon frame should be identical to the VHT Capabilities advertised | ||||||
|  | 		 * by the AP sending the report | ||||||
|  | 		 */ | ||||||
|  | 		public final boolean veryHighThroughput; | ||||||
|  | 		/** | ||||||
|  | 		 * Indicate that the AP represented by this BSSID is an AP that has set the Fine | ||||||
|  | 		 * Timing Measurement Responder field of the Extended Capabilities element | ||||||
|  | 		 */ | ||||||
|  | 		public final boolean ftm; | ||||||
|  |  | ||||||
|  | 		/** Constructor */ | ||||||
|  | 		public BssidInfo( | ||||||
|  | 			byte apReachability, | ||||||
|  | 			boolean security, | ||||||
|  | 			boolean keyScope, | ||||||
|  | 			Capabilities capabilities, | ||||||
|  | 			boolean mobilityDomain, | ||||||
|  | 			boolean highThroughput, | ||||||
|  | 			boolean veryHighThroughput, | ||||||
|  | 			boolean ftm | ||||||
|  | 		) { | ||||||
|  | 			this.apReachability = apReachability; | ||||||
|  | 			this.security = security; | ||||||
|  | 			this.keyScope = keyScope; | ||||||
|  | 			this.capabilities = capabilities; | ||||||
|  | 			this.mobilityDomain = mobilityDomain; | ||||||
|  | 			this.highThroughput = highThroughput; | ||||||
|  | 			this.veryHighThroughput = veryHighThroughput; | ||||||
|  | 			this.ftm = ftm; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** Parse BssidInfo from JSON object */ | ||||||
|  | 		// TODO rename fields as necessary - we don't know how the data format yet | ||||||
|  | 		public static BssidInfo parse(JsonObject contents) { | ||||||
|  | 			JsonElement capabilitiesJson = contents.get("capabilities"); | ||||||
|  | 			if (capabilitiesJson == null) { | ||||||
|  | 				return null; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			Capabilities capabilities = | ||||||
|  | 				Capabilities.parse(capabilitiesJson.getAsJsonObject()); | ||||||
|  | 			return new BssidInfo( | ||||||
|  | 				contents.get("AP Reachability").getAsByte(), | ||||||
|  | 				contents.get("Security").getAsBoolean(), | ||||||
|  | 				contents.get("Key Scope").getAsBoolean(), | ||||||
|  | 				capabilities, | ||||||
|  | 				contents.get("Mobility Domain").getAsBoolean(), | ||||||
|  | 				contents.get("High Throughput").getAsBoolean(), | ||||||
|  | 				contents.get("Very High Throughput").getAsBoolean(), | ||||||
|  | 				contents.get("FTM").getAsBoolean() | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public int hashCode() { | ||||||
|  | 			return Objects.hash( | ||||||
|  | 				apReachability, | ||||||
|  | 				security, | ||||||
|  | 				keyScope, | ||||||
|  | 				mobilityDomain, | ||||||
|  | 				highThroughput, | ||||||
|  | 				veryHighThroughput, | ||||||
|  | 				ftm | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public boolean equals(Object obj) { | ||||||
|  | 			if (obj == null) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (this == obj) { | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (getClass() != obj.getClass()) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			BssidInfo other = (BssidInfo) obj; | ||||||
|  | 			return apReachability == other.apReachability && | ||||||
|  | 				security == other.security && keyScope == other.keyScope && | ||||||
|  | 				capabilities == other.capabilities && | ||||||
|  | 				mobilityDomain == other.mobilityDomain && | ||||||
|  | 				highThroughput == other.highThroughput && | ||||||
|  | 				veryHighThroughput == other.veryHighThroughput && | ||||||
|  | 				ftm == other.ftm; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** BSSID */ | ||||||
|  | 	public final String bssid; | ||||||
|  | 	/** | ||||||
|  | 	 * @see BssidInfo | ||||||
|  | 	 */ | ||||||
|  | 	public final BssidInfo bssidInfo; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - indicates the channel set of the AP indicated by this BSSID | ||||||
|  | 	 */ | ||||||
|  | 	public final short operatingClass; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - channel number | ||||||
|  | 	 */ | ||||||
|  | 	public final short channelNumber; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - PHY type | ||||||
|  | 	 */ | ||||||
|  | 	public final short phyType; | ||||||
|  | 	// TODO do we want to support the subelements? | ||||||
|  | 	/** | ||||||
|  | 	 * Optional subelements | ||||||
|  | 	 */ | ||||||
|  | 	public final List<JsonObject> subelements; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public NeighborReport( | ||||||
|  | 		String bssid, | ||||||
|  | 		BssidInfo bssidInfo, | ||||||
|  | 		short operatingClass, | ||||||
|  | 		short channelNumber, | ||||||
|  | 		short phyType, | ||||||
|  | 		List<JsonObject> subelements | ||||||
|  | 	) { | ||||||
|  | 		this.bssid = bssid; | ||||||
|  | 		this.bssidInfo = bssidInfo; | ||||||
|  | 		this.operatingClass = operatingClass; | ||||||
|  | 		this.channelNumber = channelNumber; | ||||||
|  | 		this.phyType = phyType; | ||||||
|  | 		this.subelements = Collections.unmodifiableList(subelements); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse NeighborReport from JSON object */ | ||||||
|  | 	// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 	// present, we have no idea what the format looks like | ||||||
|  | 	public static NeighborReport parse(JsonObject contents) { | ||||||
|  | 		List<JsonObject> subelements = null; | ||||||
|  | 		JsonElement subelementsObj = contents.get("Subelements"); | ||||||
|  | 		if (subelementsObj != null) { | ||||||
|  | 			subelements = new ArrayList<JsonObject>(); | ||||||
|  | 			for (JsonElement elem : subelementsObj.getAsJsonArray()) { | ||||||
|  | 				subelements.add(elem.getAsJsonObject()); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return new NeighborReport( | ||||||
|  | 			contents.get("BSSID").getAsString(), | ||||||
|  | 			BssidInfo.parse(contents.get("BSSID Info").getAsJsonObject()), | ||||||
|  | 			contents.get("Operating Class").getAsShort(), | ||||||
|  | 			contents.get("Channel Number").getAsShort(), | ||||||
|  | 			contents.get("Phy Type").getAsShort(), | ||||||
|  | 			subelements | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects | ||||||
|  | 			.hash(bssid, bssidInfo, operatingClass, channelNumber, phyType); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		NeighborReport other = (NeighborReport) obj; | ||||||
|  | 		return bssid == other.bssid && bssidInfo == other.bssidInfo && | ||||||
|  | 			operatingClass == other.operatingClass && | ||||||
|  | 			channelNumber == other.channelNumber && phyType == other.phyType; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,80 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called "Power | ||||||
|  |  * Capability" in 802.11 specs (section 9.4.2.14). Refer to the specification | ||||||
|  |  * for more details. Language in javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class PowerCapability { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 33; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Signed 8 bits units of dB relative to 1mW - nominal minimum transmit power | ||||||
|  | 	 * with which the STA is capable of transmitting in the current channel, with a | ||||||
|  | 	 * tolerance ± 5 dB. | ||||||
|  | 	 */ | ||||||
|  | 	public final byte minimumTxPowerCapability; | ||||||
|  | 	/** | ||||||
|  | 	 * Signed 8 bits units of dB relative to 1mW - nominal maximum transmit power | ||||||
|  | 	 * with which the STA is capable of transmitting in the current channel, with a | ||||||
|  | 	 * tolerance ± 5 dB. | ||||||
|  | 	 */ | ||||||
|  | 	public final byte maximumTxPowerCapability; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public PowerCapability( | ||||||
|  | 		byte minimumTxPowerCapability, | ||||||
|  | 		byte maximumTxPowerCapability | ||||||
|  | 	) { | ||||||
|  | 		this.minimumTxPowerCapability = minimumTxPowerCapability; | ||||||
|  | 		this.maximumTxPowerCapability = maximumTxPowerCapability; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse PowerCapability from JSON object */ | ||||||
|  | 	// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 	// present, we have no idea what the format looks like | ||||||
|  | 	public static PowerCapability parse(JsonObject contents) { | ||||||
|  | 		return new PowerCapability( | ||||||
|  | 			contents.get("Minimum Tx Power Capability").getAsByte(), | ||||||
|  | 			contents.get("Maximum Tx Power Capability").getAsByte() | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(minimumTxPowerCapability, maximumTxPowerCapability); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		PowerCapability other = (PowerCapability) obj; | ||||||
|  | 		return minimumTxPowerCapability == other.minimumTxPowerCapability && | ||||||
|  | 			maximumTxPowerCapability == other.maximumTxPowerCapability; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,109 @@ | |||||||
|  | /* | ||||||
|  |  * 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 | ||||||
|  |  * (section 9.4.2.27). Refer to the specification for more details. Language in | ||||||
|  |  * javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class QbssLoad { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 11; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 16 bits - The total number of STAs currently associated with the BSS. | ||||||
|  | 	 */ | ||||||
|  | 	public final short stationCount; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - 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 short channelUtilization; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 16 bits - 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 short availableAdmissionCapacity; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public QbssLoad( | ||||||
|  | 		short stationCount, | ||||||
|  | 		short channelUtilization, | ||||||
|  | 		short 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 short stationCount = contents.get("Station Count").getAsShort(); | ||||||
|  | 		final short channelUtilization = | ||||||
|  | 			contents.get("Channel Utilization").getAsShort(); | ||||||
|  | 		final short availableAdmissionCapacity = | ||||||
|  | 			contents.get("Available Admission Capabilities").getAsShort(); | ||||||
|  | 		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; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,67 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called "RCPI" | ||||||
|  |  * in 802.11 specs (section 9.4.2.37). Refer to the specification for more | ||||||
|  |  * details. Language in javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class RCPI { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 53; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - indication of the received RF power in the selected | ||||||
|  | 	 * channel for a received frame | ||||||
|  | 	 */ | ||||||
|  | 	public final short rcpi; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public RCPI(short rcpi) { | ||||||
|  | 		this.rcpi = rcpi; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse RCPI from JSON object */ | ||||||
|  | 	// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 	// present, we have no idea what the format looks like | ||||||
|  | 	public static RCPI parse(JsonObject contents) { | ||||||
|  | 		return new RCPI( | ||||||
|  | 			contents.get("RCPI").getAsShort() | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(rcpi); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		RCPI other = (RCPI) obj; | ||||||
|  | 		return rcpi == other.rcpi; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,274 @@ | |||||||
|  | /* | ||||||
|  |  * 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.facebook.openwifi.cloudsdk.IEUtils; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called "RM | ||||||
|  |  * Enabled Capabilities" in 802.11 specs (section 9.4.2.45). Refer to the | ||||||
|  |  * specification for more details. Language in javadocs is taken from the | ||||||
|  |  * specification. | ||||||
|  |  */ | ||||||
|  | public class RMEnabledCapabilities { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 70; | ||||||
|  |  | ||||||
|  | 	// Bit fields | ||||||
|  | 	// @formatter:off | ||||||
|  | 	public final boolean linkMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean neighborReportCapabilityEnabled; | ||||||
|  | 	public final boolean parallelMeasurementsCapabilityEnabled; | ||||||
|  | 	public final boolean repeatedMeasurementsCapabilityEnabled; | ||||||
|  | 	public final boolean beaconPassiveMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean beaconActiveMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean beaconTableMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean beaconMeasurementReportingConditionsCapabilityEnabled; | ||||||
|  | 	public final boolean frameMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean channelLoadMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean noiseHistogramMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean statisticsMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean lciMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean lciAzimuthCapabilityEnabled; | ||||||
|  | 	public final boolean transmitStreamCategoryMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean triggeredTransmitStreamCategoryMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean apChannelReportCapabilityEnabled; | ||||||
|  | 	public final boolean rmMibCapabilityEnabled; | ||||||
|  | 	public final int operatingChannelMaxMeasurementDuration; | ||||||
|  | 	public final int nonoperatingChannelMaxMeasurementDuration; | ||||||
|  | 	public final int measurementPilotCapability; | ||||||
|  | 	public final boolean measurementPilotTransmissionInformationCapabilityEnabled; | ||||||
|  | 	public final boolean neighborReportTsfOffsetCapabilityEnabled; | ||||||
|  | 	public final boolean rcpiMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean rsniMeasurementCapabilityEnabled; | ||||||
|  | 	public final boolean bssAverageAccessDelayCapabilityEnabled; | ||||||
|  | 	public final boolean bssAvailableAdmissionCapacityCapabilityEnabled; | ||||||
|  | 	public final boolean antennaCapabilityEnabled; | ||||||
|  | 	public final boolean ftmRangeReportCapabilityEnabled; | ||||||
|  | 	public final boolean civicLocationMeasurementCapabilityEnabled; | ||||||
|  | 	// @formatter:on | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public RMEnabledCapabilities( | ||||||
|  | 		boolean linkMeasurementCapabilityEnabled, | ||||||
|  | 		boolean neighborReportCapabilityEnabled, | ||||||
|  | 		boolean parallelMeasurementsCapabilityEnabled, | ||||||
|  | 		boolean repeatedMeasurementsCapabilityEnabled, | ||||||
|  | 		boolean beaconPassiveMeasurementCapabilityEnabled, | ||||||
|  | 		boolean beaconActiveMeasurementCapabilityEnabled, | ||||||
|  | 		boolean beaconTableMeasurementCapabilityEnabled, | ||||||
|  | 		boolean beaconMeasurementReportingConditionsCapabilityEnabled, | ||||||
|  | 		boolean frameMeasurementCapabilityEnabled, | ||||||
|  | 		boolean channelLoadMeasurementCapabilityEnabled, | ||||||
|  | 		boolean noiseHistogramMeasurementCapabilityEnabled, | ||||||
|  | 		boolean statisticsMeasurementCapabilityEnabled, | ||||||
|  | 		boolean lciMeasurementCapabilityEnabled, | ||||||
|  | 		boolean lciAzimuthCapabilityEnabled, | ||||||
|  | 		boolean transmitStreamCategoryMeasurementCapabilityEnabled, | ||||||
|  | 		boolean triggeredTransmitStreamCategoryMeasurementCapabilityEnabled, | ||||||
|  | 		boolean apChannelReportCapabilityEnabled, | ||||||
|  | 		boolean rmMibCapabilityEnabled, | ||||||
|  | 		int operatingChannelMaxMeasurementDuration, | ||||||
|  | 		int nonoperatingChannelMaxMeasurementDuration, | ||||||
|  | 		int measurementPilotCapability, | ||||||
|  | 		boolean measurementPilotTransmissionInformationCapabilityEnabled, | ||||||
|  | 		boolean neighborReportTsfOffsetCapabilityEnabled, | ||||||
|  | 		boolean rcpiMeasurementCapabilityEnabled, | ||||||
|  | 		boolean rsniMeasurementCapabilityEnabled, | ||||||
|  | 		boolean bssAverageAccessDelayCapabilityEnabled, | ||||||
|  | 		boolean bssAvailableAdmissionCapacityCapabilityEnabled, | ||||||
|  | 		boolean antennaCapabilityEnabled, | ||||||
|  | 		boolean ftmRangeReportCapabilityEnabled, | ||||||
|  | 		boolean civicLocationMeasurementCapabilityEnabled | ||||||
|  | 	) { | ||||||
|  | 		// @formatter:off | ||||||
|  | 		this.linkMeasurementCapabilityEnabled = linkMeasurementCapabilityEnabled; | ||||||
|  | 		this.neighborReportCapabilityEnabled = neighborReportCapabilityEnabled; | ||||||
|  | 		this.parallelMeasurementsCapabilityEnabled = parallelMeasurementsCapabilityEnabled; | ||||||
|  | 		this.repeatedMeasurementsCapabilityEnabled = repeatedMeasurementsCapabilityEnabled; | ||||||
|  | 		this.beaconPassiveMeasurementCapabilityEnabled = beaconPassiveMeasurementCapabilityEnabled; | ||||||
|  | 		this.beaconActiveMeasurementCapabilityEnabled = beaconActiveMeasurementCapabilityEnabled; | ||||||
|  | 		this.beaconTableMeasurementCapabilityEnabled = beaconTableMeasurementCapabilityEnabled; | ||||||
|  | 		this.beaconMeasurementReportingConditionsCapabilityEnabled = beaconMeasurementReportingConditionsCapabilityEnabled; | ||||||
|  | 		this.frameMeasurementCapabilityEnabled = frameMeasurementCapabilityEnabled; | ||||||
|  | 		this.channelLoadMeasurementCapabilityEnabled = channelLoadMeasurementCapabilityEnabled; | ||||||
|  | 		this.noiseHistogramMeasurementCapabilityEnabled = noiseHistogramMeasurementCapabilityEnabled; | ||||||
|  | 		this.statisticsMeasurementCapabilityEnabled = statisticsMeasurementCapabilityEnabled; | ||||||
|  | 		this.lciMeasurementCapabilityEnabled = lciMeasurementCapabilityEnabled; | ||||||
|  | 		this.lciAzimuthCapabilityEnabled = lciAzimuthCapabilityEnabled; | ||||||
|  | 		this.transmitStreamCategoryMeasurementCapabilityEnabled = transmitStreamCategoryMeasurementCapabilityEnabled; | ||||||
|  | 		this.triggeredTransmitStreamCategoryMeasurementCapabilityEnabled = triggeredTransmitStreamCategoryMeasurementCapabilityEnabled; | ||||||
|  | 		this.apChannelReportCapabilityEnabled = apChannelReportCapabilityEnabled; | ||||||
|  | 		this.rmMibCapabilityEnabled = rmMibCapabilityEnabled; | ||||||
|  | 		this.operatingChannelMaxMeasurementDuration = operatingChannelMaxMeasurementDuration; | ||||||
|  | 		this.nonoperatingChannelMaxMeasurementDuration = nonoperatingChannelMaxMeasurementDuration; | ||||||
|  | 		this.measurementPilotCapability = measurementPilotCapability; | ||||||
|  | 		this.measurementPilotTransmissionInformationCapabilityEnabled = measurementPilotTransmissionInformationCapabilityEnabled; | ||||||
|  | 		this.neighborReportTsfOffsetCapabilityEnabled = neighborReportTsfOffsetCapabilityEnabled; | ||||||
|  | 		this.rcpiMeasurementCapabilityEnabled = rcpiMeasurementCapabilityEnabled; | ||||||
|  | 		this.rsniMeasurementCapabilityEnabled = rsniMeasurementCapabilityEnabled; | ||||||
|  | 		this.bssAverageAccessDelayCapabilityEnabled = bssAverageAccessDelayCapabilityEnabled; | ||||||
|  | 		this.bssAvailableAdmissionCapacityCapabilityEnabled = bssAvailableAdmissionCapacityCapabilityEnabled; | ||||||
|  | 		this.antennaCapabilityEnabled = antennaCapabilityEnabled; | ||||||
|  | 		this.ftmRangeReportCapabilityEnabled = ftmRangeReportCapabilityEnabled; | ||||||
|  | 		this.civicLocationMeasurementCapabilityEnabled = civicLocationMeasurementCapabilityEnabled; | ||||||
|  | 		// @formatter:on | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse RMEnabledCapabilities IE from appropriate Json object. */ | ||||||
|  | 	public static RMEnabledCapabilities parse(JsonObject contents) { | ||||||
|  | 		JsonObject o = contents.get("RM Capabilities").getAsJsonObject(); | ||||||
|  | 		// @formatter:off | ||||||
|  | 		return new RMEnabledCapabilities( | ||||||
|  | 			/* bits 0-17 */ | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Link Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Neighbor Report"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Parallel Measurements"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Repeated Measurements"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Beacon Passive Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Beacon Active Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Beacon Table Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Beacon Measurement Reporting Conditions"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Frame Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Channel Load Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Noise Histogram Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Statistics Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "LCI Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "LCI Azimuth capability"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Transmit Stream/Category Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Triggered Transmit Stream/Category Measurement"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "AP Channel Report capability"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "RM MIB capability"), | ||||||
|  | 			/* bits 18-20 */ | ||||||
|  | 			IEUtils.parseIntField(o, "Operating Channel Max Measurement Duration"), | ||||||
|  | 			/* bits 21-23 */ | ||||||
|  | 			IEUtils.parseIntField(o, "Nonoperating Channel Max Measurement Duration"), | ||||||
|  | 			/* bits 24-26 */ | ||||||
|  | 			IEUtils.parseIntField(o, "Measurement Pilotcapability"), | ||||||
|  | 			/* bits 27-35 */ | ||||||
|  | 			false /* TODO "Measurement Pilot Transmission Information Capability" */, | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Neighbor Report TSF Offset"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "RCPI Measurement capability"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "RSNI Measurement capability"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "BSS Average Access Delay capability"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "BSS Available Admission Capacity capability"), | ||||||
|  | 			IEUtils.parseBooleanNumberField(o, "Antenna capability"), | ||||||
|  | 			false /* TODO "FTM Range Report Capability" */, | ||||||
|  | 			false /* TODO "Civic Location Measurement Capability" */ | ||||||
|  | 			/* bits 36-39 reserved */ | ||||||
|  | 		); | ||||||
|  | 		// @formatter:on | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash( | ||||||
|  | 			antennaCapabilityEnabled, | ||||||
|  | 			apChannelReportCapabilityEnabled, | ||||||
|  | 			beaconActiveMeasurementCapabilityEnabled, | ||||||
|  | 			beaconMeasurementReportingConditionsCapabilityEnabled, | ||||||
|  | 			beaconPassiveMeasurementCapabilityEnabled, | ||||||
|  | 			beaconTableMeasurementCapabilityEnabled, | ||||||
|  | 			bssAvailableAdmissionCapacityCapabilityEnabled, | ||||||
|  | 			bssAverageAccessDelayCapabilityEnabled, | ||||||
|  | 			channelLoadMeasurementCapabilityEnabled, | ||||||
|  | 			civicLocationMeasurementCapabilityEnabled, | ||||||
|  | 			frameMeasurementCapabilityEnabled, | ||||||
|  | 			ftmRangeReportCapabilityEnabled, | ||||||
|  | 			lciAzimuthCapabilityEnabled, | ||||||
|  | 			lciMeasurementCapabilityEnabled, | ||||||
|  | 			linkMeasurementCapabilityEnabled, | ||||||
|  | 			measurementPilotCapability, | ||||||
|  | 			measurementPilotTransmissionInformationCapabilityEnabled, | ||||||
|  | 			neighborReportCapabilityEnabled, | ||||||
|  | 			neighborReportTsfOffsetCapabilityEnabled, | ||||||
|  | 			noiseHistogramMeasurementCapabilityEnabled, | ||||||
|  | 			nonoperatingChannelMaxMeasurementDuration, | ||||||
|  | 			operatingChannelMaxMeasurementDuration, | ||||||
|  | 			parallelMeasurementsCapabilityEnabled, | ||||||
|  | 			rcpiMeasurementCapabilityEnabled, | ||||||
|  | 			repeatedMeasurementsCapabilityEnabled, | ||||||
|  | 			rmMibCapabilityEnabled, | ||||||
|  | 			rsniMeasurementCapabilityEnabled, | ||||||
|  | 			statisticsMeasurementCapabilityEnabled, | ||||||
|  | 			transmitStreamCategoryMeasurementCapabilityEnabled, | ||||||
|  | 			triggeredTransmitStreamCategoryMeasurementCapabilityEnabled | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (this == obj) | ||||||
|  | 			return true; | ||||||
|  | 		if (obj == null) | ||||||
|  | 			return false; | ||||||
|  | 		if (getClass() != obj.getClass()) | ||||||
|  | 			return false; | ||||||
|  | 		RMEnabledCapabilities other = (RMEnabledCapabilities) obj; | ||||||
|  | 		return antennaCapabilityEnabled == other.antennaCapabilityEnabled && | ||||||
|  | 			apChannelReportCapabilityEnabled == | ||||||
|  | 				other.apChannelReportCapabilityEnabled && | ||||||
|  | 			beaconActiveMeasurementCapabilityEnabled == | ||||||
|  | 				other.beaconActiveMeasurementCapabilityEnabled && | ||||||
|  | 			beaconMeasurementReportingConditionsCapabilityEnabled == | ||||||
|  | 				other.beaconMeasurementReportingConditionsCapabilityEnabled && | ||||||
|  | 			beaconPassiveMeasurementCapabilityEnabled == | ||||||
|  | 				other.beaconPassiveMeasurementCapabilityEnabled && | ||||||
|  | 			beaconTableMeasurementCapabilityEnabled == | ||||||
|  | 				other.beaconTableMeasurementCapabilityEnabled && | ||||||
|  | 			bssAvailableAdmissionCapacityCapabilityEnabled == | ||||||
|  | 				other.bssAvailableAdmissionCapacityCapabilityEnabled && | ||||||
|  | 			bssAverageAccessDelayCapabilityEnabled == | ||||||
|  | 				other.bssAverageAccessDelayCapabilityEnabled && | ||||||
|  | 			channelLoadMeasurementCapabilityEnabled == | ||||||
|  | 				other.channelLoadMeasurementCapabilityEnabled && | ||||||
|  | 			civicLocationMeasurementCapabilityEnabled == | ||||||
|  | 				other.civicLocationMeasurementCapabilityEnabled && | ||||||
|  | 			frameMeasurementCapabilityEnabled == | ||||||
|  | 				other.frameMeasurementCapabilityEnabled && | ||||||
|  | 			ftmRangeReportCapabilityEnabled == | ||||||
|  | 				other.ftmRangeReportCapabilityEnabled && | ||||||
|  | 			lciAzimuthCapabilityEnabled == other.lciAzimuthCapabilityEnabled && | ||||||
|  | 			lciMeasurementCapabilityEnabled == | ||||||
|  | 				other.lciMeasurementCapabilityEnabled && | ||||||
|  | 			linkMeasurementCapabilityEnabled == | ||||||
|  | 				other.linkMeasurementCapabilityEnabled && | ||||||
|  | 			measurementPilotCapability == other.measurementPilotCapability && | ||||||
|  | 			measurementPilotTransmissionInformationCapabilityEnabled == | ||||||
|  | 				other.measurementPilotTransmissionInformationCapabilityEnabled && | ||||||
|  | 			neighborReportCapabilityEnabled == | ||||||
|  | 				other.neighborReportCapabilityEnabled && | ||||||
|  | 			neighborReportTsfOffsetCapabilityEnabled == | ||||||
|  | 				other.neighborReportTsfOffsetCapabilityEnabled && | ||||||
|  | 			noiseHistogramMeasurementCapabilityEnabled == | ||||||
|  | 				other.noiseHistogramMeasurementCapabilityEnabled && | ||||||
|  | 			nonoperatingChannelMaxMeasurementDuration == | ||||||
|  | 				other.nonoperatingChannelMaxMeasurementDuration && | ||||||
|  | 			operatingChannelMaxMeasurementDuration == | ||||||
|  | 				other.operatingChannelMaxMeasurementDuration && | ||||||
|  | 			parallelMeasurementsCapabilityEnabled == | ||||||
|  | 				other.parallelMeasurementsCapabilityEnabled && | ||||||
|  | 			rcpiMeasurementCapabilityEnabled == | ||||||
|  | 				other.rcpiMeasurementCapabilityEnabled && | ||||||
|  | 			repeatedMeasurementsCapabilityEnabled == | ||||||
|  | 				other.repeatedMeasurementsCapabilityEnabled && | ||||||
|  | 			rmMibCapabilityEnabled == other.rmMibCapabilityEnabled && | ||||||
|  | 			rsniMeasurementCapabilityEnabled == | ||||||
|  | 				other.rsniMeasurementCapabilityEnabled && | ||||||
|  | 			statisticsMeasurementCapabilityEnabled == | ||||||
|  | 				other.statisticsMeasurementCapabilityEnabled && | ||||||
|  | 			transmitStreamCategoryMeasurementCapabilityEnabled == | ||||||
|  | 				other.transmitStreamCategoryMeasurementCapabilityEnabled && | ||||||
|  | 			triggeredTransmitStreamCategoryMeasurementCapabilityEnabled == | ||||||
|  | 				other.triggeredTransmitStreamCategoryMeasurementCapabilityEnabled; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,313 @@ | |||||||
|  | /* | ||||||
|  |  * 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.facebook.openwifi.cloudsdk.IEUtils; | ||||||
|  | import com.google.gson.JsonElement; | ||||||
|  | import com.google.gson.JsonObject; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called | ||||||
|  |  * "Reduced Neighbor Report" in 802.11 specs (section 9.4.2.170). Refer to the | ||||||
|  |  * specification for more details. Language in javadocs is taken from the | ||||||
|  |  * specification. | ||||||
|  |  */ | ||||||
|  | public class ReducedNeighborReport { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 201; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * The Neighbor AP Information field specifies TBTT and other information | ||||||
|  | 	 * related to a group of neighbor APs on one channel. | ||||||
|  | 	 */ | ||||||
|  | 	public static class NeighborApInformation { | ||||||
|  | 		/** | ||||||
|  | 		 * Subfield for TBTT Information header | ||||||
|  | 		 */ | ||||||
|  | 		public static class TbttInformationHeader { | ||||||
|  | 			/** | ||||||
|  | 			 * Unsigned 2 bits -  identifies, together with the TBTT Information Length | ||||||
|  | 			 * subfield, the format of the TBTT Information field | ||||||
|  | 			 */ | ||||||
|  | 			public final byte tbttInformationType; | ||||||
|  | 			/** | ||||||
|  | 			 * 1 bit - reserved except when the Reduced Neighbor Report element is | ||||||
|  | 			 * carried in a Probe Response frame transmitted by a TVHT AP | ||||||
|  | 			 */ | ||||||
|  | 			public final boolean filteredNeighborAp; | ||||||
|  | 			/** | ||||||
|  | 			 * Unsigned 4 bits - number of TBTT Information fields included in the TBTT | ||||||
|  | 			 * Information Set field of the Neighbor AP Information field, minus one | ||||||
|  | 			 */ | ||||||
|  | 			public final byte tbttInformationCount; | ||||||
|  | 			/** | ||||||
|  | 			 * Unsigned 8 bits - the length of each TBTT Information field included in | ||||||
|  | 			 * the TBTT Information Set field of the Neighbor AP Information field | ||||||
|  | 			 */ | ||||||
|  | 			public final short tbttInformationLength; | ||||||
|  |  | ||||||
|  | 			/** Constructor */ | ||||||
|  | 			public TbttInformationHeader( | ||||||
|  | 				byte tbttInformationType, | ||||||
|  | 				boolean filteredNeighborAp, | ||||||
|  | 				byte tbttInformationCount, | ||||||
|  | 				short tbttInformationLength | ||||||
|  | 			) { | ||||||
|  | 				this.tbttInformationType = tbttInformationType; | ||||||
|  | 				this.filteredNeighborAp = filteredNeighborAp; | ||||||
|  | 				this.tbttInformationCount = tbttInformationCount; | ||||||
|  | 				this.tbttInformationLength = tbttInformationLength; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/** Parse TbttInformationHeader from JSON object */ | ||||||
|  | 			// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 			// present, we have no idea what the format looks like | ||||||
|  | 			public static TbttInformationHeader parse(JsonObject contents) { | ||||||
|  | 				return new TbttInformationHeader( | ||||||
|  | 					contents.get("TBTT Information Type").getAsByte(), | ||||||
|  | 					contents.get("Filtered Neighbor Map").getAsBoolean(), | ||||||
|  | 					contents.get("TBTT Information Count").getAsByte(), | ||||||
|  | 					contents.get("TBTT Information Length").getAsShort() | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@Override | ||||||
|  | 			public int hashCode() { | ||||||
|  | 				return Objects.hash( | ||||||
|  | 					tbttInformationType, | ||||||
|  | 					filteredNeighborAp, | ||||||
|  | 					tbttInformationCount, | ||||||
|  | 					tbttInformationLength | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@Override | ||||||
|  | 			public boolean equals(Object obj) { | ||||||
|  | 				if (obj == null) { | ||||||
|  | 					return false; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (this == obj) { | ||||||
|  | 					return true; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (getClass() != obj.getClass()) { | ||||||
|  | 					return false; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				TbttInformationHeader other = (TbttInformationHeader) obj; | ||||||
|  | 				return tbttInformationType == other.tbttInformationType && | ||||||
|  | 					filteredNeighborAp == other.filteredNeighborAp && | ||||||
|  | 					tbttInformationCount == other.tbttInformationCount && | ||||||
|  | 					tbttInformationLength == other.tbttInformationLength; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * Subfield for TBTT Information | ||||||
|  | 		 */ | ||||||
|  | 		public static class TbttInformation { | ||||||
|  | 			/** | ||||||
|  | 			 * Unsigned 8 bits - offset in TUs, rounded down to nearest TU, to the next | ||||||
|  | 			 * TBTT of an AP’s BSS from the immediately prior TBTT of the AP that | ||||||
|  | 			 * transmits this element | ||||||
|  | 			 */ | ||||||
|  | 			public final short neighborApTbttOffset; | ||||||
|  | 			/** BSSID of neighbor, optional */ | ||||||
|  | 			public final String bssid; | ||||||
|  | 			/** Short SSID of neighbor, optional */ | ||||||
|  | 			public final String shortSsid; | ||||||
|  |  | ||||||
|  | 			/** Constructor */ | ||||||
|  | 			public TbttInformation( | ||||||
|  | 				short neighborApTbttOffset, | ||||||
|  | 				String bssid, | ||||||
|  | 				String shortSsid | ||||||
|  | 			) { | ||||||
|  | 				this.neighborApTbttOffset = neighborApTbttOffset; | ||||||
|  | 				this.bssid = bssid; | ||||||
|  | 				this.shortSsid = shortSsid; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			/** Parse TbttInformation from JSON object */ | ||||||
|  | 			// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 			// present, we have no idea what the format looks like | ||||||
|  | 			public static TbttInformation parse(JsonObject contents) { | ||||||
|  | 				return new TbttInformation( | ||||||
|  | 					contents.get("Neighbor AP TBTT Offset").getAsShort(), | ||||||
|  | 					IEUtils.parseOptionalStringField(contents, "BSSID"), | ||||||
|  | 					IEUtils.parseOptionalStringField(contents, "Short SSID") | ||||||
|  | 				); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@Override | ||||||
|  | 			public int hashCode() { | ||||||
|  | 				return Objects.hash(neighborApTbttOffset, bssid, shortSsid); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			@Override | ||||||
|  | 			public boolean equals(Object obj) { | ||||||
|  | 				if (obj == null) { | ||||||
|  | 					return false; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (this == obj) { | ||||||
|  | 					return true; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				if (getClass() != obj.getClass()) { | ||||||
|  | 					return false; | ||||||
|  | 				} | ||||||
|  |  | ||||||
|  | 				TbttInformation other = (TbttInformation) obj; | ||||||
|  | 				return neighborApTbttOffset == | ||||||
|  | 					other.neighborApTbttOffset && bssid.equals(other.bssid) && | ||||||
|  | 					Objects.equals(shortSsid, other.shortSsid); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** | ||||||
|  | 		 * @see TbttInformationHeader | ||||||
|  | 		 */ | ||||||
|  | 		public final TbttInformationHeader tbttInformationHeader; | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned 8 bits - channel starting frequency that, together with the | ||||||
|  | 		 * Channel Number field, indicates the primary channel of the BSSs of the APs | ||||||
|  | 		 * in this Neighbor AP Information field | ||||||
|  | 		 */ | ||||||
|  | 		public final short operatingClass; | ||||||
|  | 		/** | ||||||
|  | 		 * Unsigned 8 bits - the last known primary channel of the APs in this | ||||||
|  | 		 * Neighbor AP Information field. | ||||||
|  | 		 */ | ||||||
|  | 		public final short channelNumber; | ||||||
|  | 		/** | ||||||
|  | 		 * @see TbttInformation | ||||||
|  | 		 */ | ||||||
|  | 		public final TbttInformation tbttInformation; | ||||||
|  |  | ||||||
|  | 		/** Constructor */ | ||||||
|  | 		public NeighborApInformation( | ||||||
|  | 			TbttInformationHeader tbttInformationHeader, | ||||||
|  | 			short operatingClass, | ||||||
|  | 			short channelNumber, | ||||||
|  | 			TbttInformation tbttInformation | ||||||
|  | 		) { | ||||||
|  | 			this.tbttInformationHeader = tbttInformationHeader; | ||||||
|  | 			this.operatingClass = operatingClass; | ||||||
|  | 			this.channelNumber = channelNumber; | ||||||
|  | 			this.tbttInformation = tbttInformation; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		/** Parse NeighborApInformation from JSON object */ | ||||||
|  | 		// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 		// present, we have no idea what the format looks like | ||||||
|  | 		public static NeighborApInformation parse(JsonObject contents) { | ||||||
|  | 			return new NeighborApInformation( | ||||||
|  | 				TbttInformationHeader.parse( | ||||||
|  | 					contents.get("TBTT Information Header").getAsJsonObject() | ||||||
|  | 				), | ||||||
|  | 				contents.get("Operating Class").getAsShort(), | ||||||
|  | 				contents.get("Channel Number").getAsShort(), | ||||||
|  | 				TbttInformation.parse(contents.get("TBTT Information").getAsJsonObject()) | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public int hashCode() { | ||||||
|  | 			return Objects.hash( | ||||||
|  | 				tbttInformationHeader, | ||||||
|  | 				operatingClass, | ||||||
|  | 				channelNumber, | ||||||
|  | 				tbttInformation | ||||||
|  | 			); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@Override | ||||||
|  | 		public boolean equals(Object obj) { | ||||||
|  | 			if (obj == null) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (this == obj) { | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			if (getClass() != obj.getClass()) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			NeighborApInformation other = (NeighborApInformation) obj; | ||||||
|  | 			return tbttInformationHeader.equals(other.tbttInformationHeader) && | ||||||
|  | 				operatingClass == other.operatingClass && | ||||||
|  | 				channelNumber == other.channelNumber && | ||||||
|  | 				Objects.equals(tbttInformation, other.tbttInformation); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** number of channels in a subband of supported channels */ | ||||||
|  | 	public final List<NeighborApInformation> neighborApInformations; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public ReducedNeighborReport( | ||||||
|  | 		List<NeighborApInformation> neighborApInformations | ||||||
|  | 	) { | ||||||
|  | 		this.neighborApInformations = | ||||||
|  | 			Collections.unmodifiableList(neighborApInformations); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse ReducedNeighborReport from JSON object */ | ||||||
|  | 	// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 	// present, we have no idea what the format looks like | ||||||
|  | 	public static ReducedNeighborReport parse(JsonObject contents) { | ||||||
|  | 		List<NeighborApInformation> neighborApInformations = new ArrayList<>(); | ||||||
|  |  | ||||||
|  | 		JsonElement neighborApInformationsObject = | ||||||
|  | 			contents.get("Neighbor AP Informations"); | ||||||
|  | 		if (neighborApInformationsObject != null) { | ||||||
|  | 			for ( | ||||||
|  | 				JsonElement elem : neighborApInformationsObject.getAsJsonArray() | ||||||
|  | 			) { | ||||||
|  | 				neighborApInformations | ||||||
|  | 					.add(NeighborApInformation.parse(elem.getAsJsonObject())); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return new ReducedNeighborReport(neighborApInformations); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(neighborApInformations); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		ReducedNeighborReport other = (ReducedNeighborReport) obj; | ||||||
|  | 		return neighborApInformations.equals(other.neighborApInformations); | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,70 @@ | |||||||
|  | /* | ||||||
|  |  * 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; | ||||||
|  |  | ||||||
|  | // NOTE: Not validated (not seen on test devices) | ||||||
|  | /** | ||||||
|  |  * This information element (IE) appears in wifiscan entries. It's called | ||||||
|  |  * "Supported Channels" in 802.11 specs (section 9.4.2.17). Refer to the | ||||||
|  |  * specification for more details. Language in javadocs is taken from the | ||||||
|  |  * specification. | ||||||
|  |  */ | ||||||
|  | public class SupportedChannels { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 36; | ||||||
|  |  | ||||||
|  | 	/** Unsigned 8 bits - first channel in a subband of supported channels */ | ||||||
|  | 	public final short firstChannelNumber; | ||||||
|  | 	/** Unsigned 8 bits - number of channels in a subband of supported channels */ | ||||||
|  | 	public final short numberOfChannels; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public SupportedChannels(short firstChannelNumber, short numberOfChannels) { | ||||||
|  | 		this.firstChannelNumber = firstChannelNumber; | ||||||
|  | 		this.numberOfChannels = numberOfChannels; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Parse SupportedChannels from JSON object */ | ||||||
|  | 	// TODO modify this method as necessary - since the IE doesn't seem to be | ||||||
|  | 	// present, we have no idea what the format looks like | ||||||
|  | 	public static SupportedChannels parse(JsonObject contents) { | ||||||
|  | 		return new SupportedChannels( | ||||||
|  | 			contents.get("First Channel Number").getAsShort(), | ||||||
|  | 			contents.get("Number of Channels").getAsShort() | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public int hashCode() { | ||||||
|  | 		return Objects.hash(firstChannelNumber, numberOfChannels); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@Override | ||||||
|  | 	public boolean equals(Object obj) { | ||||||
|  | 		if (obj == null) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (this == obj) { | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if (getClass() != obj.getClass()) { | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		SupportedChannels other = (SupportedChannels) obj; | ||||||
|  | 		return firstChannelNumber == other.firstChannelNumber && | ||||||
|  | 			numberOfChannels == other.numberOfChannels; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,116 @@ | |||||||
|  | /* | ||||||
|  |  * 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.facebook.openwifi.cloudsdk.IEUtils; | ||||||
|  | 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 (section 9.4.2.161). Refer to the specification for more details. Language in | ||||||
|  |  * javadocs is taken from the specification. | ||||||
|  |  */ | ||||||
|  | public class TxPwrInfo { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 195; | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - Local maximum transmit power for 20 MHz. Required field. | ||||||
|  | 	 */ | ||||||
|  | 	public final Short localMaxTxPwrConstraint20MHz; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - Local maximum transmit power for 40 MHz. Optional field. | ||||||
|  | 	 */ | ||||||
|  | 	public final Short localMaxTxPwrConstraint40MHz; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - Local maximum transmit power for 80 MHz. Optional field. | ||||||
|  | 	 */ | ||||||
|  | 	public final Short localMaxTxPwrConstraint80MHz; | ||||||
|  | 	/** | ||||||
|  | 	 * Unsigned 8 bits - Local maximum transmit power for both 160 MHz and 80+80 MHz. Optional field. | ||||||
|  | 	 */ | ||||||
|  | 	public final Short localMaxTxPwrConstraint160MHz; | ||||||
|  |  | ||||||
|  | 	/** Constructor */ | ||||||
|  | 	public TxPwrInfo( | ||||||
|  | 		short localMaxTxPwrConstraint20MHz, | ||||||
|  | 		Short localMaxTxPwrConstraint40MHz, | ||||||
|  | 		Short localMaxTxPwrConstraint80MHz, | ||||||
|  | 		Short 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 | ||||||
|  | 		short localMaxTxPwrConstraint20MHz = | ||||||
|  | 			innerObj.get("Local Max Tx Pwr Constraint 20MHz").getAsShort(); | ||||||
|  | 		// optional field | ||||||
|  | 		Short localMaxTxPwrConstraint40MHz = | ||||||
|  | 			IEUtils.parseOptionalShortField( | ||||||
|  | 				innerObj, | ||||||
|  | 				"Local Max Tx Pwr Constraint 40MHz" | ||||||
|  | 			); | ||||||
|  | 		Short localMaxTxPwrConstraint80MHz = | ||||||
|  | 			IEUtils.parseOptionalShortField( | ||||||
|  | 				innerObj, | ||||||
|  | 				"Local Max Tx Pwr Constraint 40MHz" | ||||||
|  | 			); | ||||||
|  | 		Short localMaxTxPwrConstraint160MHz = | ||||||
|  | 			IEUtils.parseOptionalShortField( | ||||||
|  | 				innerObj, | ||||||
|  | 				"Local Max Tx Pwr Constraint 40MHz" | ||||||
|  | 			); | ||||||
|  | 		return new TxPwrInfo( | ||||||
|  | 			localMaxTxPwrConstraint20MHz, | ||||||
|  | 			localMaxTxPwrConstraint40MHz, | ||||||
|  | 			localMaxTxPwrConstraint80MHz, | ||||||
|  | 			localMaxTxPwrConstraint160MHz | ||||||
|  | 		); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@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; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -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; | ||||||
| @@ -15,9 +15,12 @@ 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). Refer to the 802.11 | ||||||
|  |  * specification (section 9.4.2.158) | ||||||
|  */ |  */ | ||||||
| public class VHTOperationElement { | public class VHTOperation { | ||||||
|  | 	/** Defined in 802.11 table 9-92 */ | ||||||
|  | 	public static final int TYPE = 192; | ||||||
| 
 | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * 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 +33,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 +68,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 +92,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 +117,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 +145,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 +171,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); | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Information elements (IEs) defined in the 802.11 specifications. | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.cloudsdk.ies; | ||||||
| @@ -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; | ||||||
| 
 | 
 | ||||||
| @@ -69,7 +70,12 @@ public class UCentralKafkaConsumer { | |||||||
| 		/** The state payload JSON. */ | 		/** The state payload JSON. */ | ||||||
| 		public final JsonObject payload; | 		public final JsonObject payload; | ||||||
| 
 | 
 | ||||||
| 		/** Unix time (ms). */ | 		/** | ||||||
|  | 		 * The record timestamp (Unix time, in ms). | ||||||
|  | 		 * | ||||||
|  | 		 * Depending on the broker configuration for "message.timestamp.type", | ||||||
|  | 		 * this may either be the "CreateTime" or "LogAppendTime". | ||||||
|  | 		 */ | ||||||
| 		public final long timestampMs; | 		public final long timestampMs; | ||||||
| 
 | 
 | ||||||
| 		/** Constructor. */ | 		/** Constructor. */ | ||||||
| @@ -84,7 +90,12 @@ public class UCentralKafkaConsumer { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/** Kafka record listener interface. */ | 	/** | ||||||
|  | 	 * Kafka record listener interface. | ||||||
|  | 	 * | ||||||
|  | 	 * The inputs must NOT be mutated, as they may be passed to multiple | ||||||
|  | 	 * listeners and may result in ConcurrentModificationException. | ||||||
|  | 	 */ | ||||||
| 	public interface KafkaListener { | 	public interface KafkaListener { | ||||||
| 		/** Handle a list of state records. */ | 		/** Handle a list of state records. */ | ||||||
| 		void handleStateRecords(List<KafkaRecord> records); | 		void handleStateRecords(List<KafkaRecord> records); | ||||||
| @@ -270,7 +281,6 @@ public class UCentralKafkaConsumer { | |||||||
| 					serialNumber, | 					serialNumber, | ||||||
| 					payload.toString() | 					payload.toString() | ||||||
| 				); | 				); | ||||||
| 				// record.timestamp() is empirically confirmed to be Unix time (ms) |  | ||||||
| 				KafkaRecord kafkaRecord = | 				KafkaRecord kafkaRecord = | ||||||
| 					new KafkaRecord(serialNumber, payload, record.timestamp()); | 					new KafkaRecord(serialNumber, payload, record.timestamp()); | ||||||
| 				if (record.topic().equals(stateTopic)) { | 				if (record.topic().equals(stateTopic)) { | ||||||
| @@ -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; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Kafka consumer and producer functionality required by the CloudSDK. | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.cloudsdk.kafka; | ||||||
| @@ -0,0 +1,56 @@ | |||||||
|  | /* | ||||||
|  |  * 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.models.ap; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  |  | ||||||
|  | import com.google.gson.annotations.SerializedName; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * AP capabilities schema. | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-ucentral-schema/blob/main/system/capabilities.uc">capabilities.uc</a> | ||||||
|  |  */ | ||||||
|  | public class Capabilities { | ||||||
|  | 	public String compatible; | ||||||
|  | 	public String model; | ||||||
|  | 	public String platform; | ||||||
|  | 	public Map<String, List<String>> network; | ||||||
|  |  | ||||||
|  | 	public static class Switch { | ||||||
|  | 		public boolean enable; | ||||||
|  | 		public boolean reset; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	@SerializedName("switch") public Map<String, Switch> switch_; | ||||||
|  |  | ||||||
|  | 	public static class Phy { | ||||||
|  | 		public int tx_ant; | ||||||
|  | 		public int rx_ant; | ||||||
|  | 		public int[] frequencies; | ||||||
|  | 		public int[] channels; | ||||||
|  | 		public int[] dfs_channels; | ||||||
|  | 		public String[] htmode; | ||||||
|  | 		public String[] band; | ||||||
|  | 		public int ht_capa; | ||||||
|  | 		public int vht_capa; | ||||||
|  | 		public int[] he_phy_capa; | ||||||
|  | 		public int[] he_mac_capa; | ||||||
|  | 		public String country; | ||||||
|  | 		public String dfs_region; | ||||||
|  | 		public int temperature; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public Map<String, Phy> wifi; | ||||||
|  | 	// TODO The fields below were omitted | ||||||
|  | 	// macaddr; | ||||||
|  | 	// country_code; | ||||||
|  | 	// label_macaddr; | ||||||
|  | } | ||||||
| @@ -6,14 +6,19 @@ | |||||||
|  * 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; | ||||||
| 
 | 
 | ||||||
|  | /** | ||||||
|  |  * AP statistics/telemetry schema. | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-ucentral-schema/blob/main/state/state.yml">state.yml</a> | ||||||
|  |  */ | ||||||
| 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 +26,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; | ||||||
| @@ -54,6 +59,8 @@ public class State { | |||||||
| 				public int ack_signal; | 				public int ack_signal; | ||||||
| 				public int ack_signal_avg; | 				public int ack_signal_avg; | ||||||
| 				public JsonObject[] tid_stats; // TODO: see cfg80211_tid_stats | 				public JsonObject[] tid_stats; // TODO: see cfg80211_tid_stats | ||||||
|  | 
 | ||||||
|  | 				// TODO ipaddr_v4 - either string or object (ip4leases), but duplicated in "clients" | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
| 			public Association[] associations; | 			public Association[] associations; | ||||||
| @@ -66,7 +73,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 +103,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 +119,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; // TODO might be int[] array?? | ||||||
|  | 		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; | ||||||
| @@ -0,0 +1,96 @@ | |||||||
|  | /* | ||||||
|  |  * 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.models.ap; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import com.google.gson.JsonPrimitive; | ||||||
|  | import com.google.gson.annotations.SerializedName; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * AP configuration schema. | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-ucentral-schema/blob/main/schema/ucentral.yml">ucentral.yml</a> | ||||||
|  |  */ | ||||||
|  | public class UCentralSchema { | ||||||
|  | 	public static class Radio { | ||||||
|  | 		public String band; | ||||||
|  | 		public int bandwidth; | ||||||
|  | 		public JsonPrimitive channel; // either "auto" or int | ||||||
|  | 		@SerializedName("valid-channels") public int[] validChannels; | ||||||
|  | 		public String country; | ||||||
|  | 		@SerializedName("allow-dfs") public boolean allowDfs; | ||||||
|  | 		@SerializedName("channel-mode") public String channelMode; | ||||||
|  | 		@SerializedName("channel-width") public int channelWidth; | ||||||
|  | 		@SerializedName("require-mode") public String requireMode; | ||||||
|  | 		public String mimo; | ||||||
|  | 		@SerializedName("tx-power") public int txPower; | ||||||
|  | 		@SerializedName("legacy-rates") public boolean legacyRates; | ||||||
|  | 		@SerializedName("beacon-interval") public int beaconInterval; | ||||||
|  | 		@SerializedName("dtim-period") public int dtimPeriod; | ||||||
|  | 		@SerializedName("maximum-clients") public int maximumClients; | ||||||
|  |  | ||||||
|  | 		public static class Rates { | ||||||
|  | 			public int beacon; | ||||||
|  | 			public int multicast; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		public Rates rates; | ||||||
|  |  | ||||||
|  | 		public static class HESettings { | ||||||
|  | 			@SerializedName("multiple-bssid") public boolean multipleBssid; | ||||||
|  | 			public boolean ema; | ||||||
|  | 			@SerializedName("bss-color") public int bssColor; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@SerializedName("he-settings") public HESettings heSettings; | ||||||
|  |  | ||||||
|  | 		@SerializedName("hostapd-iface-raw") public String[] hostapdIfaceRaw; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public List<Radio> radios; | ||||||
|  |  | ||||||
|  | 	public static class Metrics { | ||||||
|  | 		public static class Statistics { | ||||||
|  | 			public int interval; | ||||||
|  | 			public List<String> types; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		public Statistics statistics; | ||||||
|  |  | ||||||
|  | 		public static class Health { | ||||||
|  | 			public int interval; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		public Health health; | ||||||
|  |  | ||||||
|  | 		public static class WifiFrames { | ||||||
|  | 			public List<String> filters; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@SerializedName("wifi-frames") public WifiFrames wifiFrames; | ||||||
|  |  | ||||||
|  | 		public static class DhcpSnooping { | ||||||
|  | 			public List<String> filters; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		@SerializedName("dhcp-snooping") public DhcpSnooping dhcpSnooping; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public Metrics metrics; | ||||||
|  |  | ||||||
|  | 	// TODO also add fields below as needed | ||||||
|  | 	// unit | ||||||
|  | 	// globals | ||||||
|  | 	// definitions | ||||||
|  | 	// ethernet | ||||||
|  | 	// switch | ||||||
|  | 	// interfaces | ||||||
|  | 	// services | ||||||
|  | } | ||||||
| @@ -6,13 +6,20 @@ | |||||||
|  * 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; | import com.facebook.openwifi.cloudsdk.WifiScanEntry; | ||||||
| 
 | 
 | ||||||
| /** Represents a single entry in wifi scan results. */ | /** | ||||||
|  |  * Wi-Fi scan result schema. | ||||||
|  |  * <p> | ||||||
|  |  * Note that the {@code ies[]} array is not stored here, but parsed into | ||||||
|  |  * {@link WifiScanEntry#ieContainer}. | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-ucentral-schema/blob/main/command/cmd_wifiscan.uc">cmd_wifiscan.uc</a> | ||||||
|  |  */ | ||||||
| public class WifiScanEntryResult { | public class WifiScanEntryResult { | ||||||
| 	public int channel; | 	public int channel; | ||||||
| 	public long last_seen; | 	public long last_seen; | ||||||
| @@ -50,8 +57,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 +73,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 +83,6 @@ public class WifiScanEntryResult { | |||||||
| 			channel, | 			channel, | ||||||
| 			frequency, | 			frequency, | ||||||
| 			ht_oper, | 			ht_oper, | ||||||
| 			ies, |  | ||||||
| 			last_seen, | 			last_seen, | ||||||
| 			signal, | 			signal, | ||||||
| 			ssid, | 			ssid, | ||||||
| @@ -104,7 +107,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 | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Schemas originating from the AP-NOS (wlan-ap). | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-ucentral-schema">wlan-ucentral-schema</a> | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.cloudsdk.models.ap; | ||||||
| @@ -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,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.gw.models; | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| 
 | 
 | ||||||
| import com.google.gson.JsonObject; | import com.facebook.openwifi.cloudsdk.models.ap.Capabilities; | ||||||
| 
 | 
 | ||||||
| public class DeviceCapabilities { | public class DeviceCapabilities { | ||||||
| 	public JsonObject capabilities; | 	public Capabilities capabilities; | ||||||
| 	public long firstUpdate; | 	public long firstUpdate; | ||||||
| 	public long lastUpdate; | 	public long lastUpdate; | ||||||
| 	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; | ||||||
| 
 | 
 | ||||||
| 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; | ||||||
| @@ -0,0 +1,18 @@ | |||||||
|  | /* | ||||||
|  |  * 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.models.gw; | ||||||
|  |  | ||||||
|  | public class ScriptRequest { | ||||||
|  | 	public String serialNumber; | ||||||
|  | 	public long timeout = 30; // in seconds | ||||||
|  | 	public String type; // "shell", "ucode", "uci" | ||||||
|  | 	public String script; | ||||||
|  | 	public String scriptId; // required but unused? | ||||||
|  | 	public long when = 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.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; | ||||||
| 
 | 
 | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Schemas defined in the uCentral Gateway. | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-cloud-ucentralgw">wlan-cloud-ucentralgw</a> | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.cloudsdk.models.gw; | ||||||
| @@ -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; | ||||||
| 
 | 
 | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Schemas defined in the Provisioning service. | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-cloud-owprov">wlan-cloud-owprov</a> | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.cloudsdk.models.prov; | ||||||
| @@ -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; | ||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) Meta Platforms, Inc. and affiliates. | ||||||
|  |  * All rights reserved. | ||||||
|  |  * | ||||||
|  |  * This source code is licensed under the BSD-style license found in the | ||||||
|  |  * LICENSE file in the root directory of this source tree. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Schemas defined in the Provisioning service specific to RRM. | ||||||
|  |  * | ||||||
|  |  * @see <a href="https://github.com/Telecominfraproject/wlan-cloud-owprov/blob/main/openapi/rrm_provider.yaml">rrm_provider.yaml</a> | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.cloudsdk.models.prov.rrm; | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Library providing clients and models for the OpenWiFi uCentral-based | ||||||
|  |  * CloudSDK. | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.cloudsdk; | ||||||
							
								
								
									
										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, | ||||||
							
								
								
									
										3
									
								
								lib-rca/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								lib-rca/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | # Root Cause Analysis (RCA) Java Library | ||||||
|  | A Java library which analyzes statistics and provides root cause analysis (RCA) for clients. | ||||||
|  | This is a work in progress. | ||||||
							
								
								
									
										72
									
								
								lib-rca/pom.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								lib-rca/pom.xml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | <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-librca</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-librca</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> | ||||||
|  |   </dependencies> | ||||||
|  | </project> | ||||||
| @@ -0,0 +1,13 @@ | |||||||
|  | /* | ||||||
|  |  * 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.librca; | ||||||
|  |  | ||||||
|  | public class RootCauseAnalyzer { | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,212 @@ | |||||||
|  | /* | ||||||
|  |  * 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.librca.inputs; | ||||||
|  |  | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | /** Define root cause analysis configuration parameters */ | ||||||
|  | public final class RCAParams { | ||||||
|  | 	// Note: we expect to receive these parameters in json format, so for now | ||||||
|  | 	// we do not include a constructor which takes in the member vars as inputs | ||||||
|  |  | ||||||
|  | 	/** Look-back window in ms */ | ||||||
|  | 	public final int detectionWindowMs; | ||||||
|  |  | ||||||
|  | 	// KPI calculation parameters | ||||||
|  | 	/** Minimum acceptable estimated throughput (Mbps) */ | ||||||
|  | 	public final double minEstimatedThroughputMbps; | ||||||
|  | 	/** Percentile (units are %) of estimated throughputs to use as the KPI */ | ||||||
|  | 	public final double throughputAggregationPercentile; | ||||||
|  | 	/** Maximum acceptable latency (ms) */ | ||||||
|  | 	public final int maxLatencyThresholdMs; | ||||||
|  | 	/** Maximum acceptable jitter (ms) */ | ||||||
|  | 	public final int maxJitterThresholdMs; | ||||||
|  | 	/** | ||||||
|  | 	 * Maximum acceptable disconnection rate (disconnetions per hour). Note that | ||||||
|  | 	 * this signifies a rate and the units happen to be per hour - this does not | ||||||
|  | 	 * signify that every contiguous one-hour period be checked. | ||||||
|  | 	 */ | ||||||
|  | 	public final int maxDisconnectionRatePerHour; | ||||||
|  |  | ||||||
|  | 	// High Level metrics thresholds | ||||||
|  | 	/** Minimum acceptable tx rate (Mbps) */ | ||||||
|  | 	public final double minTxRateMbps; | ||||||
|  | 	/** Maximum acceptable Packet Error Rate (PER) (units are %) */ | ||||||
|  | 	public final double maxPERPercent; | ||||||
|  | 	/** Minimum acceptable idle airtime (units are %) */ | ||||||
|  | 	public final double minIdleAirtimePercent; | ||||||
|  | 	/** Maximum acceptable number of clients for one radio */ | ||||||
|  | 	public final int maxNumClients; | ||||||
|  |  | ||||||
|  | 	// Low Level metrics thresholds | ||||||
|  | 	/** Minimum acceptable RSSI (dBm) */ | ||||||
|  | 	public final int minRssidBm; | ||||||
|  | 	/** Maximum acceptable noise (dBm) */ | ||||||
|  | 	public final int maxNoisedBm; | ||||||
|  | 	/** Maximum acceptable intf airtime (units are %) */ | ||||||
|  | 	public final double maxIntfAirtimePercent; | ||||||
|  | 	/** Maximum acceptable number of neighbors */ | ||||||
|  | 	public final int maxNumNeighbors; | ||||||
|  | 	/** Minimum acceptable client bandwidth (MHz) for non-2G bands / */ | ||||||
|  | 	public final int minClientBandwidthMHz; | ||||||
|  | 	/** Minimum acceptable Access Point (AP) bandwidth (MHz) for non-2G bands */ | ||||||
|  | 	public final int minApBandwidthMHz; | ||||||
|  | 	/** Minimum acceptable self airtime ratio (units are %) */ | ||||||
|  | 	public final double minSelfAirtimeRatioPercent; | ||||||
|  | 	/** Maximum acceptable tx dropped ratio (units are %) */ | ||||||
|  | 	public final double maxTxDroppedRatioPercent; | ||||||
|  |  | ||||||
|  | 	/** Default constructor */ | ||||||
|  | 	public RCAParams() { | ||||||
|  | 		// 6 hours -> 21600000 ms | ||||||
|  | 		this.detectionWindowMs = 21600000; | ||||||
|  |  | ||||||
|  | 		this.minEstimatedThroughputMbps = 10; | ||||||
|  | 		this.throughputAggregationPercentile = 10.0; | ||||||
|  | 		this.maxLatencyThresholdMs = 50; | ||||||
|  | 		this.maxJitterThresholdMs = 20; | ||||||
|  | 		this.maxDisconnectionRatePerHour = 20; | ||||||
|  |  | ||||||
|  | 		this.minTxRateMbps = 50; | ||||||
|  | 		this.maxPERPercent = 10.0; | ||||||
|  | 		this.minIdleAirtimePercent = 10.0; | ||||||
|  | 		this.maxNumClients = 10; | ||||||
|  |  | ||||||
|  | 		this.minRssidBm = -70; | ||||||
|  | 		this.maxNoisedBm = -95; | ||||||
|  | 		this.maxIntfAirtimePercent = 75.0; | ||||||
|  | 		this.maxNumNeighbors = 10; | ||||||
|  | 		this.minClientBandwidthMHz = 80; | ||||||
|  | 		this.minApBandwidthMHz = 80; | ||||||
|  | 		this.minSelfAirtimeRatioPercent = 25.0; | ||||||
|  | 		this.maxTxDroppedRatioPercent = 0.1; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Confirm that the given value is positive. If it is not, add a String | ||||||
|  | 	 * describing the problem to {@code errors}. | ||||||
|  | 	 */ | ||||||
|  | 	private static void validatePositive( | ||||||
|  | 		String varName, | ||||||
|  | 		int value, | ||||||
|  | 		List<String> errors | ||||||
|  | 	) { | ||||||
|  | 		if (value <= 0) { | ||||||
|  | 			errors.add(varName + " must be positive."); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Confirm that the given value is positive. If it is not, add a String | ||||||
|  | 	 * describing the problem to {@code errors}. | ||||||
|  | 	 */ | ||||||
|  | 	private static void validatePositive( | ||||||
|  | 		String varName, | ||||||
|  | 		double value, | ||||||
|  | 		List<String> errors | ||||||
|  | 	) { | ||||||
|  | 		if (value <= 0) { | ||||||
|  | 			errors.add(varName + " must be positive."); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** | ||||||
|  | 	 * Confirm that the given value is a valid percentile (between 0 and 100 | ||||||
|  | 	 * inclusive). If it is not, add a String describing the problem to | ||||||
|  | 	 * {@code errors}. | ||||||
|  | 	 */ | ||||||
|  | 	private static void validatePercentile( | ||||||
|  | 		String varName, | ||||||
|  | 		double value, | ||||||
|  | 		List<String> errors | ||||||
|  | 	) { | ||||||
|  | 		if (value < 0 || value > 100) { | ||||||
|  | 			errors.add(varName + " must be between 0 and 100 inclusive."); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Return a list of errors (empty list of no errors) */ | ||||||
|  | 	public List<String> validate() { | ||||||
|  | 		List<String> errors = new ArrayList<>(); | ||||||
|  | 		validatePositive("Detection window", detectionWindowMs, errors); | ||||||
|  |  | ||||||
|  | 		validatePositive( | ||||||
|  | 			"Minimum estimated throughput", | ||||||
|  | 			minEstimatedThroughputMbps, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePercentile( | ||||||
|  | 			"Thoughput aggregation percentile", | ||||||
|  | 			throughputAggregationPercentile, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePositive( | ||||||
|  | 			"Maximum latency threshold", | ||||||
|  | 			maxLatencyThresholdMs, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePositive( | ||||||
|  | 			"Maximum jitter threshold", | ||||||
|  | 			maxJitterThresholdMs, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePositive( | ||||||
|  | 			"Maximum disconnection rate", | ||||||
|  | 			maxDisconnectionRatePerHour, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  |  | ||||||
|  | 		validatePositive("Minimum tx rate", minTxRateMbps, errors); | ||||||
|  | 		validatePercentile( | ||||||
|  | 			"Maximum Packet Error Rate (PER)", | ||||||
|  | 			maxPERPercent, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePercentile( | ||||||
|  | 			"Minimum idle airtime", | ||||||
|  | 			minIdleAirtimePercent, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePositive("Maximum number of clients", maxNumClients, errors); | ||||||
|  |  | ||||||
|  | 		validatePercentile( | ||||||
|  | 			"Maximum intf airtime", | ||||||
|  | 			maxIntfAirtimePercent, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePositive( | ||||||
|  | 			"Maximum number of neighbors", | ||||||
|  | 			maxNumNeighbors, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePositive( | ||||||
|  | 			"Minimum client bandwidth", | ||||||
|  | 			minClientBandwidthMHz, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePositive( | ||||||
|  | 			"Minimum Access Point (AP) bandwidth", | ||||||
|  | 			minApBandwidthMHz, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePercentile( | ||||||
|  | 			"Minimum self airtime ratio", | ||||||
|  | 			minSelfAirtimeRatioPercent, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  | 		validatePercentile( | ||||||
|  | 			"Maximum tx dropped ratio", | ||||||
|  | 			maxTxDroppedRatioPercent, | ||||||
|  | 			errors | ||||||
|  | 		); | ||||||
|  |  | ||||||
|  | 		return errors; | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -0,0 +1,12 @@ | |||||||
|  | /* | ||||||
|  |  * 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. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Library providing Root Cause Analysis (RCA) functionality. | ||||||
|  |  */ | ||||||
|  | package com.facebook.openwifi.librca; | ||||||
| @@ -0,0 +1,23 @@ | |||||||
|  | /* | ||||||
|  |  * 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.librca.stats; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Aggregated statistics for each client. | ||||||
|  |  * Mainly handle KPI and metric calculations. | ||||||
|  |  */ | ||||||
|  | public class ClientStats { | ||||||
|  | 	/** Client MAC */ | ||||||
|  | 	public String station; | ||||||
|  |  | ||||||
|  | 	/** LinkStats that are of the same station(client) */ | ||||||
|  | 	public List<LinkStats> connections; | ||||||
|  | } | ||||||
| @@ -0,0 +1,75 @@ | |||||||
|  | /* | ||||||
|  |  * 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.librca.stats; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Aggregation Statistics Model of InputStats. | ||||||
|  |  * Aggregate by bssid, station and RadioConfig. | ||||||
|  |  */ | ||||||
|  | public class LinkStats { | ||||||
|  | 	public static class RadioConfig { | ||||||
|  | 		public int channel; | ||||||
|  | 		public int channelWidth; | ||||||
|  | 		public int txPower; | ||||||
|  | 		public String phy; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public static class AssociationInfo { | ||||||
|  | 		/** Rate information for receive/transmit data rate. */ | ||||||
|  | 		public static class Rate { | ||||||
|  | 			public long bitRate; | ||||||
|  | 			public int chWidth; | ||||||
|  | 			public int mcs; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		public long connected; | ||||||
|  | 		public long inactive; | ||||||
|  | 		public int rssi; | ||||||
|  | 		public long rxBytes; | ||||||
|  | 		public long rxPackets; | ||||||
|  | 		public Rate rxRate; | ||||||
|  | 		public long txBytes; | ||||||
|  | 		public long txDuration; | ||||||
|  | 		public long txFailed; | ||||||
|  | 		public long txPackets; | ||||||
|  | 		public Rate txRate; | ||||||
|  | 		public long txRetries; | ||||||
|  | 		public int ackSignal; | ||||||
|  | 		public int ackSignalAvg; | ||||||
|  |  | ||||||
|  | 		// The metrics below are from Interface the client was connected to. | ||||||
|  | 		public long txPacketsCounters; | ||||||
|  | 		public long txErrorsCounters; | ||||||
|  | 		public long txDroppedCounters; | ||||||
|  |  | ||||||
|  | 		// The metrics below are from the radio the client was associated to. | ||||||
|  | 		public long activeMsRadio; | ||||||
|  | 		public long busyMsRadio; | ||||||
|  | 		public long noiseRadio; | ||||||
|  | 		public long receiveMsRadio; | ||||||
|  | 		public long transmitMsRadio; | ||||||
|  |  | ||||||
|  | 		/** Unix time in milliseconds */ | ||||||
|  | 		public long timestamp; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** BSSID of the AP radio */ | ||||||
|  | 	public String bssid; | ||||||
|  |  | ||||||
|  | 	/** Client MAC */ | ||||||
|  | 	public String station; | ||||||
|  |  | ||||||
|  | 	/** Radio configuration parameters */ | ||||||
|  | 	public RadioConfig radioConfig; | ||||||
|  |  | ||||||
|  | 	/** Association list */ | ||||||
|  | 	public List<AssociationInfo> associationInfoList; | ||||||
|  | } | ||||||
| @@ -0,0 +1,78 @@ | |||||||
|  | /* | ||||||
|  |  * 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.librca.stats.inputs; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Input data model. | ||||||
|  |  * | ||||||
|  |  * TODO: very incomplete | ||||||
|  |   */ | ||||||
|  | public class InputStats { | ||||||
|  | 	/** Radio parameters */ | ||||||
|  | 	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 static class SSID { | ||||||
|  | 		public static class Association { | ||||||
|  | 			public static class Rate { | ||||||
|  | 				public long bitrate; | ||||||
|  | 				public int chwidth; | ||||||
|  | 				public int mcs; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			public String bssid; // bssid of the AP radio | ||||||
|  | 			public String station; // client MAC | ||||||
|  | 			public long connected; | ||||||
|  | 			public long inactive; | ||||||
|  | 			public int rssi; | ||||||
|  | 			public long rx_bytes; | ||||||
|  | 			public long rx_packets; | ||||||
|  | 			public Rate rx_rate; | ||||||
|  | 			public long tx_bytes; | ||||||
|  | 			public long tx_duration; | ||||||
|  | 			public long tx_failed; | ||||||
|  | 			public long tx_offset; | ||||||
|  | 			public long tx_packets; | ||||||
|  | 			public Rate tx_rate; | ||||||
|  | 			public long tx_retries; | ||||||
|  | 			public int ack_signal; | ||||||
|  | 			public int ack_signal_avg; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		public Association[] associations; | ||||||
|  | 		public Radio radio; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/** Counters are for the wireless interface as a whole */ | ||||||
|  | 	public static class Counters { | ||||||
|  | 		public long rx_bytes; | ||||||
|  | 		public long rx_packets; | ||||||
|  | 		public long rx_errors; | ||||||
|  | 		public long rx_dropped; | ||||||
|  | 		public long tx_bytes; | ||||||
|  | 		public long tx_packets; | ||||||
|  | 		public long tx_errors; | ||||||
|  | 		public long tx_dropped; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	public SSID[] ssids; | ||||||
|  | 	public Counters counters; | ||||||
|  |  | ||||||
|  | 	/** Unix time in milliseconds */ | ||||||
|  | 	public long timestamp; | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user