48 Commits

Author SHA1 Message Date
Johann Hoffmann
9e22796758 Update checkout action version (#132)
Signed-off-by: Johann Hoffmann <johann.hoffmann@mailbox.org>
2022-11-28 17:32:48 -08:00
Jeffrey Han
f1c488eac8 Add package-info.java and other class-level javadocs (#131)
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-23 13:46:04 -08:00
Jeffrey Han
066c523df5 Add UCentralSchema and convert Modeler.DataModel.latestDeviceStatusRadios (#130) 2022-11-23 11:19:57 -08:00
zhiqiand
ba8c156e72 Data Models for RCA (#125)
Signed-off-by: zhiqiand <zhiqian@fb.com>
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
Co-authored-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-21 18:01:44 -08:00
Jeffrey Han
d73eb23920 Remove device config schema copy in ConfigManager (#129)
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-21 16:09:40 -08:00
Jeffrey Han
ac5a1c8887 Client steering notes, docs, fixes (#128)
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-14 12:55:43 -08:00
Jeffrey Han
ea3a13e98c Fix owprov API handler /inventory/<serial>?rrmSettings=true (#127)
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-14 11:00:23 -08:00
Jun Woo Shin
c1511e8e91 More IE parsing (#95)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
Co-authored-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-11 16:49:51 -08:00
Jeffrey Han
05c36a535f Fix several compile errors/warnings (#126) 2022-11-11 11:07:12 -08:00
zhiqiand
c94c31cb63 Refactor AggregatedState and add timestamp (#118)
* refactor AggregatedState and add timestamp

Signed-off-by: zhiqiand <zhiqian@fb.com>

* Update gitignore (#120)

Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>

* Add CDS for hotspot and ability to add extra JVM flags in runner (#124)

* address some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* refactor AggregatedState and add timestamp

Signed-off-by: zhiqiand <zhiqian@fb.com>

* address some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* deep copy via gson in State Constructor

Signed-off-by: zhiqiand <zhiqian@fb.com>

* address some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* address some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

Signed-off-by: zhiqiand <zhiqian@fb.com>
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
Co-authored-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
Co-authored-by: Jun Woo Shin <jwoos@users.noreply.github.com>
2022-11-08 15:30:17 -08:00
RockyMandayam2
404934eda9 Prepare for client steering (#122) 2022-11-07 14:29:13 -08:00
RockyMandayam2
80626388c8 Add rca params (#123) 2022-11-07 13:31:51 -08:00
Jun Woo Shin
a79359c69d Add CDS for hotspot and ability to add extra JVM flags in runner (#124) 2022-11-03 13:30:20 -04:00
Jeffrey Han
a638d70fd6 Update gitignore (#120)
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-02 13:31:02 -07:00
Jeffrey Han
e3705699b4 Add station pinger to RRM service (#116)
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-11-01 15:12:44 -07:00
RockyMandayam2
35eddf73cf Add lib-rca sub module (#113) 2022-10-31 15:44:33 -07:00
Jun Woo Shin
f031027684 JVM options for strings (#112) 2022-10-28 13:15:19 -04:00
Jun Woo Shin
a54f9a48be Move running image to ibm-semeru and introduce runner script (#111)
Signed-off-by: Jun Woo Shin <jwoos@meta.com>
2022-10-26 16:37:43 -04:00
Jeffrey Han
c22ebeea31 Add script API and some related utilities (#109) 2022-10-24 10:57:35 -07:00
Jun Woo Shin
e1b9052ecc Create separate Capabilities structure (#108) 2022-10-21 12:01:45 -04:00
Jun Woo Shin
c635af6c1d use eclipse temurin distribution of openjdk (#107)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>
2022-10-20 09:34:09 -04:00
Jun Woo Shin
37a904087d [WIFI-11247] change host to 0.0.0.0 (#106) 2022-10-19 09:47:59 -04:00
RockyMandayam2
6df81b7fef Use device capabilities to identify radio band (#104) 2022-10-13 19:05:05 -07:00
RockyMandayam2
8171cc74ae Remove lower and upper channel limit maps (#102) 2022-10-13 09:51:18 -07:00
Jeffrey Han
215d73fee5 Fix vendorReferenceUrl in RRMConfig broken in #99 (#103) 2022-10-12 18:23:37 -07:00
RockyMandayam2
ecbf8fa644 Fix javadoc and add override decorator (#101) 2022-10-12 15:39:11 -07:00
RockyMandayam2
19928e0286 Move AVAILABLE_CHANNELS_BAND to UCentralUtils before adding 6G support (#100) 2022-10-12 15:27:11 -07:00
Jun Woo Shin
da978611d0 [WIFI-10736] Spin up separate ports for internal and external (#64)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>
2022-10-12 15:45:44 -04:00
Jeffrey Han
e42eaa747b [WIFI-11196] Restructure repository into multimodule project (#99) 2022-10-12 11:57:32 -07:00
Dmitry Dunaev
264f114be2 [WIFI-10910] Chg: internal endpoint port (#98)
Signed-off-by: Dmitry Dunaev <dmitry@opsfleet.com>

Signed-off-by: Dmitry Dunaev <dmitry@opsfleet.com>
2022-10-11 19:44:46 +03:00
zhiqiand
dd2b485b00 Follow-up on stats aggregation (#96)
* fix some comments from Jerrey

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-10 15:59:04 -07:00
Jun Woo Shin
033d93beff Fix IE parsing (#97)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>
2022-10-07 14:59:50 -04:00
zhiqiand
e5d5f7d5c0 States aggregation (#82)
* initial

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix comments on AggregatedState and ModelerUtils

Signed-off-by: zhiqiand <zhiqian@fb.com>

* reformat and thread-safe

Signed-off-by: zhiqiand <zhiqian@fb.com>

* add buffer size for state

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* add javadoc

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix comments in TestUtils

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix some comments

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix tx_power

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix channel number

Signed-off-by: zhiqiand <zhiqian@fb.com>

* fix long type

Signed-off-by: zhiqiand <zhiqian@fb.com>

Signed-off-by: zhiqiand <zhiqian@fb.com>
2022-10-06 10:10:20 -07:00
RockyMandayam2
0b4fd49627 Handle invalid IEs (#94) 2022-10-03 12:41:22 -07:00
Jun Woo Shin
d81df03637 [WIFI-10943] Deal with "auto" value for channel and fix 80p80 representation (#92) 2022-09-30 17:53:32 -04:00
RockyMandayam2
594fd9fa91 Refactor IE parsing (#90) 2022-09-30 09:42:59 -07:00
RockyMandayam2
8c48a8901b Rename HTOperationElement, HTOperationElementTest, VHTOperationElement, and VHTOperationElementTest (#91) 2022-09-29 20:52:19 -07:00
Jun Woo Shin
0ac189f493 [WIFI-10819] parse cron into valid quartz cron (#89)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>
2022-09-29 14:37:18 -04:00
RockyMandayam2
df21d07ec9 Discard unneeded IEs (#78) 2022-09-29 11:28:48 -07:00
RockyMandayam2
01a070c9b7 Only push updates for desired zones/venues (#80) 2022-09-28 12:37:19 -07:00
Jun Woo Shin
5211eae7c6 update comment around token validation to clarify behavior (#87)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>
2022-09-26 17:01:58 -04:00
Jun Woo Shin
fafbda0bd8 update comments about validating tokens (#86)
Signed-off-by: Jun Woo Shin <jwoos@fb.com>
2022-09-26 15:51:10 -04:00
Jun Woo Shin
43c9aaafb2 Make inner classes static as necessary (#84) 2022-09-21 15:13:19 -04:00
RockyMandayam2
89e637cfeb Use short instead of byte to store unsigned byte values in VHTOperationElement (#81) 2022-09-21 08:19:54 -07:00
RockyMandayam2
0a64fb4963 Minor cleanup in TPC classes (#71) 2022-09-20 14:57:56 -07:00
RockyMandayam2
4191bc1a70 Separate createModel into two methods, one for single band and one for multi-band; sync the state and device status in multi-band test (#73) 2022-09-19 17:04:34 -07:00
Jeffrey Han
3b6e83d103 Bump default event loop timers (#77)
Signed-off-by: Jeffrey Han <39203126+elludraon@users.noreply.github.com>
2022-09-19 11:03:41 -07:00
Jun Woo Shin
27c36ff444 Make AP-AP TPC algorithm use tx power from statistics and fix TPC application to correct band (#76) 2022-09-16 17:45:40 -04:00
192 changed files with 9846 additions and 2224 deletions

View File

@@ -26,7 +26,7 @@ jobs:
DOCKER_REGISTRY_USERNAME: ucentral
steps:
- name: Checkout actions repo
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: Telecominfraproject/.github
path: github
@@ -57,7 +57,7 @@ jobs:
- docker
steps:
- name: Checkout actions repo
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: Telecominfraproject/.github
path: github

View File

@@ -18,7 +18,7 @@ jobs:
distribution: 'adopt'
cache: maven
- name: Build with Maven
run: mvn javadoc:javadoc
run: mvn javadoc:aggregate
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout actions repo
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
repository: Telecominfraproject/.github
path: github

View File

@@ -17,7 +17,7 @@ jobs:
HELM_REPO_USERNAME: ucentral
steps:
- name: Checkout uCentral assembly chart repo
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
path: wlan-cloud-rrm

22
.gitignore vendored
View File

@@ -1,12 +1,15 @@
/target
/*.log*
/device_config.json
/settings.json
/topology.json
/target/
*/target/
# owrrm specific
*.log*
device_config.json
settings.json
topology.json
# Eclipse
/.settings/
/bin/
.settings/
bin/
.metadata
.classpath
.project
@@ -17,3 +20,8 @@
*.iml
*.iws
*.ipr
# Miscellaneous files thzt should not be checked in
temp/
.DS_Store

View File

@@ -1,20 +1,22 @@
FROM maven:3-jdk-11 as build
FROM maven:3-eclipse-temurin-11 as build
WORKDIR /usr/src/java
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
RUN apt-get update && apt-get install -y gettext-base wget
RUN wget 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
FROM ibm-semeru-runtimes:open-11-jre
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 --from=build /usr/src/java/target/openwifi-rrm.jar /usr/local/bin/
EXPOSE 16789
COPY runner.sh /
COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/
EXPOSE 16789 16790
ENTRYPOINT ["/docker-entrypoint.sh"]
CMD ["java", "-XX:+IdleTuningGcOnIdle", "-Xtune:virtualized", \
"-jar", "/usr/local/bin/openwifi-rrm.jar", \
"run", "--config-env", \
"-t", "/owrrm-data/topology.json", \
"-d", "/owrrm-data/device_config.json"]
ENV JVM_IMPL=openj9
CMD ["/runner.sh", "java", "/usr/local/bin/openwifi-rrm.jar", \
"run", \
"--config-env", \
"-t", "/owrrm-data/topology.json", \
"-d", "/owrrm-data/device_config.json"]

28
Dockerfile-hotspot Normal file
View 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"]

View File

@@ -1,12 +1,10 @@
# OpenWiFi RRM Service
OpenWiFi uCentral-based radio resource management (RRM) service, providing a
cloud-based Wi-Fi RRM layer for APs running the OpenWiFi SDK.
[See here](owrrm/README.md) for details.
This service collects data from OpenWiFi APs (e.g. Wi-Fi scans, stats,
capabilities) via the uCentral Gateway and Kafka, and integrates with the
OpenWiFi Provisioning service to perform optimization across configured
"venues". It pushes new device configuration parameters to APs after RRM
algorithms are run (manually or periodically).
## Project Structure
This is an [Apache Maven] project with the following modules:
* `lib-cloudsdk` - OpenWiFi CloudSDK Java Library
* `owrrm` - OpenWiFi RRM Service
## Requirements
* **Running:** JRE 11.
@@ -16,7 +14,7 @@ algorithms are run (manually or periodically).
```
$ 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
[Dockerfile](Dockerfile).
@@ -27,34 +25,7 @@ $ mvn test
```
Unit tests are written using [JUnit 5].
## Usage
```
$ java -jar openwifi-rrm.jar [-h]
```
To start the service, use the `run` command while providing configuration via
either environment variables (`--config-env`) or a static JSON file
(`--config-file`, default `settings.json`). The following data is *required*:
* Service configuration
* Env: `SERVICECONFIG_PRIVATEENDPOINT`, `SERVICECONFIG_PUBLICENDPOINT`
* JSON: `serviceConfig` structure
* Kafka broker URL
* Env: `KAFKACONFIG_BOOTSTRAPSERVER`
* JSON: `kafkaConfig` structure
* MySQL database credentials
* Env: `DATABASECONFIG_SERVER`, `DATABASECONFIG_USER`, `DATABASECONFIG_PASSWORD`
* JSON: `databaseConfig` structure
## OpenAPI
This service provides an OpenAPI HTTP interface on the port specified in the
service configuration (`moduleConfig.apiServerParams`). An auto-generated
OpenAPI 3.0 document is hosted at the endpoints `/openapi.{yaml,json}` and is
written to [openapi.yaml](openapi.yaml) during the Maven "compile" phase.
## For Developers
See [IMPLEMENTATION.md](IMPLEMENTATION.md) for service architecture details and
[ALGORITHMS.md](ALGORITHMS.md) for descriptions of the RRM algorithms.
## Code Style
Code is auto-formatted using [Spotless] with a custom Eclipse style config (see
[spotless/eclipse-java-formatter.xml](spotless/eclipse-java-formatter.xml)).
This can be applied via Maven (but is *not* enforced at build time):

View File

@@ -29,8 +29,8 @@ services:
targetPort: 16789
protocol: TCP
restapiinternal:
servicePort: 17007
targetPort: 17007
servicePort: 16790
targetPort: 16790
protocol: TCP
checks:

3
lib-cloudsdk/README.md Normal file
View 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
View 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>

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -6,15 +6,20 @@
* 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.List;
import java.util.Set;
import com.facebook.openwifi.cloudsdk.models.ap.UCentralSchema;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
/**
* Wrapper around uCentral AP configuration.
@@ -51,33 +56,33 @@ public class UCentralApConfiguration {
return config.getAsJsonArray("radios").size();
}
/** Return all info in the radio config (or an empty array if none). */
public JsonArray getRadioConfigList() {
if (!config.has("radios") || !config.get("radios").isJsonArray()) {
return new JsonArray();
/** Return all info in the radio config (or an empty list if none). */
public List<UCentralSchema.Radio> getRadioConfigList() {
if (config.has("radios") && config.get("radios").isJsonArray()) {
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) */
public Set<String> getRadioBandsSet(JsonArray radioConfigList) {
public Set<String> getRadioBandsSet(
List<UCentralSchema.Radio> radioConfigList
) {
Set<String> radioBandsSet = new HashSet<>();
if (radioConfigList == null) {
return radioBandsSet;
}
for (
int radioIndex = 0; radioIndex < radioConfigList.size();
radioIndex++
) {
JsonElement e = radioConfigList.get(radioIndex);
if (!e.isJsonObject()) {
for (UCentralSchema.Radio radio : radioConfigList) {
if (radio == null || radio.band == null) {
continue;
}
JsonObject radioObject = e.getAsJsonObject();
if (!radioObject.has("band")) {
continue;
}
radioBandsSet.add(radioObject.get("band").getAsString());
radioBandsSet.add(radio.band);
}
return radioBandsSet;
}

View File

@@ -6,7 +6,7 @@
* 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.HashMap;
@@ -20,22 +20,22 @@ import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.facebook.openwifirrm.RRMConfig.UCentralConfig.UCentralSocketParams;
import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceConfigureRequest;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceListWithStatus;
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.facebook.openwifirrm.ucentral.gw.models.StatisticsRecords;
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
import com.facebook.openwifirrm.ucentral.gw.models.WifiScanRequest;
import com.facebook.openwifirrm.ucentral.prov.models.EntityList;
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTagList;
import com.facebook.openwifirrm.ucentral.prov.models.RRMDetails;
import com.facebook.openwifirrm.ucentral.prov.models.SerialNumberList;
import com.facebook.openwifirrm.ucentral.prov.models.VenueList;
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceConfigureRequest;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceListWithStatus;
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
import com.facebook.openwifi.cloudsdk.models.gw.ScriptRequest;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
import com.facebook.openwifi.cloudsdk.models.gw.WifiScanRequest;
import com.facebook.openwifi.cloudsdk.models.prov.EntityList;
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTagList;
import com.facebook.openwifi.cloudsdk.models.prov.RRMDetails;
import com.facebook.openwifi.cloudsdk.models.prov.SerialNumberList;
import com.facebook.openwifi.cloudsdk.models.prov.VenueList;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
@@ -127,8 +127,14 @@ public class UCentralClient {
/** uCentral password */
private final String password;
/** Socket parameters */
private final UCentralSocketParams socketParams;
/** Connection timeout for all requests, in ms */
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. */
private final Map<String, ServiceEvent> serviceEndpoints = new HashMap<>();
@@ -147,7 +153,9 @@ public class UCentralClient {
* (if needed)
* @param username uCentral username (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(
String rrmEndpoint,
@@ -155,13 +163,17 @@ public class UCentralClient {
String uCentralSecPublicEndpoint,
String username,
String password,
UCentralSocketParams socketParams
int connectTimeoutMs,
int socketTimeoutMs,
int wifiScanTimeoutMs
) {
this.rrmEndpoint = rrmEndpoint;
this.usePublicEndpoints = usePublicEndpoints;
this.username = username;
this.password = password;
this.socketParams = socketParams;
this.connectTimeoutMs = connectTimeoutMs;
this.socketTimeoutMs = socketTimeoutMs;
this.wifiScanTimeoutMs = wifiScanTimeoutMs;
if (usePublicEndpoints) {
setServicePublicEndpoint(OWSEC_SERVICE, uCentralSecPublicEndpoint);
@@ -305,8 +317,8 @@ public class UCentralClient {
endpoint,
service,
parameters,
socketParams.connectTimeoutMs,
socketParams.socketTimeoutMs
connectTimeoutMs,
socketTimeoutMs
);
}
@@ -349,8 +361,8 @@ public class UCentralClient {
endpoint,
service,
body,
socketParams.connectTimeoutMs,
socketParams.socketTimeoutMs
connectTimeoutMs,
socketTimeoutMs
);
}
@@ -454,8 +466,8 @@ public class UCentralClient {
String.format("device/%s/wifiscan", serialNumber),
OWGW_SERVICE,
req,
socketParams.connectTimeoutMs,
socketParams.wifiScanTimeoutMs
connectTimeoutMs,
wifiScanTimeoutMs
);
if (!response.isSuccess()) {
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. */
public InventoryTagList getProvInventory() {
HttpResponse<String> response = httpGet("inventory", OWPROV_SERVICE);
@@ -613,6 +690,19 @@ public class UCentralClient {
try {
return gson.fromJson(response.getBody(), RRMDetails.class);
} 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(
"Failed to deserialize to RRMDetails: %s",
response.getBody()

View File

@@ -6,7 +6,7 @@
* 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.Collections;
@@ -22,7 +22,7 @@ public final class UCentralConstants {
public static final String BAND_2G = "2G";
/** String of the 5 GHz band */
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
.unmodifiableList(Arrays.asList(BAND_2G, BAND_5G));

View File

@@ -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;
}
}

View File

@@ -6,11 +6,11 @@
* 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 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.
@@ -22,6 +22,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
* time reference.
*/
public long unixTimeMs;
/** Stores Information Elements (IEs) from the wifiscan entry. */
public InformationElements ieContainer;
/** Default Constructor. */
public WifiScanEntry() {}
@@ -30,13 +32,14 @@ public class WifiScanEntry extends WifiScanEntryResult {
public WifiScanEntry(WifiScanEntry o) {
super(o);
this.unixTimeMs = o.unixTimeMs;
this.ieContainer = o.ieContainer;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(unixTimeMs);
result = prime * result + Objects.hash(ieContainer, unixTimeMs);
return result;
}
@@ -52,7 +55,8 @@ public class WifiScanEntry extends WifiScanEntryResult {
return false;
}
WifiScanEntry other = (WifiScanEntry) obj;
return unixTimeMs == other.unixTimeMs;
return Objects.equals(ieContainer, other.ieContainer) &&
unixTimeMs == other.unixTimeMs;
}
@Override

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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., B0B31) 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;
}
}

View File

@@ -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);
}
}

View File

@@ -6,7 +6,7 @@
* 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.Objects;
@@ -15,9 +15,12 @@ import org.apache.commons.codec.binary.Base64;
/**
* 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. */
public final byte primaryChannel;
@@ -78,7 +81,7 @@ public class HTOperationElement {
* For details about the parameters, see the javadocs for the corresponding
* member variables.
*/
public HTOperationElement(
public HTOperation(
byte primaryChannel,
byte secondaryChannelOffset,
boolean staChannelWidth,
@@ -114,7 +117,7 @@ public class HTOperationElement {
}
/** Constructor with the most used parameters. */
public HTOperationElement(
public HTOperation(
byte primaryChannel,
byte secondaryChannelOffset,
boolean staChannelWidth,
@@ -141,7 +144,7 @@ public class HTOperationElement {
* @param htOper a base64 encoded properly formatted HT operation element (see
* 802.11)
*/
public HTOperationElement(String htOper) {
public HTOperation(String htOper) {
byte[] bytes = Base64.decodeBase64(htOper);
/*
* 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
* aggregating statistics; false otherwise.
*/
public boolean matchesForAggregation(HTOperationElement other) {
public boolean matchesForAggregation(HTOperation other) {
return other != null && primaryChannel == other.primaryChannel &&
secondaryChannelOffset == other.secondaryChannelOffset &&
staChannelWidth == other.staChannelWidth &&
@@ -211,8 +214,8 @@ public class HTOperationElement {
if (htOper1 == null || htOper2 == null) {
return false; // false if exactly one is null
}
HTOperationElement htOperObj1 = new HTOperationElement(htOper1);
HTOperationElement htOperObj2 = new HTOperationElement(htOper2);
HTOperation htOperObj1 = new HTOperation(htOper1);
HTOperation htOperObj2 = new HTOperation(htOper2);
return htOperObj1.matchesForAggregation(htOperObj2);
}
@@ -248,7 +251,7 @@ public class HTOperationElement {
if (getClass() != obj.getClass()) {
return false;
}
HTOperationElement other = (HTOperationElement) obj;
HTOperation other = (HTOperation) obj;
return Arrays.equals(basicHtMcsSet, other.basicHtMcsSet) &&
channelCenterFrequencySegment2 ==
other.channelCenterFrequencySegment2 &&
@@ -263,4 +266,4 @@ public class HTOperationElement {
staChannelWidth == other.staChannelWidth &&
stbcBeacon == other.stbcBeacon;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 APs 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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -6,7 +6,7 @@
* 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.Objects;
@@ -15,9 +15,12 @@ import org.apache.commons.codec.binary.Base64;
/**
* Very High Throughput (VHT) Operation Element, which is potentially present in
* wifiscan entries. Introduced in 802.11ac (2013).
* 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.
@@ -30,15 +33,23 @@ public class VHTOperationElement {
* 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
* 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
* 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
* 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
* 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
* 802.11 standard)
*/
public VHTOperationElement(String vhtOper) {
public VHTOperation(String vhtOper) {
byte[] bytes = Base64.decodeBase64(vhtOper);
this.channelWidth = bytes[0];
this.channel1 = bytes[1];
this.channel2 = bytes[2];
this.channel1 = (short) (bytes[1] & 0xff); // read as unsigned value
this.channel2 = (short) (bytes[2] & 0xff); // read as unsigned value
byte[] vhtMcsForNss = new byte[8];
vhtMcsForNss[0] = (byte) (bytes[3] >>> 6);
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
* member variables.
*/
public VHTOperationElement(
public VHTOperation(
byte channelWidth,
byte channel1,
byte channel2,
short channel1,
short channel2,
byte[] vhtMcsForNss
) {
/*
@@ -106,7 +117,7 @@ public class VHTOperationElement {
* @return true if the the operation elements "match" for the purpose of
* aggregating statistics; false otherwise.
*/
public boolean matchesForAggregation(VHTOperationElement other) {
public boolean matchesForAggregation(VHTOperation other) {
// check everything except vhtMcsForNss
return other != null && channel1 == other.channel1 &&
channel2 == other.channel2 && channelWidth == other.channelWidth;
@@ -134,8 +145,8 @@ public class VHTOperationElement {
if (vhtOper1 == null || vhtOper2 == null) {
return false; // false if exactly one is null
}
VHTOperationElement vhtOperObj1 = new VHTOperationElement(vhtOper1);
VHTOperationElement vhtOperObj2 = new VHTOperationElement(vhtOper2);
VHTOperation vhtOperObj1 = new VHTOperation(vhtOper1);
VHTOperation vhtOperObj2 = new VHTOperation(vhtOper2);
return vhtOperObj1.matchesForAggregation(vhtOperObj2);
}
@@ -160,9 +171,9 @@ public class VHTOperationElement {
if (getClass() != obj.getClass()) {
return false;
}
VHTOperationElement other = (VHTOperationElement) obj;
VHTOperation other = (VHTOperation) obj;
return channel1 == other.channel1 && channel2 == other.channel2 &&
channelWidth == other.channelWidth &&
Arrays.equals(vhtMcsForNss, other.vhtMcsForNss);
}
}
}

View File

@@ -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;

View File

@@ -6,13 +6,13 @@
* 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 org.apache.kafka.common.errors.WakeupException;
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
/**
* Kafka runner.

View File

@@ -6,7 +6,7 @@
* 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.util.ArrayList;
@@ -29,7 +29,8 @@ import org.apache.kafka.common.serialization.StringDeserializer;
import org.slf4j.Logger;
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.JsonObject;
@@ -69,7 +70,12 @@ public class UCentralKafkaConsumer {
/** The state payload JSON. */
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;
/** 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 {
/** Handle a list of state records. */
void handleStateRecords(List<KafkaRecord> records);
@@ -270,7 +281,6 @@ public class UCentralKafkaConsumer {
serialNumber,
payload.toString()
);
// record.timestamp() is empirically confirmed to be Unix time (ms)
KafkaRecord kafkaRecord =
new KafkaRecord(serialNumber, payload, record.timestamp());
if (record.topic().equals(stateTopic)) {

View File

@@ -6,7 +6,7 @@
* 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;
@@ -18,7 +18,7 @@ import org.apache.kafka.common.serialization.StringSerializer;
import org.slf4j.Logger;
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;
/**

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -6,14 +6,19 @@
* 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.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 Interface {
public class Client {
public static class Interface {
public static class Client {
public String mac;
public String[] ipv4_addresses;
public String[] ipv6_addresses;
@@ -21,9 +26,9 @@ public class State {
// TODO last_seen
}
public class SSID {
public class Association {
public class Rate {
public static class SSID {
public static class Association {
public static class Rate {
public long bitrate;
public int chwidth;
public boolean sgi;
@@ -54,6 +59,8 @@ public class State {
public int ack_signal;
public int ack_signal_avg;
public JsonObject[] tid_stats; // TODO: see cfg80211_tid_stats
// TODO ipaddr_v4 - either string or object (ip4leases), but duplicated in "clients"
}
public Association[] associations;
@@ -66,7 +73,7 @@ public class State {
public JsonObject radio;
}
public class Counters {
public static class Counters {
public long collisions;
public long multicast;
public long rx_bytes;
@@ -96,8 +103,8 @@ public class State {
public Interface[] interfaces;
public class Unit {
public class Memory {
public static class Unit {
public static class Memory {
public long buffered;
public long cached;
public long free;
@@ -112,8 +119,21 @@ public class State {
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
public JsonObject[] radios;
@SerializedName("link-state") public JsonObject linkState;
public JsonObject gps;
public JsonObject poe;

View File

@@ -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
}

View File

@@ -6,13 +6,20 @@
* 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 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 int channel;
public long last_seen;
@@ -50,8 +57,6 @@ public class WifiScanEntryResult {
public String vht_oper;
public int capability;
public int frequency;
/** IE = information element */
public JsonArray ies;
/** Default Constructor. */
public WifiScanEntryResult() {}
@@ -68,7 +73,6 @@ public class WifiScanEntryResult {
this.vht_oper = o.vht_oper;
this.capability = o.capability;
this.frequency = o.frequency;
this.ies = o.ies;
}
@Override
@@ -79,7 +83,6 @@ public class WifiScanEntryResult {
channel,
frequency,
ht_oper,
ies,
last_seen,
signal,
ssid,
@@ -104,7 +107,9 @@ public class WifiScanEntryResult {
capability == other.capability && channel == other.channel &&
frequency == other.frequency && Objects
.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

View File

@@ -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;

View File

@@ -6,7 +6,7 @@
* 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 boolean Read;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,12 +6,12 @@
* 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 JsonObject capabilities;
public Capabilities capabilities;
public long firstUpdate;
public long lastUpdate;
public String serialNumber;

View File

@@ -6,7 +6,7 @@
* 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 String serialNumber;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,6 +6,6 @@
* 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 }

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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 boolean enabled;

View File

@@ -6,7 +6,7 @@
* 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 String number;

View File

@@ -6,7 +6,7 @@
* 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 long created;

View File

@@ -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;
}

View File

@@ -6,7 +6,7 @@
* 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 static final String EVENT_JOIN = "join";

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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 UserInfo userInfo;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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 {
NO_CERTIFICATE, VALID_CERTIFICATE, MISMATCH_SERIAL, VERIFIED

View File

@@ -6,7 +6,7 @@
* 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 AclTemplate aclTemplate;

View File

@@ -6,13 +6,13 @@
* 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 String access_token;
public String refresh_token;
public String token_type;
public int expires_in;
public long expires_in;
public int idle_timeout;
public String username;
public long created;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -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;

View File

@@ -6,14 +6,14 @@
* 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 com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
public class DeviceConfiguration {
public class DeviceConfigurationElement {
public static class DeviceConfigurationElement {
public String name;
public String description;
public Integer weight;

View File

@@ -6,7 +6,7 @@
* 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 String rcOnly;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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 String parent;

View File

@@ -6,11 +6,11 @@
* 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 com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
public class Entity {
// from ObjectInfo

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,11 +6,11 @@
* 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 com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
public class InventoryTag {
// from ObjectInfo

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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 String name;

View File

@@ -6,12 +6,12 @@
* 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;
public class RRMDetails {
public class RRMDetailsImpl {
public static class RRMDetailsImpl {
public String vendor;
public String schedule;
public List<RRMAlgorithmDetails> algorithms;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,11 +6,11 @@
* 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 com.facebook.openwifirrm.ucentral.gw.models.NoteInfo;
import com.facebook.openwifi.cloudsdk.models.gw.NoteInfo;
public class Venue {
// from ObjectInfo

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -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;

View File

@@ -6,7 +6,7 @@
* 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;

View File

@@ -6,7 +6,7 @@
* 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 String vendor;

View File

@@ -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;

View File

@@ -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;

View 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

View File

@@ -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
);
}
}

View File

@@ -6,17 +6,17 @@
* 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 org.junit.jupiter.api.Test;
public class HTOperationElementTest {
public class HTOperationTest {
@Test
void testGetHtOper() {
String htOper = "AQAEAAAAAAAAAAAAAAAAAAAAAAAAAA==";
HTOperationElement htOperObj = new HTOperationElement(htOper);
HTOperation htOperObj = new HTOperation(htOper);
byte expectedPrimaryChannel = 1;
byte expectedSecondaryChannelOffset = 0;
boolean expectedStaChannelWidth = false;
@@ -28,7 +28,7 @@ public class HTOperationElementTest {
boolean expectedDualBeacon = false;
boolean expectedDualCtsProtection = false;
boolean expectedStbcBeacon = false;
HTOperationElement expectedHtOperObj = new HTOperationElement(
HTOperation expectedHtOperObj = new HTOperation(
expectedPrimaryChannel,
expectedSecondaryChannelOffset,
expectedStaChannelWidth,
@@ -44,11 +44,11 @@ public class HTOperationElementTest {
assertEquals(expectedHtOperObj, htOperObj);
htOper = "JAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==";
htOperObj = new HTOperationElement(htOper);
htOperObj = new HTOperation(htOper);
// all fields except the primary channel and nongreenfield field are the same
expectedPrimaryChannel = 36;
expectedNongreenfieldHtStasPresent = false;
expectedHtOperObj = new HTOperationElement(
expectedHtOperObj = new HTOperation(
expectedPrimaryChannel,
expectedSecondaryChannelOffset,
expectedStaChannelWidth,

View File

@@ -6,23 +6,23 @@
* 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 org.junit.jupiter.api.Test;
public class VHTOperationElementTest {
public class VHTOperationTest {
@Test
void testGetVhtOper() {
String vhtOper = "ACQAAAA=";
VHTOperationElement vhtOperObj = new VHTOperationElement(vhtOper);
VHTOperation vhtOperObj = new VHTOperation(vhtOper);
byte expectedChannelWidthIndicator = 0; // 20 MHz channel width
byte expectedChannel1 = 36;
byte expectedChannel2 = 0;
short expectedChannel1 = 36;
short expectedChannel2 = 0;
byte[] expectedVhtMcsForNss = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 };
VHTOperationElement expectedVhtOperObj = new VHTOperationElement(
VHTOperation expectedVhtOperObj = new VHTOperation(
expectedChannelWidthIndicator,
expectedChannel1,
expectedChannel2,
@@ -31,12 +31,12 @@ public class VHTOperationElementTest {
assertEquals(expectedVhtOperObj, vhtOperObj);
vhtOper = "AToAUAE=";
vhtOperObj = new VHTOperationElement(vhtOper);
vhtOperObj = new VHTOperation(vhtOper);
expectedChannelWidthIndicator = 1; // 80 MHz channel width
expectedChannel1 = 58;
// same channel2
expectedVhtMcsForNss = new byte[] { 1, 1, 0, 0, 0, 0, 0, 1 };
expectedVhtOperObj = new VHTOperationElement(
expectedVhtOperObj = new VHTOperation(
expectedChannelWidthIndicator,
expectedChannel1,
expectedChannel2,
@@ -45,12 +45,27 @@ public class VHTOperationElementTest {
assertEquals(expectedVhtOperObj, vhtOperObj);
vhtOper = "ASoyUAE=";
vhtOperObj = new VHTOperationElement(vhtOper);
vhtOperObj = new VHTOperation(vhtOper);
// same channel width indicator (160 MHz channel width)
expectedChannel1 = 42;
expectedChannel2 = 50;
// 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,
expectedChannel1,
expectedChannel2,

3
lib-rca/README.md Normal file
View 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
View 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>

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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