33 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
191 changed files with 8206 additions and 1778 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,9 +6,8 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral;
package com.facebook.openwifi.cloudsdk;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -21,24 +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.WebTokenRefreshRequest;
import com.facebook.openwifirrm.ucentral.gw.models.WebTokenResult;
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;
@@ -130,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<>();
@@ -140,18 +143,7 @@ public class UCentralClient {
* The access token obtained from uCentralSec, needed only when using public
* endpoints.
*/
private WebTokenResult accessToken;
/**
* The unix timestamp (in seconds) keeps track of when the accessToken is created.
*/
private long created;
/**
* The unix timestamp (in seconds) keeps track of last time when the accessToken
* is accessed.
*/
private long lastAccess;
private String accessToken;
/**
* Constructor.
@@ -161,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,
@@ -169,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);
@@ -198,8 +196,7 @@ public class UCentralClient {
Map<String, Object> body = new HashMap<>();
body.put("userId", username);
body.put("password", password);
HttpResponse<String> response =
httpPost("oauth2", OWSEC_SERVICE, body, null);
HttpResponse<String> response = httpPost("oauth2", OWSEC_SERVICE, body);
if (!response.isSuccess()) {
logger.error(
"Login failed: Response code {}, body: {}",
@@ -210,146 +207,27 @@ public class UCentralClient {
}
// Parse access token from response
WebTokenResult token;
JSONObject respBody;
try {
token = gson.fromJson(response.getBody(), WebTokenResult.class);
} catch (JsonSyntaxException e) {
respBody = new JSONObject(response.getBody());
} catch (JSONException e) {
logger.error("Login failed: Unexpected response", e);
logger.debug("Response body: {}", response.getBody());
return false;
}
if (
token == null || token.access_token == null ||
token.access_token.isEmpty()
) {
if (!respBody.has("access_token")) {
logger.error("Login failed: Missing access token");
logger.debug("Response body: {}", response.getBody());
logger.debug("Response body: {}", respBody.toString());
return false;
}
this.accessToken = token;
this.created = accessToken.created;
this.lastAccess = accessToken.created;
this.accessToken = respBody.getString("access_token");
logger.info("Login successful as user: {}", username);
logger.debug("Access token: {}", accessToken.access_token);
logger.debug("Refresh token: {}", accessToken.refresh_token);
logger.debug("Access token: {}", accessToken);
// Load system endpoints
return loadSystemEndpoints();
}
/**
* when using public endpoints, refresh the access token if it's expired.
*/
public synchronized void refreshAccessToken() {
if (usePublicEndpoints) {
refreshAccessTokenImpl();
}
}
/**
* Check if the token is completely expired even if
* for a token refresh request
*
* @return true if the refresh token is expired
*/
private boolean isAccessTokenExpired() {
if (accessToken == null) {
return true;
}
return created + accessToken.expires_in <
Instant.now().getEpochSecond();
}
/**
* Check if an access token is expired.
*
* @return true if the access token is expired
*/
private boolean isAccessTokenTimedOut() {
if (accessToken == null) {
return true;
}
return lastAccess + accessToken.idle_timeout <
Instant.now().getEpochSecond();
}
/**
* Refresh the access toke when time out. If the refresh token is expired, login again.
* If the access token is expired, POST a WebTokenRefreshRequest to refresh token.
*/
private void refreshAccessTokenImpl() {
if (!usePublicEndpoints) {
return;
}
if (isAccessTokenExpired()) {
synchronized (this) {
if (isAccessTokenExpired()) {
logger.info("Token is expired, login again");
login();
}
}
} else if (isAccessTokenTimedOut()) {
synchronized (this) {
if (isAccessTokenTimedOut()) {
logger.debug("Access token timed out, refreshing the token");
accessToken = refreshToken();
created = Instant.now().getEpochSecond();
lastAccess = created;
if (accessToken != null) {
logger.debug("Successfully refresh token.");
}else{
logger.error(
"Fail to refresh token with access token: {}",
accessToken.access_token
);
}
}
}
}
}
/**
* POST a WebTokenRefreshRequest to refresh the access token.
*
* @return valid access token if success, otherwise return null.
*/
private WebTokenResult refreshToken() {
if (accessToken == null) {
return null;
}
WebTokenRefreshRequest refreshRequest = new WebTokenRefreshRequest();
refreshRequest.userId = username;
refreshRequest.refreshToken = accessToken.refresh_token;
logger.debug("refresh token: {}", accessToken.refresh_token);
Map<String, Object> parameters =
Collections.singletonMap("grant_type", "refresh_token");
HttpResponse<String> response =
httpPost(
"oauth2",
OWSEC_SERVICE,
refreshRequest,
parameters
);
if (!response.isSuccess()) {
logger.error(
"Failed to refresh token: Response code {}, body: {}",
response.getStatus(),
response.getBody()
);
return null;
}
try {
return gson.fromJson(response.getBody(), WebTokenResult.class);
} catch (JsonSyntaxException e) {
logger.error(
"Failed to serialize WebTokenResult: Unexpected response:",
e
);
logger.debug("Response body: {}", response.getBody());
return null;
}
}
/** Read system endpoint URLs from uCentralSec. */
private boolean loadSystemEndpoints() {
// Make request
@@ -439,8 +317,8 @@ public class UCentralClient {
endpoint,
service,
parameters,
socketParams.connectTimeoutMs,
socketParams.socketTimeoutMs
connectTimeoutMs,
socketTimeoutMs
);
}
@@ -458,12 +336,8 @@ public class UCentralClient {
.connectTimeout(connectTimeoutMs)
.socketTimeout(socketTimeoutMs);
if (usePublicEndpoints) {
if (!isAccessTokenExpired()) {
req.header(
"Authorization",
"Bearer " + accessToken.access_token
);
lastAccess = Instant.now().getEpochSecond();
if (accessToken != null) {
req.header("Authorization", "Bearer " + accessToken);
}
} else {
req
@@ -477,29 +351,26 @@ public class UCentralClient {
}
}
/** Send a POST request with a JSON body and query params. */
/** Send a POST request with a JSON body. */
private HttpResponse<String> httpPost(
String endpoint,
String service,
Object body,
Map<String, Object> parameters
Object body
) {
return httpPost(
endpoint,
service,
body,
parameters,
socketParams.connectTimeoutMs,
socketParams.socketTimeoutMs
connectTimeoutMs,
socketTimeoutMs
);
}
/** Send a POST request with a JSON body and query params using given timeout values. */
/** Send a POST request with a JSON body using given timeout values. */
private HttpResponse<String> httpPost(
String endpoint,
String service,
Object body,
Map<String, Object> parameters,
int connectTimeoutMs,
int socketTimeoutMs
) {
@@ -508,16 +379,9 @@ public class UCentralClient {
.header("accept", "application/json")
.connectTimeout(connectTimeoutMs)
.socketTimeout(socketTimeoutMs);
if (parameters != null && !parameters.isEmpty()) {
req.queryString(parameters);
}
if (usePublicEndpoints) {
if (!isAccessTokenExpired()) {
req.header(
"Authorization",
"Bearer " + accessToken.access_token
);
lastAccess = Instant.now().getEpochSecond();
if (accessToken != null) {
req.header("Authorization", "Bearer " + accessToken);
}
} else {
req
@@ -602,9 +466,8 @@ public class UCentralClient {
String.format("device/%s/wifiscan", serialNumber),
OWGW_SERVICE,
req,
null,
socketParams.connectTimeoutMs,
socketParams.wifiScanTimeoutMs
connectTimeoutMs,
wifiScanTimeoutMs
);
if (!response.isSuccess()) {
logger.error("Error: {}", response.getBody());
@@ -631,8 +494,7 @@ public class UCentralClient {
HttpResponse<String> response = httpPost(
String.format("device/%s/configure", serialNumber),
OWGW_SERVICE,
req,
null
req
);
if (!response.isSuccess()) {
logger.error("Error: {}", response.getBody());
@@ -701,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);
@@ -763,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

@@ -6,29 +6,34 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral;
package com.facebook.openwifi.cloudsdk;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.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.openwifirrm.RRMConfig;
import com.facebook.openwifirrm.Utils;
import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer;
import com.facebook.openwifirrm.ucentral.informationelement.Country;
import com.facebook.openwifirrm.ucentral.informationelement.LocalPowerConstraint;
import com.facebook.openwifirrm.ucentral.informationelement.QbssLoad;
import com.facebook.openwifirrm.ucentral.informationelement.TxPwrInfo;
import com.facebook.openwifirrm.ucentral.models.State;
import com.facebook.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;
@@ -47,25 +52,47 @@ public class UCentralUtils {
/** The Gson instance. */
private static final Gson gson = new Gson();
/** Map of band to the band-specific lowest available channel*/
public static final Map<String, Integer> LOWER_CHANNEL_LIMIT =
new HashMap<>();
static {
UCentralUtils.LOWER_CHANNEL_LIMIT.put(UCentralConstants.BAND_2G, 1);
UCentralUtils.LOWER_CHANNEL_LIMIT.put(UCentralConstants.BAND_5G, 36);
}
/** Map of band to the band-specific highest available channel*/
public static final Map<String, Integer> UPPER_CHANNEL_LIMIT =
new HashMap<>();
static {
UCentralUtils.UPPER_CHANNEL_LIMIT.put(UCentralConstants.BAND_2G, 11);
UCentralUtils.UPPER_CHANNEL_LIMIT.put(UCentralConstants.BAND_5G, 165);
}
/** 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.
*
@@ -147,9 +174,13 @@ public class UCentralUtils {
case TxPwrInfo.TYPE:
ieContainer.txPwrInfo = TxPwrInfo.parse(contents);
break;
case RMEnabledCapabilities.TYPE:
ieContainer.rmEnabledCapabilities =
RMEnabledCapabilities.parse(contents);
break;
}
} catch (Exception e) {
logger.debug("Skipping invalid IE {}", ie);
logger.error(String.format("Skipping invalid IE %s", ie), e);
continue;
}
}
@@ -249,28 +280,20 @@ public class UCentralUtils {
* Returns the results map
*/
public static Map<String, List<String>> getBandsMap(
Map<String, JsonArray> deviceStatus
Map<String, List<UCentralSchema.Radio>> deviceStatus
) {
Map<String, List<String>> bandsMap = new HashMap<>();
for (String serialNumber : deviceStatus.keySet()) {
JsonArray radioList =
deviceStatus.get(serialNumber).getAsJsonArray();
for (
int radioIndex = 0; radioIndex < radioList.size(); radioIndex++
) {
JsonElement e = radioList.get(radioIndex);
if (!e.isJsonObject()) {
return null;
}
JsonObject radioObject = e.getAsJsonObject();
String band = radioObject.get("band").getAsString();
for (
Map.Entry<String, List<UCentralSchema.Radio>> entry : deviceStatus
.entrySet()
) {
String serialNumber = entry.getKey();
for (UCentralSchema.Radio radio : entry.getValue()) {
bandsMap
.computeIfAbsent(band, k -> new ArrayList<>())
.computeIfAbsent(radio.band, k -> new ArrayList<>())
.add(serialNumber);
}
}
return bandsMap;
}
@@ -284,75 +307,61 @@ public class UCentralUtils {
* @return the results map of {band, {device, list of available channels}}
*/
public static Map<String, Map<String, List<Integer>>> getDeviceAvailableChannels(
Map<String, JsonArray> deviceStatus,
Map<String, JsonObject> deviceCapabilities,
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 (String serialNumber : deviceStatus.keySet()) {
JsonArray radioList =
deviceStatus.get(serialNumber).getAsJsonArray();
for (
int radioIndex = 0; radioIndex < radioList.size(); radioIndex++
) {
JsonElement e = radioList.get(radioIndex);
if (!e.isJsonObject()) {
return null;
}
JsonObject radioObject = e.getAsJsonObject();
String band = radioObject.get("band").getAsString();
JsonObject capabilitesObject =
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 (capabilitesObject == null) {
if (capabilitiesPhyMap == null) {
availableChannels
.addAll(defaultAvailableChannels.get(band));
.addAll(defaultAvailableChannels.get(radio.band));
} else {
Set<Entry<String, JsonElement>> entrySet = capabilitesObject
.entrySet();
for (Map.Entry<String, JsonElement> f : entrySet) {
String bandInsideObject = f.getValue()
.getAsJsonObject()
.get("band")
.getAsString();
if (bandInsideObject.equals(band)) {
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 {
JsonArray channelInfo = f.getValue()
.getAsJsonObject()
.get("dfs_channels")
.getAsJsonArray();
for (JsonElement d : channelInfo) {
dfsChannels.add(d.getAsInt());
int[] channelInfo = phy.dfs_channels;
for (int d : channelInfo) {
dfsChannels.add(d);
}
} catch (Exception d) {}
try {
JsonArray channelInfo = f.getValue()
.getAsJsonObject()
.get("channels")
.getAsJsonArray();
for (JsonElement c : channelInfo) {
int channel = c.getAsInt();
int[] channelInfo = phy.channels;
for (int channel : channelInfo) {
if (!dfsChannels.contains(channel)) {
availableChannels.add(channel);
}
}
} catch (Exception c) {
availableChannels
.addAll(defaultAvailableChannels.get(band));
.addAll(
defaultAvailableChannels.get(radio.band)
);
}
}
}
}
deviceAvailableChannels.computeIfAbsent(
band,
radio.band,
k -> new HashMap<>()
)
.put(
@@ -371,10 +380,10 @@ public class UCentralUtils {
* Returns the results map
*/
public static Map<String, String> getBssidsMap(
Map<String, State> latestState
Map<String, ? extends State> latestState
) {
Map<String, String> bssidMap = new HashMap<>();
for (Map.Entry<String, State> e : latestState.entrySet()) {
for (Entry<String, ? extends State> e : latestState.entrySet()) {
State state = e.getValue();
for (
int interfaceIndex = 0;
@@ -399,47 +408,6 @@ public class UCentralUtils {
return bssidMap;
}
/** Generate the RRM service key. */
public static String generateServiceKey(
RRMConfig.ServiceConfig serviceConfig
) {
try {
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
sha256.update(serviceConfig.publicEndpoint.getBytes());
sha256.update(serviceConfig.privateEndpoint.getBytes());
return Utils.bytesToHex(sha256.digest());
} catch (NoSuchAlgorithmException e) {
logger.error("Unable to generate service key", e);
return "";
}
}
/**
* Converts channel number to that channel's center frequency in MHz.
*
* @param channel channel number. See
* {@link ChannelOptimizer#AVAILABLE_CHANNELS_BAND} for channels
* in each band.
* @return the center frequency of the given channel in MHz
*/
public static int channelToFrequencyMHz(int channel) {
if (
ChannelOptimizer.AVAILABLE_CHANNELS_BAND
.get(UCentralConstants.BAND_2G)
.contains(channel)
) {
return 2407 + 5 * channel;
} else if (
ChannelOptimizer.AVAILABLE_CHANNELS_BAND
.get(UCentralConstants.BAND_5G)
.contains(channel)
) {
return 5000 + channel;
} else {
throw new IllegalArgumentException("Must provide a valid channel.");
}
}
/**
* Determines if the given channel is in the given band.
*
@@ -448,24 +416,16 @@ public class UCentralUtils {
* @return true if the given channel is in the given band; false otherwise
*/
public static boolean isChannelInBand(int channel, String band) {
return LOWER_CHANNEL_LIMIT.get(band) <= channel &&
channel <= UPPER_CHANNEL_LIMIT.get(band);
return AVAILABLE_CHANNELS_BAND.get(band).contains(channel);
}
/**
* Given the channel, gets the band by checking lower bound and upper bound
* of each band
*
* @param channel channel number
* @return band if the channel can be mapped to a valid band; null otherwise
*/
public static String getBandFromChannel(int channel) {
for (String band : UCentralConstants.BANDS) {
if (isChannelInBand(channel, band)) {
return band;
}
/** Return which band contains the given frequency (MHz). */
public static String freqToBand(int freqMHz) {
if (2412 <= freqMHz && freqMHz <= 2484) {
return "2G";
} else {
return "5G";
}
return null;
}
/**
@@ -515,4 +475,116 @@ public class UCentralUtils {
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.

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

@@ -6,52 +6,47 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
package com.facebook.openwifi.cloudsdk.ies;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
* This information element (IE) appears in wifiscan entries.
* Refer to the 802.11 specification for more details. Language in
* javadocs is taken from the specification.
* 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 {
private static final Logger logger = LoggerFactory.getLogger(Country.class);
/** Defined in 802.11 */
/** 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 {
/**
* The lowest channel number in the CountryInfo.
* 8 bits unsigned - the lowest channel number in the CountryInfo.
*/
public final int firstChannelNumber;
public final short firstChannelNumber;
/**
* The maximum power, in dBm, allowed to be transmitted.
* 8 bits unsigned - The maximum power, in dBm, allowed to be transmitted.
*/
public final int maximumTransmitPowerLevel;
public final short maximumTransmitPowerLevel;
/**
* Number of channels this CountryInfo applies to. E.g., if First
* Channel Number is 2 and Number of Channels is 4, this CountryInfo
* 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 int numberOfChannels;
public final short numberOfChannels;
/** Constructor. */
public CountryInfo(
int firstChannelNumber,
int maximumTransmitPowerLevel,
int numberOfChannels
short firstChannelNumber,
short maximumTransmitPowerLevel,
short numberOfChannels
) {
this.firstChannelNumber = firstChannelNumber;
this.maximumTransmitPowerLevel = maximumTransmitPowerLevel;
@@ -60,13 +55,13 @@ public class Country {
/** Parse CountryInfo from the appropriate Json object. */
public static CountryInfo parse(JsonObject contents) {
final int firstChannelNumber =
contents.get("First Channel Number").getAsInt();
final int maximumTransmitPowerLevel = contents
final short firstChannelNumber =
contents.get("First Channel Number").getAsShort();
final short maximumTransmitPowerLevel = contents
.get("Maximum Transmit Power Level (in dBm)")
.getAsInt();
final int numberOfChannels =
contents.get("Number of Channels").getAsInt();
.getAsShort();
final short numberOfChannels =
contents.get("Number of Channels").getAsShort();
return new CountryInfo(
firstChannelNumber,
maximumTransmitPowerLevel,
@@ -99,15 +94,10 @@ public class Country {
maximumTransmitPowerLevel == other.maximumTransmitPowerLevel &&
numberOfChannels == other.numberOfChannels;
}
@Override
public String toString() {
return "CountryInfo [firstChannelNumber=" + firstChannelNumber +
", maximumTransmitPowerLevel=" + maximumTransmitPowerLevel +
", numberOfChannels=" + numberOfChannels + "]";
}
}
/** Country */
public final String country;
/**
* Each constraint is a CountryInfo describing tx power constraints on
* one or more channels, for the current country.
@@ -115,7 +105,11 @@ public class Country {
public final List<CountryInfo> constraints;
/** Constructor */
public Country(List<CountryInfo> countryInfos) {
public Country(
String country,
List<CountryInfo> countryInfos
) {
this.country = country;
this.constraints = Collections.unmodifiableList(countryInfos);
}
@@ -125,12 +119,16 @@ public class Country {
JsonElement constraintsObject = contents.get("constraints");
if (constraintsObject != null) {
for (JsonElement jsonElement : constraintsObject.getAsJsonArray()) {
JsonObject innerElem = jsonElement.getAsJsonObject();
CountryInfo countryInfo =
CountryInfo.parse(jsonElement.getAsJsonObject());
CountryInfo.parse(innerElem.get("Country Info").getAsJsonObject());
constraints.add(countryInfo);
}
}
return new Country(constraints);
return new Country(
contents.get("Code").getAsString(),
constraints
);
}
@Override
@@ -152,9 +150,4 @@ public class Country {
Country other = (Country) obj;
return Objects.equals(constraints, other.constraints);
}
@Override
public String toString() {
return "Country [constraints=" + constraints + "]";
}
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
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 HTOperation {
/** Defined in 802.11 table 9-92 */
public static final int TYPE = 61;
/** Channel number of the primary channel. */
public final byte primaryChannel;
@@ -263,4 +266,4 @@ public class HTOperation {
staChannelWidth == other.staChannelWidth &&
stbcBeacon == other.stbcBeacon;
}
}
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
package com.facebook.openwifi.cloudsdk.ies;
import java.util.Objects;
@@ -15,32 +15,31 @@ import com.google.gson.JsonObject;
/**
* This information element (IE) appears in wifiscan entries. It is called
* "Local Power Constraint" in these entries, and just "Power Constraint" in
* the 802.11 specification. Refer to the specification for more details.
* 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 */
/** Defined in 802.11 table 9-92 */
public static final int TYPE = 32;
/**
* Units are dB.
* 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 int localPowerConstraint;
public final short localPowerConstraint;
/** Constructor */
public LocalPowerConstraint(int localPowerConstraint) {
public LocalPowerConstraint(short localPowerConstraint) {
this.localPowerConstraint = localPowerConstraint;
}
/** Parse LocalPowerConstraint IE from appropriate Json object. */
public static LocalPowerConstraint parse(JsonObject contents) {
final int localPowerConstraint =
contents.get("Local Power Constraint").getAsInt();
final short localPowerConstraint =
contents.get("Local Power Constraint").getAsShort();
return new LocalPowerConstraint(localPowerConstraint);
}
@@ -63,10 +62,4 @@ public class LocalPowerConstraint {
LocalPowerConstraint other = (LocalPowerConstraint) obj;
return localPowerConstraint == other.localPowerConstraint;
}
@Override
public String toString() {
return "LocalPowerConstraint [localPowerConstraint=" +
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

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
package com.facebook.openwifi.cloudsdk.ies;
import java.util.Objects;
@@ -15,22 +15,21 @@ import com.google.gson.JsonObject;
/**
* This information element (IE) appears in wifiscan entries. It is called
* "QBSS Load" in these entries, and just "BSS Load" in the 802.11
* specification. Refer to the specification for more details. Language in
* "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 */
/** Defined in 802.11 table 9-92 */
public static final int TYPE = 11;
/**
* The total number of STAs currently associated with the BSS.
* Unsigned 16 bits - The total number of STAs currently associated with the BSS.
*/
public final int stationCount;
public final short stationCount;
/**
* The Channel Utilization field is defined as the percentage of time,
* linearly scaled with 255 representing 100%, that the AP sensed the
* 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
@@ -40,22 +39,22 @@ public class QbssLoad {
* (dot11ChannelUtilizationBeaconIntervals * dot11BeaconPeriod * 1024)
* )
*/
public final int channelUtilization;
public final short channelUtilization;
/**
* The Available Admission Capacity field contains an unsigned integer that
* specifies the remaining amount of medium time available via explicit
* admission control, in units of 32 miscrosecond/second. The field is
* helpful for roaming STAs to select an AP that is likely to accept future
* admission control requests, but it does not represent an assurance that
* the HC admits these requests.
* 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 int availableAdmissionCapacity;
public final short availableAdmissionCapacity;
/** Constructor */
public QbssLoad(
int stationCount,
int channelUtilization,
int availableAdmissionCapacity
short stationCount,
short channelUtilization,
short availableAdmissionCapacity
) {
this.stationCount = stationCount;
this.channelUtilization = channelUtilization;
@@ -70,11 +69,11 @@ public class QbssLoad {
return null;
}
contents = ccaContentJsonElement.getAsJsonObject();
final int stationCount = contents.get("Station Count").getAsInt();
final int channelUtilization =
contents.get("Channel Utilization").getAsInt();
final int availableAdmissionCapacity =
contents.get("Available Admission Capabilities").getAsInt();
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,
@@ -107,11 +106,4 @@ public class QbssLoad {
channelUtilization == other.channelUtilization &&
stationCount == other.stationCount;
}
@Override
public String toString() {
return "QbssLoad [stationCount=" + stationCount +
", channelUtilization=" + channelUtilization +
", availableAdmissionCapacity=" + availableAdmissionCapacity + "]";
}
}

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

@@ -6,39 +6,46 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
package com.facebook.openwifi.cloudsdk.ies;
import java.util.Objects;
import com.google.gson.JsonElement;
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. Refer to the specification for more details. Language in
* 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 */
/** Defined in 802.11 table 9-92 */
public static final int TYPE = 195;
/** Local maximum transmit power for 20 MHz. Required field. */
public final Integer localMaxTxPwrConstraint20MHz;
/** Local maximum transmit power for 40 MHz. Optional field. */
public final Integer localMaxTxPwrConstraint40MHz;
/** Local maximum transmit power for 80 MHz. Optional field. */
public final Integer localMaxTxPwrConstraint80MHz;
/** Local maximum transmit power for both 160 MHz and 80+80 MHz. Optional field. */
public final Integer localMaxTxPwrConstraint160MHz;
/**
* 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(
int localMaxTxPwrConstraint20MHz,
int localMaxTxPwrConstraint40MHz,
int localMaxTxPwrConstraint80MHz,
int localMaxTxPwrConstraint160MHz
short localMaxTxPwrConstraint20MHz,
Short localMaxTxPwrConstraint40MHz,
Short localMaxTxPwrConstraint80MHz,
Short localMaxTxPwrConstraint160MHz
) {
this.localMaxTxPwrConstraint20MHz = localMaxTxPwrConstraint20MHz;
this.localMaxTxPwrConstraint40MHz = localMaxTxPwrConstraint40MHz;
@@ -48,16 +55,26 @@ public class TxPwrInfo {
/** Parse TxPwrInfo IE from appropriate Json object. */
public static TxPwrInfo parse(JsonObject contents) {
JsonObject innerObj = contents.get("Tx Pwr Info").getAsJsonObject();
// required field
int localMaxTxPwrConstraint20MHz =
contents.get("Local Max Tx Pwr Constraint 20MHz").getAsInt();
short localMaxTxPwrConstraint20MHz =
innerObj.get("Local Max Tx Pwr Constraint 20MHz").getAsShort();
// optional field
Integer localMaxTxPwrConstraint40MHz =
parseOptionalField(contents, "Local Max Tx Pwr Constraint 40MHz");
Integer localMaxTxPwrConstraint80MHz =
parseOptionalField(contents, "Local Max Tx Pwr Constraint 40MHz");
Integer localMaxTxPwrConstraint160MHz =
parseOptionalField(contents, "Local Max Tx Pwr Constraint 40MHz");
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,
@@ -66,17 +83,6 @@ public class TxPwrInfo {
);
}
private static Integer parseOptionalField(
JsonObject contents,
String fieldName
) {
JsonElement element = contents.get(fieldName);
if (element == null) {
return null;
}
return element.getAsInt();
}
@Override
public int hashCode() {
return Objects.hash(
@@ -107,13 +113,4 @@ public class TxPwrInfo {
other.localMaxTxPwrConstraint40MHz &&
localMaxTxPwrConstraint80MHz == other.localMaxTxPwrConstraint80MHz;
}
@Override
public String toString() {
return "TxPwrInfo [localMaxTxPwrConstraint20MHz=" +
localMaxTxPwrConstraint20MHz + ", localMaxTxPwrConstraint40MHz=" +
localMaxTxPwrConstraint40MHz + ", localMaxTxPwrConstraint80MHz=" +
localMaxTxPwrConstraint80MHz + ", localMaxTxPwrConstraint160MHz=" +
localMaxTxPwrConstraint160MHz + "]";
}
}

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
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 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.
@@ -173,4 +176,4 @@ public class VHTOperation {
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,11 +6,16 @@
* 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 static class Interface {
public static class Client {
@@ -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;
@@ -115,7 +122,7 @@ public class State {
public static class Radio {
public long active_ms;
public long busy_ms;
public int channel;
public int channel; // TODO might be int[] array??
public String channel_width;
public long noise;
public String phy;

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,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 java.util.Objects;
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
/**
* Represents a single entry in wifi scan results.
* ies[] array is not stored directly, but parsed into WifiScanEntry fields
* 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;

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,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 DeviceConfiguration {
public static class DeviceConfigurationElement {

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

@@ -6,15 +6,15 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral;
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 static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import org.junit.jupiter.api.Test;
public class UCentralUtilsTest {

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
package com.facebook.openwifi.cloudsdk.ies;
import static org.junit.jupiter.api.Assertions.assertEquals;

View File

@@ -6,7 +6,7 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.informationelement;
package com.facebook.openwifi.cloudsdk.ies;
import static org.junit.jupiter.api.Assertions.assertEquals;

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

@@ -6,9 +6,8 @@
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.openwifirrm.ucentral.gw.models;
package com.facebook.openwifi.librca;
public class WebTokenRefreshRequest {
public String userId;
public String refreshToken;
}
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