mirror of
https://github.com/Telecominfraproject/wlan-cloud-rrm.git
synced 2025-10-30 18:17:58 +00:00
Compare commits
6 Commits
main
...
token_refr
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6fb30ce7e4 | ||
|
|
350a45b616 | ||
|
|
52dae760d8 | ||
|
|
343fc7b6ee | ||
|
|
2a952f56a9 | ||
|
|
52a2258c2d |
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
DOCKER_REGISTRY_USERNAME: ucentral
|
||||
steps:
|
||||
- name: Checkout actions repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: Telecominfraproject/.github
|
||||
path: github
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
- docker
|
||||
steps:
|
||||
- name: Checkout actions repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: Telecominfraproject/.github
|
||||
path: github
|
||||
|
||||
2
.github/workflows/deploy-gh-pages.yml
vendored
2
.github/workflows/deploy-gh-pages.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
distribution: 'adopt'
|
||||
cache: maven
|
||||
- name: Build with Maven
|
||||
run: mvn javadoc:aggregate
|
||||
run: mvn javadoc:javadoc
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
|
||||
2
.github/workflows/enforce-jira-issue-key.yml
vendored
2
.github/workflows/enforce-jira-issue-key.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout actions repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: Telecominfraproject/.github
|
||||
path: github
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
HELM_REPO_USERNAME: ucentral
|
||||
steps:
|
||||
- name: Checkout uCentral assembly chart repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: wlan-cloud-rrm
|
||||
|
||||
|
||||
22
.gitignore
vendored
22
.gitignore
vendored
@@ -1,15 +1,12 @@
|
||||
/target/
|
||||
*/target/
|
||||
|
||||
# owrrm specific
|
||||
*.log*
|
||||
device_config.json
|
||||
settings.json
|
||||
topology.json
|
||||
/target
|
||||
/*.log*
|
||||
/device_config.json
|
||||
/settings.json
|
||||
/topology.json
|
||||
|
||||
# Eclipse
|
||||
.settings/
|
||||
bin/
|
||||
/.settings/
|
||||
/bin/
|
||||
.metadata
|
||||
.classpath
|
||||
.project
|
||||
@@ -20,8 +17,3 @@ bin/
|
||||
*.iml
|
||||
*.iws
|
||||
*.ipr
|
||||
|
||||
# Miscellaneous files thzt should not be checked in
|
||||
temp/
|
||||
|
||||
.DS_Store
|
||||
|
||||
@@ -94,30 +94,6 @@ levels of these APs will be determined by the following steps:
|
||||
Parameters:
|
||||
* `mode`: "measure_ap_ap"
|
||||
* `coverageThreshold`: Coverage threshold between APs in dBm
|
||||
* values: int < 30 (default: -70)
|
||||
* values: int < 30 (default: -70)
|
||||
* `nthSmallestRssi`: the nth smallest RSSI that is used for tx power calculation
|
||||
* values: int >= 0 (default: 0)
|
||||
|
||||
## Client Steering
|
||||
`ClientSteeringOptimizer` and its subclasses implement client steering
|
||||
algorithms via 802.11k/v/r mechanisms, with the goal of moving clients to
|
||||
optimal APs and/or bands.
|
||||
|
||||
**Client steering is a work in progress and NOT currently functional.**
|
||||
|
||||
### `SingleAPBandSteering`
|
||||
This algorithm performs same-AP RRSI-based steering only, using a simple
|
||||
decision and backoff procedure.
|
||||
|
||||
Parameters:
|
||||
* `mode`: "band"
|
||||
* `minRssi2G`: RSSI (dBm) below which a client on the 2G band should be kicked
|
||||
* values: int < 30 (default: -87)
|
||||
* `maxRssi2G`: RSSI (dBm) above which a client on the 2G band should roam to
|
||||
5G/6G
|
||||
* values: int < 30 (default: -67)
|
||||
* `minRssiNon2G`: RSSI (dBm) below which a client on the 5G/6G band should roam
|
||||
to 2G
|
||||
* values: int < 30 (default: -82)
|
||||
* `backoffTimeSec`: Backoff time (seconds) for all APs and radios
|
||||
* values: int >= 0 (default: 300)
|
||||
28
Dockerfile
28
Dockerfile
@@ -1,22 +1,20 @@
|
||||
FROM maven:3-eclipse-temurin-11 as build
|
||||
FROM maven:3-jdk-11 as build
|
||||
WORKDIR /usr/src/java
|
||||
COPY . .
|
||||
RUN mvn clean package -pl owrrm -am -DappendVersionString="$(./scripts/get_build_version.sh)"
|
||||
RUN mvn clean package -DappendVersionString="$(./scripts/get_build_version.sh)"
|
||||
|
||||
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
|
||||
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
|
||||
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/
|
||||
EXPOSE 16789 16790
|
||||
COPY --from=build /usr/src/java/target/openwifi-rrm.jar /usr/local/bin/
|
||||
EXPOSE 16789
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
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"]
|
||||
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"]
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
FROM maven:3-eclipse-temurin-11 as build
|
||||
WORKDIR /usr/src/java
|
||||
COPY . .
|
||||
RUN mvn clean package -pl owrrm -am -DappendVersionString="$(./scripts/get_build_version.sh)"
|
||||
|
||||
FROM eclipse-temurin:11-jre-jammy
|
||||
RUN apt-get update && apt-get install -y gettext-base
|
||||
ADD https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentral-deploy/main/docker-compose/certs/restapi-ca.pem \
|
||||
/usr/local/share/ca-certificates/restapi-ca-selfsigned.pem
|
||||
RUN mkdir /owrrm-data
|
||||
WORKDIR /usr/src/java
|
||||
COPY docker-entrypoint.sh /
|
||||
COPY runner.sh /
|
||||
COPY --from=build /usr/src/java/owrrm/target/openwifi-rrm.jar /usr/local/bin/
|
||||
|
||||
# generate Application CDS
|
||||
RUN java -Xshare:off -XX:DumpLoadedClassList=static-cds.lst -jar /usr/local/bin/openwifi-rrm.jar --help && \
|
||||
java -Xshare:dump -XX:SharedClassListFile=static-cds.lst -XX:SharedArchiveFile=static-cds.jsa -jar /usr/local/bin/openwifi-rrm.jar
|
||||
|
||||
EXPOSE 16789 16790
|
||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||
ENV JVM_IMPL=hotspot
|
||||
ENV EXTRA_JVM_FLAGS="-XX:SharedArchiveFile=static-cds.jsa -Xshare:auto"
|
||||
CMD ["/runner.sh", "java", "/usr/local/bin/openwifi-rrm.jar", \
|
||||
"run", \
|
||||
"--config-env", \
|
||||
"-t", "/owrrm-data/topology.json", \
|
||||
"-d", "/owrrm-data/device_config.json"]
|
||||
43
README.md
43
README.md
@@ -1,10 +1,12 @@
|
||||
# OpenWiFi RRM Service
|
||||
[See here](owrrm/README.md) for details.
|
||||
OpenWiFi uCentral-based radio resource management (RRM) service, providing a
|
||||
cloud-based Wi-Fi RRM layer for APs running the OpenWiFi SDK.
|
||||
|
||||
## Project Structure
|
||||
This is an [Apache Maven] project with the following modules:
|
||||
* `lib-cloudsdk` - OpenWiFi CloudSDK Java Library
|
||||
* `owrrm` - OpenWiFi RRM Service
|
||||
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).
|
||||
|
||||
## Requirements
|
||||
* **Running:** JRE 11.
|
||||
@@ -14,7 +16,7 @@ This is an [Apache Maven] project with the following modules:
|
||||
```
|
||||
$ mvn package [-DskipTests]
|
||||
```
|
||||
This will build a runnable JAR located at `owrrm/target/openwifi-rrm.jar`.
|
||||
This will build a runnable JAR located at `target/openwifi-rrm.jar`.
|
||||
|
||||
Alternatively, Docker builds can be launched using the provided
|
||||
[Dockerfile](Dockerfile).
|
||||
@@ -25,7 +27,34 @@ $ mvn test
|
||||
```
|
||||
Unit tests are written using [JUnit 5].
|
||||
|
||||
## Code Style
|
||||
## 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 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):
|
||||
|
||||
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
@@ -29,8 +29,8 @@ services:
|
||||
targetPort: 16789
|
||||
protocol: TCP
|
||||
restapiinternal:
|
||||
servicePort: 16790
|
||||
targetPort: 16790
|
||||
servicePort: 17007
|
||||
targetPort: 17007
|
||||
protocol: TCP
|
||||
|
||||
checks:
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# OpenWiFi CloudSDK Java Library
|
||||
A Java library providing clients and models for the OpenWiFi uCentral-based
|
||||
CloudSDK.
|
||||
@@ -1,80 +0,0 @@
|
||||
<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>
|
||||
@@ -1,253 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.cloudsdk.ies;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
// NOTE: Not validated (not seen on test devices)
|
||||
/**
|
||||
* This information element (IE) appears in wifiscan entries. It's called
|
||||
* "Collocated Interference Report" in 802.11 specs (section 9.4.2.84). Refer to
|
||||
* the specification for more details. Language in javadocs is taken from the
|
||||
* specification.
|
||||
*/
|
||||
public class CollocatedInterferenceReport {
|
||||
/** Defined in 802.11 table 9-92 */
|
||||
public static final int TYPE = 96;
|
||||
|
||||
public static class InterferenceAccuracyAndIndex {
|
||||
/**
|
||||
* Unsigned int (4 bits) representing expected accuracy of the estimate of
|
||||
* interference in dB with 95% confidence interval
|
||||
*/
|
||||
public final byte expectedAccuracy;
|
||||
/**
|
||||
* Unsigned int (4 bits) indicating the interference index that is unique for
|
||||
* each type of interference source
|
||||
*/
|
||||
public final byte interferenceIndex;
|
||||
|
||||
/** Constructor */
|
||||
public InterferenceAccuracyAndIndex(
|
||||
byte expectedAccuracy,
|
||||
byte interferenceIndex
|
||||
) {
|
||||
this.expectedAccuracy = expectedAccuracy;
|
||||
this.interferenceIndex = interferenceIndex;
|
||||
}
|
||||
|
||||
/** Parse InterferenceAccuracyAndIndex from JSON object */
|
||||
// TODO modify this method as necessary - since the IE doesn't seem to be
|
||||
// present, we have no idea what the format looks like
|
||||
public static InterferenceAccuracyAndIndex parse(JsonObject contents) {
|
||||
return new InterferenceAccuracyAndIndex(
|
||||
contents.get("Expected Accuracy").getAsByte(),
|
||||
contents.get("Interference Index").getAsByte()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(expectedAccuracy, interferenceIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
InterferenceAccuracyAndIndex other =
|
||||
(InterferenceAccuracyAndIndex) obj;
|
||||
return expectedAccuracy == other.expectedAccuracy &&
|
||||
interferenceIndex == other.interferenceIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/** Unsigned 8 bits representing when the report is generated */
|
||||
public final short reportPeriod;
|
||||
/**
|
||||
* signed 8 bits representing the maximum level of the collocated
|
||||
* interference power in units of dBm over all receive chains averaged over a
|
||||
* 4 microsecond period during an interference period and across interference
|
||||
* bandwidth
|
||||
*/
|
||||
public final byte interferenceLevel;
|
||||
/** Subfield for interference level accuracy and index - 8 bits */
|
||||
public final InterferenceAccuracyAndIndex interferenceAccuracyAndIndex;
|
||||
/**
|
||||
* Unsigned 32 bits representing the interval between two successibe periods
|
||||
* of interference in microseconds
|
||||
*/
|
||||
public final long interferenceInterval;
|
||||
/**
|
||||
* Unsigned 32 bits representing the duration of each period of interference in
|
||||
* microseconds
|
||||
*/
|
||||
public final long interferenceBurstLength;
|
||||
/**
|
||||
* Unsigned 32 bits contains the least significant 4 octets (i.e., B0–B31) of
|
||||
* the TSF timer at the start of the interference burst. When either the
|
||||
* Interference Interval or the Interference Burst Length fields are set to
|
||||
* 2^32 – 1, this field indicates the average duty cycle
|
||||
*/
|
||||
public final long interferenceStartTimeDutyCycle;
|
||||
/**
|
||||
* Unsigned 32 bits representing indicates the center frequency of interference
|
||||
* in units of 5 kHz
|
||||
*/
|
||||
public final long interferenceCenterFrequency;
|
||||
/**
|
||||
* Unsigned 16 bits representing the bandwidth in units of 5 kHz at the –3 dB
|
||||
* roll-off point of the interference signal
|
||||
*/
|
||||
public final short interferenceBandwidth;
|
||||
|
||||
/** Constructor */
|
||||
public CollocatedInterferenceReport(
|
||||
short reportPeriod,
|
||||
byte interferenceLevel,
|
||||
InterferenceAccuracyAndIndex interferenceAccuracyAndIndex,
|
||||
long interferenceInterval,
|
||||
long interferenceBurstLength,
|
||||
long interferenceStartTimeDutyCycle,
|
||||
long interferenceCenterFrequency,
|
||||
short interferenceBandwidth
|
||||
) {
|
||||
this.reportPeriod = reportPeriod;
|
||||
this.interferenceLevel = interferenceLevel;
|
||||
this.interferenceAccuracyAndIndex = interferenceAccuracyAndIndex;
|
||||
this.interferenceInterval = interferenceInterval;
|
||||
this.interferenceBurstLength = interferenceBurstLength;
|
||||
this.interferenceStartTimeDutyCycle = interferenceStartTimeDutyCycle;
|
||||
this.interferenceCenterFrequency = interferenceCenterFrequency;
|
||||
this.interferenceBandwidth = interferenceBandwidth;
|
||||
}
|
||||
|
||||
/** Parse CollocatedInterferenceReport from JSON object */
|
||||
// TODO rename fields as necessary - we don't know how the data format yet
|
||||
public static CollocatedInterferenceReport parse(JsonObject contents) {
|
||||
return new CollocatedInterferenceReport(
|
||||
contents.get("Report Period").getAsShort(),
|
||||
contents.get("Intereference Level").getAsByte(),
|
||||
InterferenceAccuracyAndIndex
|
||||
.parse(
|
||||
contents.get("Interference Level Accuracy/Inteference Index").getAsJsonObject()
|
||||
),
|
||||
contents.get("Interference Interval").getAsLong(),
|
||||
contents.get("Interference Burst Length").getAsLong(),
|
||||
contents.get("Interference Start Time/Duty Cycle").getAsLong(),
|
||||
contents.get("Interference Center Frequency").getAsLong(),
|
||||
contents.get("Interference Bandwidth").getAsShort()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
reportPeriod,
|
||||
interferenceLevel,
|
||||
interferenceAccuracyAndIndex,
|
||||
interferenceInterval,
|
||||
interferenceBurstLength,
|
||||
interferenceStartTimeDutyCycle,
|
||||
interferenceCenterFrequency,
|
||||
interferenceBandwidth
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CollocatedInterferenceReport other = (CollocatedInterferenceReport) obj;
|
||||
return reportPeriod == other.reportPeriod &&
|
||||
interferenceLevel == other.interferenceLevel &&
|
||||
interferenceAccuracyAndIndex
|
||||
.equals(other.interferenceAccuracyAndIndex) &&
|
||||
interferenceInterval == other.interferenceInterval &&
|
||||
interferenceBurstLength == other.interferenceBurstLength &&
|
||||
interferenceStartTimeDutyCycle ==
|
||||
other.interferenceStartTimeDutyCycle &&
|
||||
interferenceCenterFrequency == other.interferenceCenterFrequency &&
|
||||
interferenceBandwidth == other.interferenceBandwidth;
|
||||
}
|
||||
}
|
||||
@@ -1,318 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,313 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.cloudsdk.ies;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.IEUtils;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
// NOTE: Not validated (not seen on test devices)
|
||||
/**
|
||||
* This information element (IE) appears in wifiscan entries. It's called
|
||||
* "Reduced Neighbor Report" in 802.11 specs (section 9.4.2.170). Refer to the
|
||||
* specification for more details. Language in javadocs is taken from the
|
||||
* specification.
|
||||
*/
|
||||
public class ReducedNeighborReport {
|
||||
/** Defined in 802.11 table 9-92 */
|
||||
public static final int TYPE = 201;
|
||||
|
||||
/**
|
||||
* The Neighbor AP Information field specifies TBTT and other information
|
||||
* related to a group of neighbor APs on one channel.
|
||||
*/
|
||||
public static class NeighborApInformation {
|
||||
/**
|
||||
* Subfield for TBTT Information header
|
||||
*/
|
||||
public static class TbttInformationHeader {
|
||||
/**
|
||||
* Unsigned 2 bits - identifies, together with the TBTT Information Length
|
||||
* subfield, the format of the TBTT Information field
|
||||
*/
|
||||
public final byte tbttInformationType;
|
||||
/**
|
||||
* 1 bit - reserved except when the Reduced Neighbor Report element is
|
||||
* carried in a Probe Response frame transmitted by a TVHT AP
|
||||
*/
|
||||
public final boolean filteredNeighborAp;
|
||||
/**
|
||||
* Unsigned 4 bits - number of TBTT Information fields included in the TBTT
|
||||
* Information Set field of the Neighbor AP Information field, minus one
|
||||
*/
|
||||
public final byte tbttInformationCount;
|
||||
/**
|
||||
* Unsigned 8 bits - the length of each TBTT Information field included in
|
||||
* the TBTT Information Set field of the Neighbor AP Information field
|
||||
*/
|
||||
public final short tbttInformationLength;
|
||||
|
||||
/** Constructor */
|
||||
public TbttInformationHeader(
|
||||
byte tbttInformationType,
|
||||
boolean filteredNeighborAp,
|
||||
byte tbttInformationCount,
|
||||
short tbttInformationLength
|
||||
) {
|
||||
this.tbttInformationType = tbttInformationType;
|
||||
this.filteredNeighborAp = filteredNeighborAp;
|
||||
this.tbttInformationCount = tbttInformationCount;
|
||||
this.tbttInformationLength = tbttInformationLength;
|
||||
}
|
||||
|
||||
/** Parse TbttInformationHeader from JSON object */
|
||||
// TODO modify this method as necessary - since the IE doesn't seem to be
|
||||
// present, we have no idea what the format looks like
|
||||
public static TbttInformationHeader parse(JsonObject contents) {
|
||||
return new TbttInformationHeader(
|
||||
contents.get("TBTT Information Type").getAsByte(),
|
||||
contents.get("Filtered Neighbor Map").getAsBoolean(),
|
||||
contents.get("TBTT Information Count").getAsByte(),
|
||||
contents.get("TBTT Information Length").getAsShort()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
tbttInformationType,
|
||||
filteredNeighborAp,
|
||||
tbttInformationCount,
|
||||
tbttInformationLength
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TbttInformationHeader other = (TbttInformationHeader) obj;
|
||||
return tbttInformationType == other.tbttInformationType &&
|
||||
filteredNeighborAp == other.filteredNeighborAp &&
|
||||
tbttInformationCount == other.tbttInformationCount &&
|
||||
tbttInformationLength == other.tbttInformationLength;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subfield for TBTT Information
|
||||
*/
|
||||
public static class TbttInformation {
|
||||
/**
|
||||
* Unsigned 8 bits - offset in TUs, rounded down to nearest TU, to the next
|
||||
* TBTT of an AP’s BSS from the immediately prior TBTT of the AP that
|
||||
* transmits this element
|
||||
*/
|
||||
public final short neighborApTbttOffset;
|
||||
/** BSSID of neighbor, optional */
|
||||
public final String bssid;
|
||||
/** Short SSID of neighbor, optional */
|
||||
public final String shortSsid;
|
||||
|
||||
/** Constructor */
|
||||
public TbttInformation(
|
||||
short neighborApTbttOffset,
|
||||
String bssid,
|
||||
String shortSsid
|
||||
) {
|
||||
this.neighborApTbttOffset = neighborApTbttOffset;
|
||||
this.bssid = bssid;
|
||||
this.shortSsid = shortSsid;
|
||||
}
|
||||
|
||||
/** Parse TbttInformation from JSON object */
|
||||
// TODO modify this method as necessary - since the IE doesn't seem to be
|
||||
// present, we have no idea what the format looks like
|
||||
public static TbttInformation parse(JsonObject contents) {
|
||||
return new TbttInformation(
|
||||
contents.get("Neighbor AP TBTT Offset").getAsShort(),
|
||||
IEUtils.parseOptionalStringField(contents, "BSSID"),
|
||||
IEUtils.parseOptionalStringField(contents, "Short SSID")
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(neighborApTbttOffset, bssid, shortSsid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TbttInformation other = (TbttInformation) obj;
|
||||
return neighborApTbttOffset ==
|
||||
other.neighborApTbttOffset && bssid.equals(other.bssid) &&
|
||||
Objects.equals(shortSsid, other.shortSsid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TbttInformationHeader
|
||||
*/
|
||||
public final TbttInformationHeader tbttInformationHeader;
|
||||
/**
|
||||
* Unsigned 8 bits - channel starting frequency that, together with the
|
||||
* Channel Number field, indicates the primary channel of the BSSs of the APs
|
||||
* in this Neighbor AP Information field
|
||||
*/
|
||||
public final short operatingClass;
|
||||
/**
|
||||
* Unsigned 8 bits - the last known primary channel of the APs in this
|
||||
* Neighbor AP Information field.
|
||||
*/
|
||||
public final short channelNumber;
|
||||
/**
|
||||
* @see TbttInformation
|
||||
*/
|
||||
public final TbttInformation tbttInformation;
|
||||
|
||||
/** Constructor */
|
||||
public NeighborApInformation(
|
||||
TbttInformationHeader tbttInformationHeader,
|
||||
short operatingClass,
|
||||
short channelNumber,
|
||||
TbttInformation tbttInformation
|
||||
) {
|
||||
this.tbttInformationHeader = tbttInformationHeader;
|
||||
this.operatingClass = operatingClass;
|
||||
this.channelNumber = channelNumber;
|
||||
this.tbttInformation = tbttInformation;
|
||||
}
|
||||
|
||||
/** Parse NeighborApInformation from JSON object */
|
||||
// TODO modify this method as necessary - since the IE doesn't seem to be
|
||||
// present, we have no idea what the format looks like
|
||||
public static NeighborApInformation parse(JsonObject contents) {
|
||||
return new NeighborApInformation(
|
||||
TbttInformationHeader.parse(
|
||||
contents.get("TBTT Information Header").getAsJsonObject()
|
||||
),
|
||||
contents.get("Operating Class").getAsShort(),
|
||||
contents.get("Channel Number").getAsShort(),
|
||||
TbttInformation.parse(contents.get("TBTT Information").getAsJsonObject())
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
tbttInformationHeader,
|
||||
operatingClass,
|
||||
channelNumber,
|
||||
tbttInformation
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
NeighborApInformation other = (NeighborApInformation) obj;
|
||||
return tbttInformationHeader.equals(other.tbttInformationHeader) &&
|
||||
operatingClass == other.operatingClass &&
|
||||
channelNumber == other.channelNumber &&
|
||||
Objects.equals(tbttInformation, other.tbttInformation);
|
||||
}
|
||||
}
|
||||
|
||||
/** number of channels in a subband of supported channels */
|
||||
public final List<NeighborApInformation> neighborApInformations;
|
||||
|
||||
/** Constructor */
|
||||
public ReducedNeighborReport(
|
||||
List<NeighborApInformation> neighborApInformations
|
||||
) {
|
||||
this.neighborApInformations =
|
||||
Collections.unmodifiableList(neighborApInformations);
|
||||
}
|
||||
|
||||
/** Parse ReducedNeighborReport from JSON object */
|
||||
// TODO modify this method as necessary - since the IE doesn't seem to be
|
||||
// present, we have no idea what the format looks like
|
||||
public static ReducedNeighborReport parse(JsonObject contents) {
|
||||
List<NeighborApInformation> neighborApInformations = new ArrayList<>();
|
||||
|
||||
JsonElement neighborApInformationsObject =
|
||||
contents.get("Neighbor AP Informations");
|
||||
if (neighborApInformationsObject != null) {
|
||||
for (
|
||||
JsonElement elem : neighborApInformationsObject.getAsJsonArray()
|
||||
) {
|
||||
neighborApInformations
|
||||
.add(NeighborApInformation.parse(elem.getAsJsonObject()));
|
||||
}
|
||||
}
|
||||
|
||||
return new ReducedNeighborReport(neighborApInformations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(neighborApInformations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ReducedNeighborReport other = (ReducedNeighborReport) obj;
|
||||
return neighborApInformations.equals(other.neighborApInformations);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,6 +0,0 @@
|
||||
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
|
||||
@@ -1,3 +0,0 @@
|
||||
# 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.
|
||||
@@ -1,72 +0,0 @@
|
||||
<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>
|
||||
@@ -1,212 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
log4j.rootLogger=DEBUG, stdout
|
||||
#log4j.rootLogger=ERROR, stdout, file
|
||||
|
||||
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
|
||||
@@ -1,19 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class RootCauseAnalyzerTest {
|
||||
|
||||
@Test
|
||||
void placeholderTest() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -551,12 +551,6 @@ components:
|
||||
additionalProperties:
|
||||
type: integer
|
||||
format: int32
|
||||
apClientActionMap:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
Certificate:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1,39 +0,0 @@
|
||||
# OpenWiFi RRM Service
|
||||
OpenWiFi uCentral-based radio resource management (RRM) service, providing a
|
||||
cloud-based Wi-Fi RRM layer for APs running the OpenWiFi SDK.
|
||||
|
||||
This service collects data from OpenWiFi APs (e.g. Wi-Fi scans, stats,
|
||||
capabilities) via the uCentral Gateway and Kafka, and integrates with the
|
||||
OpenWiFi Provisioning service to perform optimization across configured
|
||||
"venues". It pushes new device configuration parameters to APs after RRM
|
||||
algorithms are run (manually or periodically).
|
||||
|
||||
See [IMPLEMENTATION.md](IMPLEMENTATION.md) for service architecture details and
|
||||
[ALGORITHMS.md](ALGORITHMS.md) for descriptions of the RRM algorithms.
|
||||
|
||||
## Usage
|
||||
```
|
||||
$ java -jar openwifi-rrm.jar [-h]
|
||||
```
|
||||
|
||||
To start the service, use the `run` command while providing configuration via
|
||||
either environment variables (`--config-env`) or a static JSON file
|
||||
(`--config-file`, default `settings.json`). The following data is *required*:
|
||||
* Service configuration
|
||||
* Env: `SERVICECONFIG_PRIVATEENDPOINT`, `SERVICECONFIG_PUBLICENDPOINT`
|
||||
* JSON: `serviceConfig` structure
|
||||
* Kafka broker URL
|
||||
* Env: `KAFKACONFIG_BOOTSTRAPSERVER`
|
||||
* JSON: `kafkaConfig` structure
|
||||
* MySQL database credentials
|
||||
* Env: `DATABASECONFIG_SERVER`, `DATABASECONFIG_USER`, `DATABASECONFIG_PASSWORD`
|
||||
* JSON: `databaseConfig` structure
|
||||
|
||||
## OpenAPI
|
||||
This service provides an OpenAPI HTTP interface on the port specified in the
|
||||
service configuration (`moduleConfig.apiServerParams`). An auto-generated
|
||||
OpenAPI 3.0 document is hosted at the endpoints `/openapi.{yaml,json}` and is
|
||||
written to [openapi.yaml](openapi.yaml) during the Maven "compile" phase.
|
||||
|
||||
|
||||
[JUnit 5]: https://junit.org/junit5/
|
||||
139
owrrm/pom.xml
139
owrrm/pom.xml
@@ -1,139 +0,0 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>openwifi-rrm</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<parent>
|
||||
<groupId>com.facebook</groupId>
|
||||
<artifactId>openwifi-base</artifactId>
|
||||
<version>2.7.0</version>
|
||||
</parent>
|
||||
<properties>
|
||||
<!-- Hack for static files located in root project -->
|
||||
<myproject.root>${project.basedir}/..</myproject.root>
|
||||
<mainClassName>com.facebook.openwifi.rrm.Launcher</mainClassName>
|
||||
<appendVersionString></appendVersionString>
|
||||
</properties>
|
||||
<build>
|
||||
<finalName>openwifi-rrm</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources-filtered</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Main-Class>${mainClassName}</Main-Class>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.facebook</groupId>
|
||||
<artifactId>openwifi-cloudsdk</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.konghq</groupId>
|
||||
<artifactId>unirest-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sparkjava</groupId>
|
||||
<artifactId>spark-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -1,160 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.server.Connector;
|
||||
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
|
||||
import org.eclipse.jetty.server.HttpConfiguration;
|
||||
import org.eclipse.jetty.server.HttpConnectionFactory;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.server.ServerConnector;
|
||||
import org.eclipse.jetty.util.thread.QueuedThreadPool;
|
||||
import org.eclipse.jetty.util.thread.ThreadPool;
|
||||
|
||||
import spark.embeddedserver.jetty.JettyServerFactory;
|
||||
import spark.utils.Assert;
|
||||
|
||||
/**
|
||||
* Creates Jetty Server instances. Majority of the logic is taken from
|
||||
* JettyServerFactory. The additional feature is that this class will actually
|
||||
* set two connectors (original class doesn't set any connectors at all and
|
||||
* leaves it up to the serivce start logic). Since we set two connectors here on
|
||||
* the server, Spark uses the existing conectors instead of trying to spin up
|
||||
* its own connectors. The other difference is that it uses a different
|
||||
* ServerConnector constructor to avoid allocating additional threads that
|
||||
* aren't necessary ({@link #makeConnector})
|
||||
*
|
||||
* @see spark.embeddedserver.jetty.EmbeddedJettyFactory
|
||||
*/
|
||||
public class CustomJettyServerFactory implements JettyServerFactory {
|
||||
// normally this is set in EmbeddedJettyServer but since we create our own connectors here,
|
||||
// we need the value here
|
||||
private boolean trustForwardHeaders = true; // true by default
|
||||
private final int internalPort;
|
||||
private final int externalPort;
|
||||
|
||||
public CustomJettyServerFactory(int internalPort, int externalPort) {
|
||||
this.internalPort = internalPort;
|
||||
this.externalPort = externalPort;
|
||||
}
|
||||
|
||||
public void setTrustForwardHeaders(boolean trustForwardHeaders) {
|
||||
this.trustForwardHeaders = trustForwardHeaders;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is basically
|
||||
* spark.embeddedserver.jetty.SocketConnectorFactory.createSocketConnector,
|
||||
* the only difference being that we use a different constructor for the
|
||||
* Connector and that the private methods called are just inlined.
|
||||
*/
|
||||
public Connector makeConnector(
|
||||
Server server,
|
||||
String host,
|
||||
int port,
|
||||
boolean trustForwardHeaders
|
||||
) {
|
||||
Assert.notNull(server, "'server' must not be null");
|
||||
Assert.notNull(host, "'host' must not be null");
|
||||
|
||||
// spark.embeddedserver.jetty.SocketConnectorFactory.createHttpConnectionFactory
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
httpConfig.setSecureScheme("https");
|
||||
if (trustForwardHeaders) {
|
||||
httpConfig.addCustomizer(new ForwardedRequestCustomizer());
|
||||
}
|
||||
HttpConnectionFactory httpConnectionFactory =
|
||||
new HttpConnectionFactory(httpConfig);
|
||||
|
||||
ServerConnector connector = new ServerConnector(
|
||||
server,
|
||||
0, // acceptors, don't allocate separate threads for acceptor
|
||||
0, // selectors, use default number
|
||||
httpConnectionFactory
|
||||
);
|
||||
// spark.embeddedserver.jetty.SocketConnectorFactory.initializeConnector
|
||||
connector.setIdleTimeout(TimeUnit.HOURS.toMillis(1));
|
||||
connector.setHost(host);
|
||||
connector.setPort(port);
|
||||
|
||||
return connector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Jetty server.
|
||||
*
|
||||
* @param maxThreads maxThreads
|
||||
* @param minThreads minThreads
|
||||
* @param threadTimeoutMillis threadTimeoutMillis
|
||||
* @return a new jetty server instance
|
||||
*/
|
||||
@Override
|
||||
public Server create(
|
||||
int maxThreads,
|
||||
int minThreads,
|
||||
int threadTimeoutMillis
|
||||
) {
|
||||
Server server;
|
||||
|
||||
if (maxThreads > 0) {
|
||||
int max = maxThreads;
|
||||
int min = (minThreads > 0) ? minThreads : 8;
|
||||
int idleTimeout =
|
||||
(threadTimeoutMillis > 0) ? threadTimeoutMillis : 60000;
|
||||
|
||||
server = new Server(new QueuedThreadPool(max, min, idleTimeout));
|
||||
} else {
|
||||
server = new Server();
|
||||
}
|
||||
|
||||
Connector internalConnector = null;
|
||||
if (internalPort != -1) {
|
||||
internalConnector = makeConnector(
|
||||
server,
|
||||
"0.0.0.0",
|
||||
internalPort,
|
||||
trustForwardHeaders
|
||||
);
|
||||
}
|
||||
|
||||
Connector externalConnector = null;
|
||||
if (externalPort != -1) {
|
||||
externalConnector = makeConnector(
|
||||
server,
|
||||
"0.0.0.0",
|
||||
externalPort,
|
||||
trustForwardHeaders
|
||||
);
|
||||
}
|
||||
|
||||
if (internalConnector == null) {
|
||||
server.setConnectors(new Connector[] { externalConnector });
|
||||
} else if (externalConnector == null) {
|
||||
server.setConnectors(new Connector[] { internalConnector });
|
||||
} else {
|
||||
server.setConnectors(
|
||||
new Connector[] { internalConnector, externalConnector }
|
||||
);
|
||||
}
|
||||
|
||||
return server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Jetty server with supplied thread pool
|
||||
* @param threadPool thread pool
|
||||
* @return a new jetty server instance
|
||||
*/
|
||||
@Override
|
||||
public Server create(ThreadPool threadPool) {
|
||||
return threadPool != null ? new Server(threadPool) : new Server();
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* RRM service modules.
|
||||
*/
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Channel assignment algorithms.
|
||||
*/
|
||||
package com.facebook.openwifi.rrm.optimizers.channel;
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.clientsteering;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
|
||||
/** Client steering base class */
|
||||
public abstract class ClientSteeringOptimizer {
|
||||
// TODO call upon triggers, not only via one-off or period runs
|
||||
|
||||
/** Represents client steering actions an AP can take */
|
||||
public static enum CLIENT_STEERING_ACTIONS {
|
||||
/** Steer from 2G to 5G/6G */
|
||||
STEER_UP,
|
||||
/** Steer from 5G/6G to 2G */
|
||||
STEER_DOWN,
|
||||
/** Deauthenticate client */
|
||||
DEAUTHENTICATE
|
||||
}
|
||||
|
||||
/**
|
||||
* 802.11 BTM reason codes (ex. for deauth).
|
||||
*
|
||||
* See IEEE Std 802.11-2016, 9.4.1.7, Table 9-45.
|
||||
*/
|
||||
public static class BTMReasonCode {
|
||||
private BTMReasonCode() {}
|
||||
|
||||
public static final int UNSPECIFIED = 1;
|
||||
public static final int PREV_AUTH_NOT_VALID = 2;
|
||||
public static final int DEAUTH_LEAVING = 3;
|
||||
public static final int DISASSOC_DUE_TO_INACTIVITY = 4;
|
||||
public static final int DISASSOC_AP_BUSY = 5;
|
||||
public static final int CLASS2_FRAME_FROM_NONAUTH_STA = 6;
|
||||
public static final int CLASS3_FRAME_FROM_NONASSOC_STA = 7;
|
||||
public static final int DISASSOC_STA_HAS_LEFT = 8;
|
||||
public static final int STA_REQ_ASSOC_WITHOUT_AUTH = 9;
|
||||
public static final int PWR_CAPABILITY_NOT_VALID = 10;
|
||||
public static final int SUPPORTED_CHANNEL_NOT_VALID = 11;
|
||||
public static final int INVALID_IE = 13;
|
||||
public static final int MICHAEL_MIC_FAILURE = 14;
|
||||
public static final int FOURWAY_HANDSHAKE_TIMEOUT = 15;
|
||||
public static final int GROUP_KEY_UPDATE_TIMEOUT = 16;
|
||||
public static final int IE_IN_4WAY_DIFFERS = 17;
|
||||
public static final int GROUP_CIPHER_NOT_VALID = 18;
|
||||
public static final int PAIRWISE_CIPHER_NOT_VALID = 19;
|
||||
public static final int AKMP_NOT_VALID = 20;
|
||||
public static final int UNSUPPORTED_RSN_IE_VERSION = 21;
|
||||
public static final int INVALID_RSN_IE_CAPAB = 22;
|
||||
public static final int IEEE_802_1X_AUTH_FAILED = 23;
|
||||
public static final int CIPHER_SUITE_REJECTED = 24;
|
||||
public static final int TDLS_TEARDOWN_UNREACHABLE = 25;
|
||||
public static final int TDLS_TEARDOWN_UNSPECIFIED = 26;
|
||||
public static final int DISASSOC_LOW_ACK = 34;
|
||||
public static final int MESH_PEERING_CANCELLED = 52;
|
||||
public static final int MESH_MAX_PEERS = 53;
|
||||
public static final int MESH_CONFIG_POLICY_VIOLATION = 54;
|
||||
public static final int MESH_CLOSE_RCVD = 55;
|
||||
public static final int MESH_MAX_RETRIES = 56;
|
||||
public static final int MESH_CONFIRM_TIMEOUT = 57;
|
||||
public static final int MESH_INVALID_GTK = 58;
|
||||
public static final int MESH_INCONSISTENT_PARAMS = 59;
|
||||
public static final int MESH_INVALID_SECURITY_CAP = 60;
|
||||
public static final int MESH_PATH_ERROR_NO_PROXY_INFO = 61;
|
||||
public static final int MESH_PATH_ERROR_NO_FORWARDING_INFO = 62;
|
||||
public static final int MESH_PATH_ERROR_DEST_UNREACHABLE = 63;
|
||||
public static final int MAC_ADDRESS_ALREADY_EXISTS_IN_MBSS = 64;
|
||||
public static final int MESH_CHANNEL_SWITCH_REGULATORY_REQ = 65;
|
||||
public static final int MESH_CHANNEL_SWITCH_UNSPECIFIED = 66;
|
||||
}
|
||||
|
||||
/** The input data model. */
|
||||
protected final DataModel model;
|
||||
/** The RF zone. */
|
||||
protected final String zone;
|
||||
/** The device configs within {@link #zone}, keyed on serial number. */
|
||||
protected final Map<String, DeviceConfig> deviceConfigs;
|
||||
/** Client steering state */
|
||||
protected final ClientSteeringState clientSteeringState;
|
||||
|
||||
/** Constructor */
|
||||
public ClientSteeringOptimizer(
|
||||
DataModel model,
|
||||
String zone,
|
||||
DeviceDataManager deviceDataManager,
|
||||
ClientSteeringState clientSteeringState
|
||||
) {
|
||||
this.model = model;
|
||||
this.zone = zone;
|
||||
this.deviceConfigs = deviceDataManager.getAllDeviceConfigs(zone);
|
||||
|
||||
this.clientSteeringState = clientSteeringState;
|
||||
|
||||
// Remove model entries not in the given zone
|
||||
this.model.latestWifiScans.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
this.model.latestStates.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
this.model.latestDeviceStatusRadios.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
this.model.latestDeviceCapabilitiesPhy.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute map from AP serial number to client MAC to client steering
|
||||
* action.
|
||||
*/
|
||||
public abstract Map<String, Map<String, String>> computeApClientActionMap(
|
||||
boolean dryRun
|
||||
);
|
||||
|
||||
/**
|
||||
* Steer clients (steer up, steer down, and deauthenticate).
|
||||
*
|
||||
* @param apClientActionMap the map from AP serial number to client MAC to
|
||||
* action to take
|
||||
*/
|
||||
public void steer(
|
||||
Map<String, Map<String, String>> apClientActionMap
|
||||
) {
|
||||
// FIXME implement this
|
||||
//
|
||||
// TODO: input must also contain AP interface for each client (needed in hostapd commands below)
|
||||
//
|
||||
// NOTE: 802.11k/v features must first be enabled on APs:
|
||||
// ubus call hostapd.<iface> bss_mgmt_enable \
|
||||
// '{"neighbor_report": true, "beacon_report": true, "link_measurements": true, "bss_transition": true}'
|
||||
//
|
||||
// Actions:
|
||||
//
|
||||
// - Kick/Deauth:
|
||||
// ubus call hostapd.<iface> del_client \
|
||||
// '{"addr": "<client_mac>", "reason": 5, "deauth": true}'
|
||||
// Where "reason" is a code in BTMReasonCode
|
||||
//
|
||||
// - Steer:
|
||||
// ubus call hostapd.<iface> bss_transition_request \
|
||||
// '{"addr": "<client_mac>", "disassociation_imminent": false, "disassociation_timer": 0, "validity_period": 30, "neighbors": ["<hex>"], "abridged": 1}'
|
||||
// Where "neighbors" list element = a hex identifier (array index 2 in command below) - MUST fetch per interface per AP
|
||||
// ubus call hostapd.<iface> rrm_nr_get_own
|
||||
// TODO: also send Multi Band Operation (MBO) code ("mbo_reason") for 802.11ax clients
|
||||
}
|
||||
|
||||
// TODO Issue 802.11k RRM Beacon Measurement Requests periodically
|
||||
// 1. Enable 802.11k/v features on the AP ("bss_mgmt_enable" hostapd command)
|
||||
// 2. Send request to client
|
||||
// ubus call hostapd.wlan0-1 rrm_beacon_req '{"addr": "<client_mac>", "channel": <number>, "mode": 1, "op_class": 128, "duration": 100}'
|
||||
// 3. Must be subscribed to hostapd 'beacon-report' event on AP to receive reply ("BEACON-RESP-RX")
|
||||
// ubus subscribe hostapd.<iface>
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.clientsteering;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
/** Class to manage global client steering state */
|
||||
public class ClientSteeringState {
|
||||
/**
|
||||
* Map from AP serial number to client MAC to time (JVM monotonic time in
|
||||
* ns) of the latest attempted client steering action. The {@code Long}
|
||||
* values are never null.
|
||||
*/
|
||||
private ConcurrentMap<String, Map<String, Long>> apClientLastAttempt =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Register a client steering attempt for the given AP and station at the
|
||||
* given time if there is no previous registered attempt or more than the
|
||||
* given backoff time has passed since the registration time of the last
|
||||
* attempt and the current time. Note that only registration times are
|
||||
* checked and/or entered, and nothing is executed here. Return true if the
|
||||
* attempt was registered; false otherwise. The attempt is not registered if
|
||||
* this run is specified as a dry run.
|
||||
* <p>
|
||||
* The backoff time must be non-negative. The backoff time window is
|
||||
* "exclusive" - e.g., if the backoff time is X ns, and the current time is
|
||||
* exactly X ns after the last attempt, the backoff is considered expired.
|
||||
* <p>
|
||||
* Note that if there was a previous attempt for the given AP and station,
|
||||
* the current time must not be before the last attempt.
|
||||
*
|
||||
* @param apSerialNumber AP serial number
|
||||
* @param station client MAC
|
||||
* @param currentTimeNs JVM monotonic time in ns
|
||||
* @param backoffTimeNs non-negative backoff time (ns)
|
||||
* @param dryRun if set, do not apply changes
|
||||
* @return true if client steering attempt was registered; false otherwise
|
||||
*/
|
||||
public boolean registerIfBackoffExpired(
|
||||
String apSerialNumber,
|
||||
String station,
|
||||
long currentTimeNs,
|
||||
long backoffTimeNs,
|
||||
boolean dryRun
|
||||
) {
|
||||
if (backoffTimeNs < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"Backoff time must be non-negative."
|
||||
);
|
||||
}
|
||||
// get last attempt
|
||||
Map<String, Long> clientLastAttempt = apClientLastAttempt
|
||||
.computeIfAbsent(apSerialNumber, k -> new HashMap<>());
|
||||
synchronized (clientLastAttempt) {
|
||||
Long lastAttempt = clientLastAttempt.get(station);
|
||||
// check if backoff expired
|
||||
if (
|
||||
lastAttempt != null &&
|
||||
currentTimeNs - lastAttempt < backoffTimeNs
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// register attempt
|
||||
if (!dryRun) {
|
||||
clientLastAttempt.put(station, currentTimeNs);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.clientsteering;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.StateInfo;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralConstants;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||
import com.google.gson.Gson;
|
||||
|
||||
/**
|
||||
* Implements simple band steering for each AP separately
|
||||
* <p>
|
||||
* 2G clients below a specified RSSI threshold are deauthenticated. 2G clients
|
||||
* above a specified RSSI threshold are asked to move to either 5G or 6G. 5G and
|
||||
* 6G clients below a configurable RSSI threshold are asked to move to 2G.
|
||||
*/
|
||||
public class SingleAPBandSteering extends ClientSteeringOptimizer {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(SingleAPBandSteering.class);
|
||||
|
||||
/** The RRM algorithm ID. */
|
||||
public static final String ALGORITHM_ID = "band";
|
||||
|
||||
/** The Gson instance. */
|
||||
private static final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* RSSI (dBm) below which a client on 2G should be disconnected using
|
||||
* deauthentication.
|
||||
*/
|
||||
public static final short DEFAULT_MIN_RSSI_2G = -87;
|
||||
/**
|
||||
* RSSI (dBm) above which a client on 2G should be requested to move to
|
||||
* 5G/6G
|
||||
*/
|
||||
public static final short DEFAULT_MAX_RSSI_2G = -67;
|
||||
/**
|
||||
* RSSI (dBm) below which a client on 5G/6G should be requested to move to
|
||||
* 2G
|
||||
*/
|
||||
public static final short DEFAULT_MIN_RSSI_NON_2G = -82;
|
||||
/** Default backoff time (ns) for all APs and radios */
|
||||
public static final long DEFAULT_BACKOFF_TIME_NS = 300_000_000_000L; // 5 min
|
||||
|
||||
/** RSSI below which 2G clients are deauthenticated */
|
||||
private final short minRssi2G;
|
||||
/** RSSI above which 2G clients are asked to move to 5G or 6G */
|
||||
private final short maxRssi2G;
|
||||
/** RSSI below which 5G and 6G clients are asked to move to 2G */
|
||||
private final short minRssiNon2G;
|
||||
/** Backoff time (ns) for all APs and radios */
|
||||
private final long backoffTimeNs;
|
||||
|
||||
/** Make a SingleAPBandSteering object with the given arguments */
|
||||
public static SingleAPBandSteering makeWithArgs(
|
||||
DataModel model,
|
||||
String zone,
|
||||
DeviceDataManager deviceDataManager,
|
||||
ClientSteeringState clientSteeringState,
|
||||
Map<String, String> args
|
||||
) {
|
||||
short minRssi2G = DEFAULT_MIN_RSSI_2G;
|
||||
short maxRssi2G = DEFAULT_MAX_RSSI_2G;
|
||||
short minRssiNon2G = DEFAULT_MIN_RSSI_NON_2G;
|
||||
long backoffTimeNs = DEFAULT_BACKOFF_TIME_NS;
|
||||
|
||||
String arg;
|
||||
if ((arg = args.get("minRssi2G")) != null) {
|
||||
minRssi2G = Short.parseShort(arg);
|
||||
}
|
||||
if ((arg = args.get("maxRssi2G")) != null) {
|
||||
maxRssi2G = Short.parseShort(arg);
|
||||
}
|
||||
if ((arg = args.get("minRssiNon2G")) != null) {
|
||||
minRssiNon2G = Short.parseShort(arg);
|
||||
}
|
||||
if ((arg = args.get("backoffTimeSec")) != null) {
|
||||
backoffTimeNs = Long.parseLong(arg) * 1_000_000_000L;
|
||||
}
|
||||
|
||||
return new SingleAPBandSteering(
|
||||
model,
|
||||
zone,
|
||||
deviceDataManager,
|
||||
clientSteeringState,
|
||||
minRssi2G,
|
||||
maxRssi2G,
|
||||
minRssiNon2G,
|
||||
backoffTimeNs
|
||||
);
|
||||
}
|
||||
|
||||
/** Constructor */
|
||||
public SingleAPBandSteering(
|
||||
DataModel model,
|
||||
String zone,
|
||||
DeviceDataManager deviceDataManager,
|
||||
ClientSteeringState clientSteeringState,
|
||||
short minRssi2G,
|
||||
short maxRssi2G,
|
||||
short minRssiNon2G,
|
||||
long backoffTimeNs
|
||||
) {
|
||||
super(model, zone, deviceDataManager, clientSteeringState);
|
||||
this.minRssi2G = minRssi2G;
|
||||
this.maxRssi2G = maxRssi2G;
|
||||
this.minRssiNon2G = minRssiNon2G;
|
||||
this.backoffTimeNs = backoffTimeNs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Map<String, String>> computeApClientActionMap(
|
||||
boolean dryRun
|
||||
) {
|
||||
Map<String, Map<String, String>> apClientActionMap = new HashMap<>();
|
||||
// iterate through every AP
|
||||
for (
|
||||
Map.Entry<String, List<StateInfo>> entry : model.latestStates
|
||||
.entrySet()
|
||||
) {
|
||||
// get the latest state
|
||||
// TODO window size (look at multiple states)
|
||||
// TODO window percent (% of samples that must violate thresholds)
|
||||
// TODO also check wifiscan IEs to see if 11k beacon requests are supported/enabled
|
||||
// (RMEnabledCapabilities.beaconActiveMeasurementCapabilityEnabled)
|
||||
List<? extends State> states = entry.getValue();
|
||||
if (states == null || states.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
final String serialNumber = entry.getKey();
|
||||
final State state = states.get(states.size() - 1);
|
||||
// iterate through every radio and every connected client
|
||||
if (state.interfaces == null || state.interfaces.length == 0) {
|
||||
continue;
|
||||
}
|
||||
final long currentTimeNs = System.nanoTime();
|
||||
for (State.Interface iface : state.interfaces) {
|
||||
if (iface.ssids == null || iface.ssids.length == 0) {
|
||||
continue;
|
||||
}
|
||||
for (State.Interface.SSID ssid : iface.ssids) {
|
||||
if (
|
||||
ssid.associations == null ||
|
||||
ssid.associations.length == 0
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
final State.Radio radio = gson.fromJson(
|
||||
ssid.radio,
|
||||
State.Radio.class
|
||||
);
|
||||
// get band for this radio/ssid
|
||||
Map<String, Capabilities.Phy> capabilitiesPhy =
|
||||
model.latestDeviceCapabilitiesPhy
|
||||
.get(serialNumber);
|
||||
if (capabilitiesPhy == null) {
|
||||
continue;
|
||||
}
|
||||
final String band = ModelerUtils.getBand(
|
||||
radio,
|
||||
capabilitiesPhy
|
||||
);
|
||||
if (band == null) {
|
||||
continue;
|
||||
}
|
||||
// decide steering action (if any) for each client
|
||||
for (
|
||||
State.Interface.SSID.Association assoc : ssid.associations
|
||||
) {
|
||||
maybeAddApClientActionEntry(
|
||||
assoc,
|
||||
band,
|
||||
serialNumber,
|
||||
currentTimeNs,
|
||||
apClientActionMap,
|
||||
dryRun
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return apClientActionMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a client steering action is desired, add an appropriate entry to the
|
||||
* apClientActionMap, unless this run is marked as a dry run.
|
||||
*
|
||||
* @param assoc association between AP radio and client
|
||||
* @param band band (e.g., "2G")
|
||||
* @param serialNumber AP serial number
|
||||
* @param currentTimeNs JVM monotonic time in ns
|
||||
* @param dryRun if set, do not apply changes
|
||||
* @param apClientActionMap map from AP serial number to client MAC to client
|
||||
* steering action name ({@link ClientSteeringOptimizer.CLIENT_STEERING_ACTIONS})
|
||||
*/
|
||||
private void maybeAddApClientActionEntry(
|
||||
State.Interface.SSID.Association assoc,
|
||||
String band,
|
||||
String serialNumber,
|
||||
long currentTimeNs,
|
||||
Map<String, Map<String, String>> apClientActionMap,
|
||||
boolean dryRun
|
||||
) {
|
||||
// decide whether to do any band steering
|
||||
// TODO check which bands AP & client can use (see 11k)
|
||||
if (UCentralConstants.BAND_2G.equals(band)) {
|
||||
if (assoc.rssi < minRssi2G) {
|
||||
if (
|
||||
clientSteeringState
|
||||
.registerIfBackoffExpired(
|
||||
serialNumber,
|
||||
assoc.station,
|
||||
currentTimeNs,
|
||||
backoffTimeNs,
|
||||
dryRun
|
||||
)
|
||||
) {
|
||||
logger.debug(
|
||||
"Planning to deauthenticate client {} on AP {}",
|
||||
assoc.station,
|
||||
serialNumber
|
||||
);
|
||||
apClientActionMap
|
||||
.computeIfAbsent(
|
||||
serialNumber,
|
||||
k -> new HashMap<>()
|
||||
)
|
||||
.put(
|
||||
assoc.station,
|
||||
CLIENT_STEERING_ACTIONS.DEAUTHENTICATE
|
||||
.name()
|
||||
);
|
||||
}
|
||||
} else if (assoc.rssi > maxRssi2G) {
|
||||
if (
|
||||
clientSteeringState
|
||||
.registerIfBackoffExpired(
|
||||
serialNumber,
|
||||
assoc.station,
|
||||
currentTimeNs,
|
||||
backoffTimeNs,
|
||||
dryRun
|
||||
)
|
||||
) {
|
||||
logger.debug(
|
||||
"Planning to request client {} on AP {} to move to 5G or 6G",
|
||||
assoc.station,
|
||||
serialNumber
|
||||
);
|
||||
apClientActionMap
|
||||
.computeIfAbsent(
|
||||
serialNumber,
|
||||
k -> new HashMap<>()
|
||||
)
|
||||
.put(
|
||||
assoc.station,
|
||||
CLIENT_STEERING_ACTIONS.STEER_UP
|
||||
.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
// otherwise, do nothing
|
||||
} else {
|
||||
// treat 5G and 6G clients the same way
|
||||
if (assoc.rssi < minRssiNon2G) {
|
||||
if (
|
||||
clientSteeringState
|
||||
.registerIfBackoffExpired(
|
||||
serialNumber,
|
||||
assoc.station,
|
||||
currentTimeNs,
|
||||
backoffTimeNs,
|
||||
dryRun
|
||||
)
|
||||
) {
|
||||
logger.debug(
|
||||
"Planning to request client {} on AP {} to move to 2G",
|
||||
assoc.station,
|
||||
serialNumber
|
||||
);
|
||||
apClientActionMap
|
||||
.computeIfAbsent(
|
||||
serialNumber,
|
||||
k -> new HashMap<>()
|
||||
)
|
||||
.put(
|
||||
assoc.station,
|
||||
CLIENT_STEERING_ACTIONS.STEER_DOWN
|
||||
.name()
|
||||
);
|
||||
}
|
||||
}
|
||||
// otherwise, do nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Client steering algorithms.
|
||||
*/
|
||||
package com.facebook.openwifi.rrm.optimizers.clientsteering;
|
||||
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* RRM algorithm implementations.
|
||||
*
|
||||
* @see com.facebook.openwifi.rrm.RRMAlgorithm
|
||||
*/
|
||||
package com.facebook.openwifi.rrm.optimizers;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transmit power control (TPC) algorithms.
|
||||
*/
|
||||
package com.facebook.openwifi.rrm.optimizers.tpc;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Radio Resource Management (RRM) service.
|
||||
*/
|
||||
package com.facebook.openwifi.rrm;
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.rca;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Root cause analysis service configuration model.
|
||||
*/
|
||||
public class RCAConfig {
|
||||
//
|
||||
// NOTE:
|
||||
// Currently assumes RCA is embedded in the RRM service and does NOT
|
||||
// duplicate SDK-related fields.
|
||||
//
|
||||
|
||||
/**
|
||||
* StationPinger parameters.
|
||||
*/
|
||||
public class StationPingerParams {
|
||||
/**
|
||||
* How often to ping each station, in seconds (or 0 to disable)
|
||||
* ({@code STATIONPINGERPARAMS_PINGINTERVALSEC})
|
||||
*/
|
||||
// NOTE: cannot be shorter than Kafka "state" publish interval
|
||||
public int pingIntervalSec = 0 /* TODO enable by default */;
|
||||
|
||||
/**
|
||||
* The number of pings to send to each station
|
||||
* ({@code STATIONPINGERPARAMS_PINGCOUNT})
|
||||
*/
|
||||
public int pingCount = 5;
|
||||
|
||||
/**
|
||||
* Ignore state records older than this interval (in ms)
|
||||
* ({@code STATIONPINGERPARAMS_STALESTATETHRESHOLDMS})
|
||||
*/
|
||||
// NOTE: should not be longer than Kafka "state" publish interval
|
||||
public int staleStateThresholdMs = 300000; // 5 min
|
||||
|
||||
/**
|
||||
* Number of executor threads for ping tasks
|
||||
* ({@code STATIONPINGERPARAMS_EXECUTORTHREADCOUNT})
|
||||
*/
|
||||
public int executorThreadCount = 3;
|
||||
}
|
||||
|
||||
/** StationPinger parameters. */
|
||||
public StationPingerParams stationPingerParams = new StationPingerParams();
|
||||
|
||||
/** Construct RCAConfig from environment variables. */
|
||||
public static RCAConfig fromEnv(Map<String, String> env) {
|
||||
RCAConfig config = new RCAConfig();
|
||||
String v;
|
||||
|
||||
// @formatter:off
|
||||
|
||||
/* StationPingerParams */
|
||||
StationPingerParams stationPingerParams = config.stationPingerParams;
|
||||
if ((v = env.get("STATIONPINGERPARAMS_PINGINTERVALSEC")) != null) {
|
||||
stationPingerParams.pingIntervalSec = Integer.parseInt(v);
|
||||
}
|
||||
if ((v = env.get("STATIONPINGERPARAMS_PINGCOUNT")) != null) {
|
||||
stationPingerParams.pingCount = Integer.parseInt(v);
|
||||
}
|
||||
if ((v = env.get("STATIONPINGERPARAMS_STALESTATETHRESHOLDMS")) != null) {
|
||||
stationPingerParams.staleStateThresholdMs = Integer.parseInt(v);
|
||||
}
|
||||
if ((v = env.get("STATIONPINGERPARAMS_EXECUTORTHREADCOUNT")) != null) {
|
||||
stationPingerParams.executorThreadCount = Integer.parseInt(v);
|
||||
}
|
||||
|
||||
// @formatter:on
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.rca;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
|
||||
|
||||
/**
|
||||
* Utilities for root cause analysis.
|
||||
*/
|
||||
public class RCAUtils {
|
||||
/** Ping result, only containing a data summary (not individual pings). */
|
||||
public static class PingResult {
|
||||
// NOTE: fields are taken directly from ping output
|
||||
/** Minimum ping RTT (ms) */
|
||||
public double min;
|
||||
/** Average ping RTT (ms) */
|
||||
public double avg;
|
||||
/** Maximum ping RTT (ms) */
|
||||
public double max;
|
||||
/** Standard deviation of ping RTT measurements (ms) */
|
||||
public double mdev;
|
||||
// TODO other stats? (ex. tx/rx packets, % packet loss)
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%.3f/%.3f/%.3f/%.3f ms", min, avg, max, mdev);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse raw ping output, returning null upon error.
|
||||
*
|
||||
* This only supports the busybox ping format.
|
||||
*/
|
||||
private static PingResult parsePingOutput(String output) {
|
||||
// Only parse summary line (should be last line in output).
|
||||
// Code below is optimized for minimal string operations.
|
||||
//
|
||||
// Examples of supported formats:
|
||||
// round-trip min/avg/max = 4.126/42.470/84.081 ms
|
||||
// rtt min/avg/max/mdev = 16.853/20.114/23.375/3.261 ms
|
||||
final String SUMMARY_TEXT_3 = "min/avg/max";
|
||||
int idx = output.lastIndexOf(SUMMARY_TEXT_3);
|
||||
if (idx != -1) {
|
||||
idx += SUMMARY_TEXT_3.length();
|
||||
} else {
|
||||
final String SUMMARY_TEXT_4 = "min/avg/max/mdev";
|
||||
idx = output.lastIndexOf(SUMMARY_TEXT_4);
|
||||
if (idx != -1) {
|
||||
idx += SUMMARY_TEXT_4.length();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
PingResult result = null;
|
||||
for (; idx < output.length(); idx++) {
|
||||
if (Character.isDigit(output.charAt(idx))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (idx < output.length()) {
|
||||
int endIdx = output.indexOf(' ', idx);
|
||||
if (endIdx != -1) {
|
||||
String s = output.substring(idx, endIdx);
|
||||
String[] tokens = s.split("/");
|
||||
if (tokens.length == 3) {
|
||||
result = new PingResult();
|
||||
result.min = Double.parseDouble(tokens[0]);
|
||||
result.avg = Double.parseDouble(tokens[1]);
|
||||
result.max = Double.parseDouble(tokens[2]);
|
||||
} else if (tokens.length == 4) {
|
||||
result = new PingResult();
|
||||
result.min = Double.parseDouble(tokens[0]);
|
||||
result.avg = Double.parseDouble(tokens[1]);
|
||||
result.max = Double.parseDouble(tokens[2]);
|
||||
result.mdev = Double.parseDouble(tokens[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instruct a device (AP) to ping a given destination (IP/hostname),
|
||||
* returning the raw ping output or null upon error.
|
||||
*
|
||||
* @param client the UCentralClient instance
|
||||
* @param serialNumber the device (AP) serial number
|
||||
* @param host the ping destination
|
||||
* @param pingCount the number of pings to send
|
||||
* @return the ping output, or null upon error
|
||||
*/
|
||||
public static PingResult pingFromDevice(
|
||||
UCentralClient client,
|
||||
String serialNumber,
|
||||
String host,
|
||||
int pingCount
|
||||
) {
|
||||
if (pingCount < 1) {
|
||||
throw new IllegalArgumentException("Invalid pingCount < 1");
|
||||
}
|
||||
String script = String.format("ping -c %d %s", pingCount, host);
|
||||
int timeoutSec = pingCount /* time buffer as follows: */ * 2 + 10;
|
||||
CommandInfo info = client.runScript(serialNumber, script, timeoutSec);
|
||||
String output = UCentralUtils.getScriptOutput(info);
|
||||
return output != null ? parsePingOutput(output) : null;
|
||||
}
|
||||
}
|
||||
@@ -1,269 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.rca.modules;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||
import com.facebook.openwifi.rrm.Utils;
|
||||
import com.facebook.openwifi.rrm.rca.RCAConfig.StationPingerParams;
|
||||
import com.facebook.openwifi.rrm.rca.RCAUtils;
|
||||
import com.facebook.openwifi.rrm.rca.RCAUtils.PingResult;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Ping service to measure latency/jitter between Wi-Fi APs and clients
|
||||
* (stations).
|
||||
* <p>
|
||||
* This class subscribes to the Kafka "state" topic to retrieve the list of APs
|
||||
* with connected clients, then issues ping commands for each (AP, STA) pair at
|
||||
* a given frequency. All actions are submitted to an executor during Kafka
|
||||
* callbacks.
|
||||
*/
|
||||
public class StationPinger {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(StationPinger.class);
|
||||
|
||||
/** The module parameters. */
|
||||
private final StationPingerParams params;
|
||||
|
||||
/** The uCentral client. */
|
||||
private final UCentralClient uCentralClient;
|
||||
|
||||
/** The executor service instance. */
|
||||
private final ExecutorService executor;
|
||||
|
||||
/** The Gson instance. */
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/**
|
||||
* Map from device (serial number) to the latest map of STAs
|
||||
* (i.e. client MAC address to Client structure).
|
||||
*/
|
||||
private Map<String, Map<String, State.Interface.Client>> deviceToClients =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Map of last ping timestamps, keyed on
|
||||
* {@link #getDeviceKey(String, String)}.
|
||||
*/
|
||||
private Map<String, Long> lastPingTsMap = new ConcurrentHashMap<>();
|
||||
|
||||
/** Constructor. */
|
||||
public StationPinger(
|
||||
StationPingerParams params,
|
||||
UCentralClient uCentralClient,
|
||||
UCentralKafkaConsumer consumer
|
||||
) {
|
||||
this.params = params;
|
||||
this.uCentralClient = uCentralClient;
|
||||
this.executor =
|
||||
Executors.newFixedThreadPool(
|
||||
params.executorThreadCount,
|
||||
new Utils.NamedThreadFactory(
|
||||
"RCA_" + this.getClass().getSimpleName()
|
||||
)
|
||||
);
|
||||
|
||||
if (params.pingIntervalSec < 1) {
|
||||
logger.info("StationPinger is disabled");
|
||||
return; // quit before registering listeners
|
||||
}
|
||||
|
||||
// Register Kafka listener
|
||||
if (consumer != null) {
|
||||
consumer.addKafkaListener(
|
||||
getClass().getSimpleName(),
|
||||
new UCentralKafkaConsumer.KafkaListener() {
|
||||
@Override
|
||||
public void handleStateRecords(List<KafkaRecord> records) {
|
||||
handleKafkaStateRecords(records);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleWifiScanRecords(
|
||||
List<KafkaRecord> records
|
||||
) { /* ignored */ }
|
||||
|
||||
@Override
|
||||
public void handleServiceEventRecords(
|
||||
List<ServiceEvent> serviceEventRecords
|
||||
) { /* ignored */ }
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Process the list of received State records. */
|
||||
private void handleKafkaStateRecords(List<KafkaRecord> records) {
|
||||
long now = System.currentTimeMillis();
|
||||
for (KafkaRecord record : records) {
|
||||
// Drop old records
|
||||
if (now - record.timestampMs > params.staleStateThresholdMs) {
|
||||
logger.debug(
|
||||
"Dropping old state record for {} at time {}",
|
||||
record.serialNumber,
|
||||
record.timestampMs
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Deserialize State
|
||||
JsonObject state = record.payload.getAsJsonObject("state");
|
||||
if (state == null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
State stateModel = gson.fromJson(state, State.class);
|
||||
Map<String, State.Interface.Client> clientMap =
|
||||
UCentralUtils.getWifiClientInfo(stateModel);
|
||||
if (
|
||||
deviceToClients.put(record.serialNumber, clientMap) == null
|
||||
) {
|
||||
// Enqueue this device
|
||||
final String serialNumber = record.serialNumber;
|
||||
executor.submit(() -> pingDevices(serialNumber));
|
||||
}
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error(
|
||||
String.format(
|
||||
"Device %s: failed to deserialize state: %s",
|
||||
record.serialNumber,
|
||||
state
|
||||
),
|
||||
e
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Shut down all resources. */
|
||||
public void shutdown() {
|
||||
executor.shutdownNow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Issue ping commands to all clients of a given AP.
|
||||
*
|
||||
* Note that this is intentionally NOT parallelized to avoid collisions
|
||||
* while transmitting to/from multiple clients of the same AP.
|
||||
*/
|
||||
private void pingDevices(String serialNumber) {
|
||||
Map<String, State.Interface.Client> clientMap =
|
||||
deviceToClients.get(serialNumber);
|
||||
if (clientMap == null) {
|
||||
return; // shouldn't happen
|
||||
}
|
||||
|
||||
logger.trace(
|
||||
"{}: Pinging all clients ({} total)...",
|
||||
serialNumber,
|
||||
clientMap.size()
|
||||
);
|
||||
final long PING_INTERVAL_NS =
|
||||
Math.max(params.pingIntervalSec, 1) * 1_000_000_000L;
|
||||
for (
|
||||
Map.Entry<String, State.Interface.Client> entry : clientMap
|
||||
.entrySet()
|
||||
) {
|
||||
String mac = entry.getKey();
|
||||
String host = getClientAddress(entry.getValue());
|
||||
if (host == null) {
|
||||
logger.debug(
|
||||
"{}: client {} has no pingable address",
|
||||
serialNumber,
|
||||
mac
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check backoff timer
|
||||
long now = System.nanoTime();
|
||||
String deviceKey = getDeviceKey(serialNumber, mac);
|
||||
Long lastPingTs = lastPingTsMap.putIfAbsent(deviceKey, now);
|
||||
if (lastPingTs != null && now - lastPingTs < PING_INTERVAL_NS) {
|
||||
logger.trace(
|
||||
"{}: Skipping ping for {} (last pinged {}s ago)",
|
||||
serialNumber,
|
||||
mac,
|
||||
(now - lastPingTs) / 1_000_000_000L
|
||||
);
|
||||
continue;
|
||||
}
|
||||
lastPingTsMap.put(deviceKey, now);
|
||||
|
||||
// Issue ping command
|
||||
logger.debug(
|
||||
"{}: Pinging client {} ({})",
|
||||
serialNumber,
|
||||
mac,
|
||||
host
|
||||
);
|
||||
PingResult result = RCAUtils
|
||||
.pingFromDevice(
|
||||
uCentralClient,
|
||||
serialNumber,
|
||||
host,
|
||||
params.pingCount
|
||||
);
|
||||
if (result == null) {
|
||||
logger.debug(
|
||||
"Ping failed from {} to {} ({})",
|
||||
serialNumber,
|
||||
mac,
|
||||
host
|
||||
);
|
||||
continue;
|
||||
}
|
||||
// TODO handle results
|
||||
logger.info(
|
||||
"Ping result from {} to {} ({}): {}",
|
||||
serialNumber,
|
||||
mac,
|
||||
host,
|
||||
result.toString()
|
||||
);
|
||||
}
|
||||
|
||||
// Remove map entries after we process them
|
||||
deviceToClients.remove(serialNumber);
|
||||
}
|
||||
|
||||
/** Return an address to ping for the given client. */
|
||||
private String getClientAddress(State.Interface.Client client) {
|
||||
if (client.ipv4_addresses.length > 0) {
|
||||
return client.ipv4_addresses[0];
|
||||
} else if (client.ipv6_addresses.length > 0) {
|
||||
return client.ipv6_addresses[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Return a key to use in {@link #lastPingTsMap}. */
|
||||
private String getDeviceKey(String serialNumber, String sta) {
|
||||
// Use (AP, STA) pair as the key to handle STAs moving between APs
|
||||
// TODO - do we care about radio/band/channel changes too?
|
||||
return serialNumber + '\0' + sta;
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Root Cause Analysis (RCA) service.
|
||||
*/
|
||||
package com.facebook.openwifi.rrm.rca;
|
||||
@@ -1,113 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.clientsteering;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
public class ClientSteeringStateTest {
|
||||
|
||||
@Test
|
||||
void testRegisterAndCheckBackoff() {
|
||||
final String apA = "aaaaaaaaaaaa";
|
||||
final String clientA1 = "1a:aa:aa:aa:aa:aa";
|
||||
final String clientA2 = "2a:aa:aa:aa:aa:aa";
|
||||
final String apB = "bbbbbbbbbbbb";
|
||||
final String clientB = "1b:bb:bb:bb:bb:bb";
|
||||
|
||||
final long currentTimeNs = System.nanoTime();
|
||||
final long bufferTimeNs = 60_000_000_000L; // 1 min
|
||||
|
||||
ClientSteeringState clientSteeringState = new ClientSteeringState();
|
||||
|
||||
// first attempt should register
|
||||
assertTrue(
|
||||
clientSteeringState.registerIfBackoffExpired(
|
||||
apA,
|
||||
clientA1,
|
||||
currentTimeNs,
|
||||
bufferTimeNs,
|
||||
false // dryRun
|
||||
)
|
||||
);
|
||||
// should not register AP A & clientA1 again while backoff is in effect
|
||||
assertFalse(
|
||||
clientSteeringState.registerIfBackoffExpired(
|
||||
apA,
|
||||
clientA1,
|
||||
currentTimeNs + 1,
|
||||
bufferTimeNs,
|
||||
false // dryRun
|
||||
)
|
||||
);
|
||||
// one client's backoff should not affect another client
|
||||
assertTrue(
|
||||
clientSteeringState.registerIfBackoffExpired(
|
||||
apA,
|
||||
clientA2,
|
||||
currentTimeNs + 1,
|
||||
bufferTimeNs,
|
||||
false // dryRun
|
||||
)
|
||||
);
|
||||
// one AP should not affect another
|
||||
assertTrue(
|
||||
clientSteeringState.registerIfBackoffExpired(
|
||||
apB,
|
||||
clientB,
|
||||
currentTimeNs + 1,
|
||||
bufferTimeNs,
|
||||
false // dryRun
|
||||
)
|
||||
);
|
||||
// should re-register AP A & clientA1 after backoff has expired
|
||||
assertTrue(
|
||||
clientSteeringState.registerIfBackoffExpired(
|
||||
apA,
|
||||
clientA1,
|
||||
currentTimeNs + bufferTimeNs,
|
||||
bufferTimeNs,
|
||||
false // dryRun
|
||||
)
|
||||
);
|
||||
// an older timestamp should not register
|
||||
assertFalse(
|
||||
clientSteeringState.registerIfBackoffExpired(
|
||||
apB,
|
||||
clientB,
|
||||
currentTimeNs - 1,
|
||||
bufferTimeNs,
|
||||
false // dryRun
|
||||
)
|
||||
);
|
||||
// try a different backoffTimeNs
|
||||
assertTrue(
|
||||
clientSteeringState
|
||||
.registerIfBackoffExpired(
|
||||
apA,
|
||||
clientA2,
|
||||
currentTimeNs + 2,
|
||||
1,
|
||||
false /* dryRun */
|
||||
)
|
||||
);
|
||||
// same client on a different AP should have separate timer
|
||||
assertTrue(
|
||||
clientSteeringState.registerIfBackoffExpired(
|
||||
apB,
|
||||
clientA2,
|
||||
currentTimeNs + 3,
|
||||
bufferTimeNs,
|
||||
false // dryRun
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.clientsteering;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralConstants;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler;
|
||||
import com.facebook.openwifi.rrm.optimizers.TestUtils;
|
||||
import com.facebook.openwifi.rrm.optimizers.clientsteering.ClientSteeringOptimizer.CLIENT_STEERING_ACTIONS;
|
||||
|
||||
public class SingleAPBandSteeringTest {
|
||||
// TODO test 6G also (should be treated same as 5G)
|
||||
|
||||
/** Test zone name. */
|
||||
private static final String TEST_ZONE = "test-zone";
|
||||
|
||||
// AP serial numbers
|
||||
private static final String apA = "aaaaaaaaaaaa";
|
||||
private static final String apB = "bbbbbbbbbbbb";
|
||||
private static final String apC = "cccccccccccc";
|
||||
|
||||
// arrays are mutable, but these are private and this is just a test class
|
||||
// arrays are more convenient for constructing states
|
||||
|
||||
/** bssids for radios on AP A */
|
||||
private static final String[] bssidsA =
|
||||
new String[] { "aa:aa:aa:aa:aa:a1", "aa:aa:aa:aa:aa:a2" };
|
||||
/** bssids for radios on AP B */
|
||||
private static final String[] bssidsB =
|
||||
new String[] { "bb:bb:bb:bb:bb:b1", "bb:bb:bb:bb:bb:b2" };
|
||||
/** bssids for radios on AP C */
|
||||
private static final String[] bssidsC =
|
||||
new String[] { "cc:cc:cc:cc:cc:c1", "cc:cc:cc:cc:cc:c2" };
|
||||
|
||||
/** Array: each element is an array of client MACS for a radio on AP A */
|
||||
private static final String[][] clientsA =
|
||||
new String[][] { new String[] { "1a:aa:aa:aa:aa:aa" },
|
||||
new String[] { "2a:aa:aa:aa:aa:aa" } };
|
||||
/** Array: each element is an array of client MACS for a radio on AP B */
|
||||
private static final String[][] clientsB =
|
||||
new String[][] { new String[] { "1b:bb:bb:bb:bb:bb" },
|
||||
new String[] { "2b:bb:bb:bb:bb:bb" } };
|
||||
/** Array: each element is an array of client MACS for a radio on AP B */
|
||||
private static final String[][] clientsC =
|
||||
new String[][] { new String[] { "1c:cc:cc:cc:cc:cc" },
|
||||
new String[] { "2c:cc:cc:cc:cc:cc" } };
|
||||
|
||||
/** Default channel width */
|
||||
private static final int DEFAULT_CHANNEL_WIDTH = 20;
|
||||
|
||||
/** Default tx power */
|
||||
private static final int DEFAULT_TX_POWER = 20;
|
||||
|
||||
/** Adds matching State and DeviceCapabilityPhy objects to the data model */
|
||||
private void addStateAndCapability(
|
||||
Modeler.DataModel dataModel,
|
||||
String apSerialNumber,
|
||||
String[] bssids,
|
||||
String[][] clients,
|
||||
int[][] clientRssis
|
||||
) {
|
||||
dataModel.latestStates.put(
|
||||
apSerialNumber,
|
||||
Arrays.asList(
|
||||
TestUtils.createState(
|
||||
new int[] { 1, 36 },
|
||||
new int[] { DEFAULT_CHANNEL_WIDTH, DEFAULT_CHANNEL_WIDTH },
|
||||
new int[] { DEFAULT_TX_POWER, DEFAULT_TX_POWER },
|
||||
bssids,
|
||||
clients,
|
||||
clientRssis,
|
||||
TestUtils.DEFAULT_LOCAL_TIME
|
||||
)
|
||||
)
|
||||
);
|
||||
dataModel.latestDeviceCapabilitiesPhy.put(
|
||||
apSerialNumber,
|
||||
TestUtils.createDeviceCapabilityPhy(
|
||||
new String[] {
|
||||
UCentralConstants.BAND_2G,
|
||||
UCentralConstants.BAND_5G }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a data model such that:
|
||||
* Client on (AP A, radio 2G) -> should be deauthenticated
|
||||
* Client on (AP A, radio 5G) -> should be steered down
|
||||
* Client on (AP B, radio 2G) -> no action
|
||||
* Client on (AP B, radio 5G) -> no action
|
||||
* Client on (AP C, radio 2G) -> should be steered up
|
||||
* Client on (AP C, radio 5G) -> no action
|
||||
*
|
||||
* @return the data model
|
||||
*/
|
||||
private Modeler.DataModel createModel() {
|
||||
Modeler.DataModel dataModel = new Modeler.DataModel();
|
||||
// AP A
|
||||
int[] clientRssis2G =
|
||||
new int[] { SingleAPBandSteering.DEFAULT_MIN_RSSI_2G - 1 }; // deauthenticate
|
||||
int[] clientRssis5G =
|
||||
new int[] { SingleAPBandSteering.DEFAULT_MIN_RSSI_NON_2G - 1 }; // steer down
|
||||
int[][] clientRssis = new int[][] { clientRssis2G, clientRssis5G };
|
||||
addStateAndCapability(
|
||||
dataModel,
|
||||
apA,
|
||||
bssidsA,
|
||||
clientsA,
|
||||
clientRssis
|
||||
);
|
||||
|
||||
// AP B
|
||||
clientRssis2G = new int[] { SingleAPBandSteering.DEFAULT_MIN_RSSI_2G }; // do nothing
|
||||
clientRssis5G =
|
||||
new int[] { SingleAPBandSteering.DEFAULT_MIN_RSSI_NON_2G }; // do nothing
|
||||
clientRssis = new int[][] { clientRssis2G, clientRssis5G };
|
||||
addStateAndCapability(
|
||||
dataModel,
|
||||
apB,
|
||||
bssidsB,
|
||||
clientsB,
|
||||
clientRssis
|
||||
);
|
||||
|
||||
// AP C
|
||||
clientRssis2G =
|
||||
new int[] { SingleAPBandSteering.DEFAULT_MAX_RSSI_2G + 1 }; // steer up
|
||||
clientRssis5G =
|
||||
new int[] { SingleAPBandSteering.DEFAULT_MIN_RSSI_NON_2G }; // do nothing
|
||||
clientRssis = new int[][] { clientRssis2G, clientRssis5G };
|
||||
addStateAndCapability(
|
||||
dataModel,
|
||||
apC,
|
||||
bssidsC,
|
||||
clientsC,
|
||||
clientRssis
|
||||
);
|
||||
|
||||
return dataModel;
|
||||
}
|
||||
|
||||
@Test
|
||||
void testComputeApClientActionMap() {
|
||||
DeviceDataManager deviceDataManager = new DeviceDataManager();
|
||||
deviceDataManager
|
||||
.setTopology(TestUtils.createTopology(TEST_ZONE, apA, apB, apC));
|
||||
Modeler.DataModel dataModel = createModel();
|
||||
// create expected results
|
||||
// see javadoc of createModel for more details
|
||||
Map<String, Map<String, String>> exp = new HashMap<>();
|
||||
Map<String, String> apAMap = new HashMap<>();
|
||||
apAMap
|
||||
.put(clientsA[0][0], CLIENT_STEERING_ACTIONS.DEAUTHENTICATE.name());
|
||||
apAMap.put(clientsA[1][0], CLIENT_STEERING_ACTIONS.STEER_DOWN.name());
|
||||
exp.put(apA, apAMap);
|
||||
// no action for AP B
|
||||
Map<String, String> apCMap = new HashMap<>();
|
||||
apCMap.put(clientsC[0][0], CLIENT_STEERING_ACTIONS.STEER_UP.name());
|
||||
exp.put(apC, apCMap);
|
||||
SingleAPBandSteering optimizer = SingleAPBandSteering.makeWithArgs(
|
||||
dataModel,
|
||||
TEST_ZONE,
|
||||
deviceDataManager,
|
||||
new ClientSteeringState(),
|
||||
new HashMap<>(0) // args (use default)
|
||||
);
|
||||
Map<String, Map<String, String>> apClientActionMap =
|
||||
optimizer.computeApClientActionMap(false /* dryRun */);
|
||||
assertEquals(exp, apClientActionMap);
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.services;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.HashMap;
|
||||
import java.time.Instant;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.UserInfo;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.WebTokenResult;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import spark.Service;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
|
||||
/**
|
||||
* This is a mock OW Security service meant to be used in tests.
|
||||
*
|
||||
* @see <a href="https://github.com/Telecominfraproject/wlan-cloud-ucentralsec">owsec</a>
|
||||
*/
|
||||
public class MockOWSecService {
|
||||
private class TokenInfo {
|
||||
long expiry;
|
||||
long created;
|
||||
}
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
/** A mapping of valid tokens to their expiry time in seconds since epoch */
|
||||
private Map<String, TokenInfo> validTokens;
|
||||
|
||||
/** The Spark service */
|
||||
private Service service;
|
||||
|
||||
public MockOWSecService(int port) {
|
||||
validTokens = new HashMap<>();
|
||||
service = Service.ignite();
|
||||
service.port(port);
|
||||
|
||||
service.get("/api/v1/validateToken", new ValidateTokenEndpoint());
|
||||
service.get("/api/v1/oauth2", new ValidateTokenEndpoint());
|
||||
service.get("/api/v1/systemEndpoints", new SystemEndpoint());
|
||||
|
||||
service.awaitInitialization();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
service.stop();
|
||||
service.awaitStop();
|
||||
}
|
||||
|
||||
public void addToken(String token, long expiresInSec) {
|
||||
TokenInfo time = new TokenInfo();
|
||||
time.created = Instant.now().getEpochSecond();
|
||||
time.expiry = expiresInSec;
|
||||
|
||||
validTokens.put(token, time);
|
||||
}
|
||||
|
||||
public void removeToken(String token) {
|
||||
validTokens.remove(token);
|
||||
}
|
||||
|
||||
public int getPort() { return service.port(); }
|
||||
|
||||
public class Oauth2Endpoint implements Route {
|
||||
@Override
|
||||
public String handle(Request request, Response response) {
|
||||
response.status(501);
|
||||
return "Not Implemented";
|
||||
}
|
||||
}
|
||||
|
||||
public class SystemEndpoint implements Route {
|
||||
@Override
|
||||
public String handle(Request request, Response response) {
|
||||
response.status(501);
|
||||
return "Not Implemented";
|
||||
}
|
||||
}
|
||||
|
||||
public class ValidateTokenEndpoint implements Route {
|
||||
@Override
|
||||
public String handle(Request request, Response response) {
|
||||
String token = request.queryParams("token");
|
||||
if (token == null) {
|
||||
response.status(403);
|
||||
return "Forbidden";
|
||||
}
|
||||
|
||||
TokenInfo info = validTokens.get(token);
|
||||
if (info == null) {
|
||||
response.status(403);
|
||||
return "Forbidden";
|
||||
}
|
||||
|
||||
if (info.created + info.expiry < Instant.now().getEpochSecond()) {
|
||||
response.status(403);
|
||||
return "Forbidden";
|
||||
}
|
||||
|
||||
TokenValidationResult result = new TokenValidationResult();
|
||||
result.userInfo = new UserInfo();
|
||||
result.tokenInfo = new WebTokenResult();
|
||||
result.tokenInfo.access_token = token;
|
||||
result.tokenInfo.created = info.created;
|
||||
result.tokenInfo.expires_in = info.expiry;
|
||||
|
||||
return gson.toJson(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
413
pom.xml
413
pom.xml
@@ -2,217 +2,230 @@
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>com.facebook</groupId>
|
||||
<artifactId>openwifi-base</artifactId>
|
||||
<artifactId>openwifi-rrm</artifactId>
|
||||
<version>2.7.0</version>
|
||||
<packaging>pom</packaging>
|
||||
<modules>
|
||||
<module>lib-cloudsdk</module>
|
||||
<module>lib-rca</module>
|
||||
<module>owrrm</module>
|
||||
</modules>
|
||||
<properties>
|
||||
<!-- Hack for static files located in root project -->
|
||||
<myproject.root>${project.basedir}</myproject.root>
|
||||
<java.version>11</java.version>
|
||||
<slf4j.version>1.7.32</slf4j.version>
|
||||
<junit.version>5.7.2</junit.version>
|
||||
<swagger.version>2.1.10</swagger.version>
|
||||
<mainClassName>com.facebook.openwifirrm.Launcher</mainClassName>
|
||||
<appendVersionString></appendVersionString>
|
||||
<!-- do not abort builds on autoformatter errors -->
|
||||
<spotless.check.skip>true</spotless.check.skip>
|
||||
</properties>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>**/*.md</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>log4j:log4j</artifact>
|
||||
<excludes>
|
||||
<!-- CVE-2021-4104 -->
|
||||
<exclude>org/apache/log4j/net/JMSAppender.class</exclude>
|
||||
<!-- CVE-2019-17571 -->
|
||||
<exclude>org/apache/log4j/net/SocketServer.class</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<configuration>
|
||||
<doclint>all,-missing</doclint>
|
||||
<notimestamp>true</notimestamp>
|
||||
<overview>${basedir}/owrrm/src/main/javadoc/overview.html</overview>
|
||||
<bottom>Copyright © Meta Platforms, Inc. and affiliates.</bottom>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!-- Generate "openapi.yaml" in project root, NOT packaged -->
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-maven-plugin</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
<configuration>
|
||||
<outputFileName>openapi</outputFileName>
|
||||
<outputPath>${project.basedir}</outputPath>
|
||||
<outputFormat>YAML</outputFormat>
|
||||
<prettyPrint>TRUE</prettyPrint>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>resolve</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>2.25.0</version>
|
||||
<configuration>
|
||||
<java>
|
||||
<eclipse>
|
||||
<file>${myproject.root}/spotless/eclipse-java-formatter.xml</file>
|
||||
<version>4.12.0</version>
|
||||
</eclipse>
|
||||
<trimTrailingWhitespace />
|
||||
<removeUnusedImports/>
|
||||
<licenseHeader>
|
||||
<file>${myproject.root}/spotless/license-header.txt</file>
|
||||
</licenseHeader>
|
||||
</java>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</pluginManagement>
|
||||
</build>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>4.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20210307</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.konghq</groupId>
|
||||
<artifactId>unirest-java</artifactId>
|
||||
<version>3.11.09</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.26</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>4.0.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sparkjava</groupId>
|
||||
<artifactId>spark-core</artifactId>
|
||||
<version>2.9.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<finalName>openwifi-rrm</finalName>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources-filtered</directory>
|
||||
<filtering>true</filtering>
|
||||
<includes>
|
||||
<include>**/*</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.0</version>
|
||||
<configuration>
|
||||
<source>${java.version}</source>
|
||||
<target>${java.version}</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>2.22.2</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<configuration>
|
||||
<filters>
|
||||
<filter>
|
||||
<artifact>*:*</artifact>
|
||||
<excludes>
|
||||
<exclude>**/*.md</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
<filter>
|
||||
<artifact>log4j:log4j</artifact>
|
||||
<excludes>
|
||||
<!-- CVE-2021-4104 -->
|
||||
<exclude>org/apache/log4j/net/JMSAppender.class</exclude>
|
||||
<!-- CVE-2019-17571 -->
|
||||
<exclude>org/apache/log4j/net/SocketServer.class</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
<transformers>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Main-Class>${mainClassName}</Main-Class>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
</transformers>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<configuration>
|
||||
<doclint>all,-missing</doclint>
|
||||
<notimestamp>true</notimestamp>
|
||||
<bottom>Copyright © Meta Platforms, Inc. and affiliates.</bottom>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!-- Generate "openapi.yaml" in project root, NOT packaged -->
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
<artifactId>swagger-maven-plugin</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<version>2.1.1</version>
|
||||
<configuration>
|
||||
<outputFileName>openapi</outputFileName>
|
||||
<outputPath>${project.basedir}</outputPath>
|
||||
<outputFormat>YAML</outputFormat>
|
||||
<prettyPrint>TRUE</prettyPrint>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>compile</phase>
|
||||
<goals>
|
||||
<goal>resolve</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>com.diffplug.spotless</groupId>
|
||||
<artifactId>spotless-maven-plugin</artifactId>
|
||||
<version>2.25.0</version>
|
||||
<configuration>
|
||||
<java>
|
||||
<eclipse>
|
||||
<file>${project.basedir}/spotless/eclipse-java-formatter.xml</file>
|
||||
<version>4.12.0</version>
|
||||
</eclipse>
|
||||
<trimTrailingWhitespace />
|
||||
<removeUnusedImports/>
|
||||
<licenseHeader>
|
||||
<file>${project.basedir}/spotless/license-header.txt</file>
|
||||
</licenseHeader>
|
||||
</java>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
<version>${slf4j.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>4.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.json</groupId>
|
||||
<artifactId>json</artifactId>
|
||||
<version>20210307</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.8.8</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.konghq</groupId>
|
||||
<artifactId>unirest-java</artifactId>
|
||||
<version>3.11.09</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.kafka</groupId>
|
||||
<artifactId>kafka-clients</artifactId>
|
||||
<version>2.8.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>8.0.26</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>4.0.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sparkjava</groupId>
|
||||
<artifactId>spark-core</artifactId>
|
||||
<version>2.9.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.swagger.core.v3</groupId>
|
||||
<artifactId>swagger-jaxrs2</artifactId>
|
||||
<version>${swagger.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
<dependency>
|
||||
<groupId>javax.ws.rs</groupId>
|
||||
<artifactId>javax.ws.rs-api</artifactId>
|
||||
<version>2.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.reflections</groupId>
|
||||
<artifactId>reflections</artifactId>
|
||||
<version>0.10.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.quartz-scheduler</groupId>
|
||||
<artifactId>quartz</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
41
runner.sh
41
runner.sh
@@ -1,41 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# script to help run the binary with specific jvm and jvm options
|
||||
# Defaults to openj9. Switch by setting JVM_IMPL environment variable
|
||||
|
||||
JAVA_BIN=$1
|
||||
JAR=$2
|
||||
shift 2
|
||||
|
||||
COMMON_PARAMETERS=" \
|
||||
-XX:+CompactStrings \
|
||||
"
|
||||
|
||||
JVM_IMPL="${JVM_IMPL:-openj9}"
|
||||
EXTRA_JVM_FLAGS="${EXTRA_JVM_FLAGS:-}"
|
||||
|
||||
if [ "$JVM_IMPL" = "hotspot" ]; then
|
||||
# for hotspot
|
||||
PARAMETERS="\
|
||||
$COMMON_PARAMETERS \
|
||||
-XX:+UseG1GC \
|
||||
-XX:+UseStringDeduplication \
|
||||
$EXTRA_JVM_FLAGS \
|
||||
"
|
||||
elif [ "$JVM_IMPL" = "openj9" ]; then
|
||||
# for openj9
|
||||
PARAMETERS=" \
|
||||
$COMMON_PARAMETERS \
|
||||
-XX:+IdleTuningGcOnIdle \
|
||||
-Xtune:virtualized
|
||||
$EXTRA_JVM_FLAGS \
|
||||
"
|
||||
else
|
||||
echo "Invalid JVM_IMPL option"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
"$JAVA_BIN" \
|
||||
$PARAMETERS \
|
||||
-jar "$JAR" \
|
||||
$@
|
||||
@@ -2,9 +2,7 @@ import http from 'k6/http';
|
||||
import { sleep } from 'k6';
|
||||
|
||||
|
||||
const INTERNAL_BASE_URL = 'http://localhost:16790/api/v1';
|
||||
const EXTERNAL_BASE_URL = __ENV.SEPARATE_INTERNAL_EXTERNAL_PORTS ? 'http://localhost:16789/api/v1' : INTERNAL_BASE_URL;
|
||||
|
||||
const BASE_URL = 'http://localhost:16789/api/v1';
|
||||
|
||||
export default function () {
|
||||
const endpoints = [
|
||||
@@ -14,19 +12,13 @@ export default function () {
|
||||
'getToplogy',
|
||||
'currentModel',
|
||||
];
|
||||
const internalRequests = endpoints.map(endpoint => {
|
||||
const requests = endpoints.map(endpoint => {
|
||||
return {
|
||||
method: 'GET',
|
||||
url: `${INTERNAL_BASE_URL}/${endpoint}`,
|
||||
};
|
||||
});
|
||||
const externalRequests = endpoints.map(endpoint => {
|
||||
return {
|
||||
method: 'GET',
|
||||
url: `${EXTERNAL_BASE_URL}/${endpoint}`,
|
||||
url: `${BASE_URL}/${endpoint}`,
|
||||
};
|
||||
});
|
||||
|
||||
let responses = http.batch([...internalRequests, ...externalRequests]);
|
||||
let responses = http.batch(requests);
|
||||
sleep(0.1);
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
@@ -18,10 +18,11 @@ import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer;
|
||||
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
|
||||
import com.facebook.openwifirrm.mysql.DatabaseManager;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
@@ -154,7 +155,8 @@ public class Launcher implements Callable<Integer> {
|
||||
: DEFAULT_DEVICE_LAYERED_CONFIG_FILE
|
||||
);
|
||||
|
||||
String serviceKey = Utils.generateServiceKey(config.serviceConfig);
|
||||
String serviceKey =
|
||||
UCentralUtils.generateServiceKey(config.serviceConfig);
|
||||
|
||||
// Instantiate clients
|
||||
UCentralClient.verifySsl(config.uCentralConfig.verifySsl);
|
||||
@@ -164,9 +166,7 @@ public class Launcher implements Callable<Integer> {
|
||||
config.uCentralConfig.uCentralSecPublicEndpoint,
|
||||
config.uCentralConfig.username,
|
||||
config.uCentralConfig.password,
|
||||
config.uCentralConfig.uCentralSocketParams.connectTimeoutMs,
|
||||
config.uCentralConfig.uCentralSocketParams.socketTimeoutMs,
|
||||
config.uCentralConfig.uCentralSocketParams.wifiScanTimeoutMs
|
||||
config.uCentralConfig.uCentralSocketParams
|
||||
);
|
||||
UCentralKafkaConsumer consumer;
|
||||
UCentralKafkaProducer producer;
|
||||
@@ -265,7 +265,7 @@ public class Launcher implements Callable<Integer> {
|
||||
.setPrettyPrinting()
|
||||
.serializeNulls() // for here only!!
|
||||
.create();
|
||||
System.out.println(gson.toJson(DeviceConfig.createDefault()));
|
||||
logger.info(gson.toJson(DeviceConfig.createDefault()));
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@@ -18,19 +18,18 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.KafkaRunner;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaProducer;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
|
||||
import com.facebook.openwifi.rrm.modules.ApiServer;
|
||||
import com.facebook.openwifi.rrm.modules.ConfigManager;
|
||||
import com.facebook.openwifi.rrm.modules.DataCollector;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler;
|
||||
import com.facebook.openwifi.rrm.modules.ProvMonitor;
|
||||
import com.facebook.openwifi.rrm.modules.RRMScheduler;
|
||||
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
|
||||
import com.facebook.openwifi.rrm.rca.modules.StationPinger;
|
||||
import com.facebook.openwifirrm.modules.ApiServer;
|
||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
||||
import com.facebook.openwifirrm.modules.DataCollector;
|
||||
import com.facebook.openwifirrm.modules.Modeler;
|
||||
import com.facebook.openwifirrm.modules.ProvMonitor;
|
||||
import com.facebook.openwifirrm.modules.RRMScheduler;
|
||||
import com.facebook.openwifirrm.mysql.DatabaseManager;
|
||||
import com.facebook.openwifirrm.ucentral.KafkaRunner;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaProducer;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
||||
|
||||
/**
|
||||
* RRM service runner.
|
||||
@@ -135,16 +134,10 @@ public class RRM {
|
||||
) : null;
|
||||
KafkaRunner kafkaRunner = (consumer == null && producer == null)
|
||||
? null : new KafkaRunner(consumer, producer);
|
||||
StationPinger stationPinger = new StationPinger(
|
||||
config.rcaConfig.stationPingerParams,
|
||||
client,
|
||||
consumer
|
||||
);
|
||||
|
||||
// Add shutdown hook
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
logger.debug("Running shutdown hook...");
|
||||
stationPinger.shutdown();
|
||||
if (kafkaRunner != null) {
|
||||
kafkaRunner.shutdown();
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -14,20 +14,17 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.rrm.modules.ConfigManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler;
|
||||
import com.facebook.openwifi.rrm.optimizers.channel.ChannelOptimizer;
|
||||
import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer;
|
||||
import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer;
|
||||
import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
||||
import com.facebook.openwifi.rrm.optimizers.clientsteering.ClientSteeringOptimizer;
|
||||
import com.facebook.openwifi.rrm.optimizers.clientsteering.ClientSteeringState;
|
||||
import com.facebook.openwifi.rrm.optimizers.clientsteering.SingleAPBandSteering;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.TPC;
|
||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler;
|
||||
import com.facebook.openwifirrm.optimizers.channel.ChannelOptimizer;
|
||||
import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer;
|
||||
import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer;
|
||||
import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.TPC;
|
||||
|
||||
/**
|
||||
* RRM algorithm model and utility methods.
|
||||
@@ -36,14 +33,6 @@ public class RRMAlgorithm {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(RRMAlgorithm.class);
|
||||
|
||||
/**
|
||||
* Client steering state. A single instance must be passed into all client
|
||||
* steering algorithms, as the state must persist across runs of the
|
||||
* optimizers.
|
||||
*/
|
||||
private static final ClientSteeringState clientSteeringState =
|
||||
new ClientSteeringState();
|
||||
|
||||
/** RRM algorithm type enum. */
|
||||
public enum AlgorithmType {
|
||||
OptimizeChannel (
|
||||
@@ -53,10 +42,6 @@ public class RRMAlgorithm {
|
||||
OptimizeTxPower (
|
||||
"Optimize tx power configuration",
|
||||
"Run transmit power control algorithm"
|
||||
),
|
||||
ClientSteering (
|
||||
"Steer clients onto the optimal AP and band",
|
||||
"Run client steering algorithm"
|
||||
);
|
||||
|
||||
/** The long name. */
|
||||
@@ -87,13 +72,6 @@ public class RRMAlgorithm {
|
||||
* @see TPC#computeTxPowerMap()
|
||||
*/
|
||||
public Map<String, Map<String, Integer>> txPowerMap;
|
||||
|
||||
/**
|
||||
* Computed actions for each AP-client pair.
|
||||
*
|
||||
* @see ClientSteeringOptimizer#computeApClientActionMap(boolean)
|
||||
*/
|
||||
public Map<String, Map<String, String>> apClientActionMap;
|
||||
}
|
||||
|
||||
/** The algorithm name (should be AlgorithmType enum string). */
|
||||
@@ -308,41 +286,6 @@ public class RRMAlgorithm {
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
}
|
||||
}
|
||||
} else if (
|
||||
name.equals(RRMAlgorithm.AlgorithmType.ClientSteering.name())
|
||||
) {
|
||||
logger.info(
|
||||
"Zone '{}': Running client steering optimizer (mode='{}')",
|
||||
zone,
|
||||
mode
|
||||
);
|
||||
ClientSteeringOptimizer optimizer;
|
||||
switch (mode) {
|
||||
default:
|
||||
if (!allowDefaultMode || !mode.isEmpty()) {
|
||||
result.error = modeErrorStr;
|
||||
return result;
|
||||
}
|
||||
logger.info("Using default algorithm mode...");
|
||||
// fall through
|
||||
case SingleAPBandSteering.ALGORITHM_ID:
|
||||
optimizer = SingleAPBandSteering.makeWithArgs(
|
||||
modeler.getDataModelCopy(),
|
||||
zone,
|
||||
deviceDataManager,
|
||||
clientSteeringState,
|
||||
args
|
||||
);
|
||||
break;
|
||||
}
|
||||
result.apClientActionMap =
|
||||
optimizer.computeApClientActionMap(dryRun);
|
||||
if (!dryRun) {
|
||||
optimizer.steer(result.apClientActionMap);
|
||||
if (updateImmediately) {
|
||||
configManager.queueZoneAndWakeUp(zone);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
result.error = String.format("Unknown algorithm: '%s'", name);
|
||||
}
|
||||
@@ -6,12 +6,10 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import com.facebook.openwifi.rrm.rca.RCAConfig;
|
||||
|
||||
/**
|
||||
* RRM service configuration model.
|
||||
*/
|
||||
@@ -36,7 +34,7 @@ public class RRMConfig {
|
||||
* Private endpoint for the RRM service
|
||||
* ({@code SERVICECONFIG_PRIVATEENDPOINT})
|
||||
*/
|
||||
public String privateEndpoint = "http://owrrm.wlan.local:16790"; // see ApiServerParams.internalHttpPort
|
||||
public String privateEndpoint = "http://owrrm.wlan.local:16789"; // see ApiServerParams.httpPort
|
||||
|
||||
/**
|
||||
* Public endpoint for the RRM service
|
||||
@@ -62,7 +60,7 @@ public class RRMConfig {
|
||||
* ({@code SERVICECONFIG_VENDORREFERENCEURL})
|
||||
*/
|
||||
public String vendorReferenceUrl =
|
||||
"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/owrrm/ALGORITHMS.md";
|
||||
"https://github.com/Telecominfraproject/wlan-cloud-rrm/blob/main/ALGORITHMS.md";
|
||||
}
|
||||
|
||||
/** Service configuration. */
|
||||
@@ -311,12 +309,6 @@ public class RRMConfig {
|
||||
* ({@code MODELERPARAMS_WIFISCANBUFFERSIZE})
|
||||
*/
|
||||
public int wifiScanBufferSize = 10;
|
||||
|
||||
/**
|
||||
* Maximum rounds of States to store per device
|
||||
* ({@code MODELERPARAMS_STATEBUFFERSIZE})
|
||||
*/
|
||||
public int stateBufferSize = 10;
|
||||
}
|
||||
|
||||
/** Modeler parameters. */
|
||||
@@ -327,16 +319,10 @@ public class RRMConfig {
|
||||
*/
|
||||
public class ApiServerParams {
|
||||
/**
|
||||
* The HTTP port to listen on for internal traffic, or -1 to disable
|
||||
* ({@code APISERVERPARAMS_INTERNALHTTPPORT})
|
||||
* The HTTP port to listen on, or -1 to disable
|
||||
* ({@code APISERVERPARAMS_HTTPPORT})
|
||||
*/
|
||||
public int internalHttpPort = 16790;
|
||||
|
||||
/**
|
||||
* The HTTP port to listen on for external traffic, or -1 to disable
|
||||
* ({@code APISERVERPARAMS_EXTERNALHTTPPORT})
|
||||
*/
|
||||
public int externalHttpPort = 16789;
|
||||
public int httpPort = 16789;
|
||||
|
||||
/**
|
||||
* Comma-separated list of all allowed CORS domains (exact match
|
||||
@@ -407,9 +393,6 @@ public class RRMConfig {
|
||||
/** Module configuration. */
|
||||
public ModuleConfig moduleConfig = new ModuleConfig();
|
||||
|
||||
/** Root cause analysis configuration. */
|
||||
public RCAConfig rcaConfig = new RCAConfig();
|
||||
|
||||
/** Construct RRMConfig from environment variables. */
|
||||
public static RRMConfig fromEnv(Map<String, String> env) {
|
||||
RRMConfig config = new RRMConfig();
|
||||
@@ -549,16 +532,10 @@ public class RRMConfig {
|
||||
if ((v = env.get("MODELERPARAMS_WIFISCANBUFFERSIZE")) != null) {
|
||||
modelerParams.wifiScanBufferSize = Integer.parseInt(v);
|
||||
}
|
||||
if ((v = env.get("MODELERPARAMS_STATEBUFFERSIZE")) != null) {
|
||||
modelerParams.stateBufferSize = Integer.parseInt(v);
|
||||
}
|
||||
ModuleConfig.ApiServerParams apiServerParams =
|
||||
config.moduleConfig.apiServerParams;
|
||||
if ((v = env.get("APISERVERPARAMS_INTERNALHTTPPORT")) != null) {
|
||||
apiServerParams.internalHttpPort = Integer.parseInt(v);
|
||||
}
|
||||
if ((v = env.get("APISERVERPARAMS_EXTERNALHTTPPORT")) != null) {
|
||||
apiServerParams.externalHttpPort = Integer.parseInt(v);
|
||||
if ((v = env.get("APISERVERPARAMS_HTTPPORT")) != null) {
|
||||
apiServerParams.httpPort = Integer.parseInt(v);
|
||||
}
|
||||
if ((v = env.get("APISERVERPARAMS_CORSDOMAINLIST")) != null) {
|
||||
apiServerParams.corsDomainList = v;
|
||||
@@ -588,9 +565,6 @@ public class RRMConfig {
|
||||
|
||||
// @formatter:on
|
||||
|
||||
/* RCAConfig */
|
||||
config.rcaConfig = RCAConfig.fromEnv(env);
|
||||
|
||||
return config;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
@@ -16,8 +16,6 @@ import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Scanner;
|
||||
@@ -25,8 +23,6 @@ import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.json.JSONObject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
@@ -35,8 +31,6 @@ import com.google.gson.GsonBuilder;
|
||||
* Generic utility methods.
|
||||
*/
|
||||
public class Utils {
|
||||
private static final Logger logger = LoggerFactory.getLogger(Utils.class);
|
||||
|
||||
/** Hex value array for use in {@link #longToMac(long)}. */
|
||||
private static final char[] HEX_VALUES = "0123456789abcdef".toCharArray();
|
||||
|
||||
@@ -199,19 +193,4 @@ public class Utils {
|
||||
public static <T> T deepCopy(T obj, Class<T> classOfT) {
|
||||
return gson.fromJson(gson.toJson(obj), classOfT);
|
||||
}
|
||||
|
||||
/** Generate the RRM service key. */
|
||||
public static String generateServiceKey(
|
||||
RRMConfig.ServiceConfig serviceConfig
|
||||
) {
|
||||
try {
|
||||
MessageDigest sha256 = MessageDigest.getInstance("SHA-256");
|
||||
sha256.update(serviceConfig.publicEndpoint.getBytes());
|
||||
sha256.update(serviceConfig.privateEndpoint.getBytes());
|
||||
return Utils.bytesToHex(sha256.digest());
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.error("Unable to generate service key", e);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm;
|
||||
package com.facebook.openwifirrm;
|
||||
|
||||
import picocli.CommandLine.IVersionProvider;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.aggregators;
|
||||
package com.facebook.openwifirrm.aggregators;
|
||||
|
||||
/**
|
||||
* Aggregates added values into one "aggregate" measure.
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.aggregators;
|
||||
package com.facebook.openwifirrm.aggregators;
|
||||
|
||||
/**
|
||||
* Tracks the mean of all added values. If no values are added, the mean is 0.
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
@@ -35,29 +35,28 @@ import org.reflections.util.ConfigurationBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.SystemInfoResults;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.TokenValidationResult;
|
||||
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Algorithm;
|
||||
import com.facebook.openwifi.cloudsdk.models.prov.rrm.Provider;
|
||||
import com.facebook.openwifi.rrm.CustomJettyServerFactory;
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.DeviceLayeredConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceTopology;
|
||||
import com.facebook.openwifi.rrm.RRMAlgorithm;
|
||||
import com.facebook.openwifi.rrm.Utils;
|
||||
import com.facebook.openwifi.rrm.VersionProvider;
|
||||
import com.facebook.openwifi.rrm.RRMConfig.ServiceConfig;
|
||||
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ApiServerParams;
|
||||
import com.facebook.openwifi.rrm.Utils.LruCache;
|
||||
import com.facebook.openwifi.rrm.optimizers.channel.LeastUsedChannelOptimizer;
|
||||
import com.facebook.openwifi.rrm.optimizers.channel.RandomChannelInitializer;
|
||||
import com.facebook.openwifi.rrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.LocationBasedOptimalTPC;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApApTPC;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
||||
import com.facebook.openwifi.rrm.optimizers.tpc.RandomTxPowerInitializer;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.DeviceLayeredConfig;
|
||||
import com.facebook.openwifirrm.DeviceTopology;
|
||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ApiServerParams;
|
||||
import com.facebook.openwifirrm.RRMConfig.ServiceConfig;
|
||||
import com.facebook.openwifirrm.Utils.LruCache;
|
||||
import com.facebook.openwifirrm.VersionProvider;
|
||||
import com.facebook.openwifirrm.optimizers.channel.LeastUsedChannelOptimizer;
|
||||
import com.facebook.openwifirrm.optimizers.channel.RandomChannelInitializer;
|
||||
import com.facebook.openwifirrm.optimizers.channel.UnmanagedApAwareChannelOptimizer;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.LocationBasedOptimalTPC;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApApTPC;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.MeasurementBasedApClientTPC;
|
||||
import com.facebook.openwifirrm.optimizers.tpc.RandomTxPowerInitializer;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.SystemInfoResults;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.TokenValidationResult;
|
||||
import com.facebook.openwifirrm.ucentral.prov.rrm.models.Algorithm;
|
||||
import com.facebook.openwifirrm.ucentral.prov.rrm.models.Provider;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
@@ -82,9 +81,7 @@ import io.swagger.v3.oas.models.OpenAPI;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
import spark.Service;
|
||||
import spark.embeddedserver.EmbeddedServers;
|
||||
import spark.embeddedserver.jetty.EmbeddedJettyFactory;
|
||||
import spark.Spark;
|
||||
|
||||
/**
|
||||
* HTTP API server.
|
||||
@@ -113,27 +110,6 @@ public class ApiServer implements Runnable {
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(ApiServer.class);
|
||||
|
||||
/**
|
||||
* This is the identifier for the server factory that Spark should use. This
|
||||
* particular identifier points to the custom factory that we register to
|
||||
* enable running multiple ports on one service.
|
||||
*
|
||||
* @see #run()
|
||||
*/
|
||||
private static final String SPARK_EMBEDDED_SERVER_IDENTIFIER =
|
||||
ApiServer.class.getName();
|
||||
|
||||
/**
|
||||
* The Spark service instance. Normally, you would use the static methods on
|
||||
* Spark, but since we need to spin up multiple instances of Spark for testing,
|
||||
* we choose to go with instantiating the service ourselves. There is really no
|
||||
* difference except with the static method, Spark calls ignite and holds a
|
||||
* singleton instance for us.
|
||||
*
|
||||
* @see Spark
|
||||
*/
|
||||
private final Service service;
|
||||
|
||||
/** The module parameters. */
|
||||
private final ApiServerParams params;
|
||||
|
||||
@@ -188,10 +164,9 @@ public class ApiServer implements Runnable {
|
||||
UCentralClient client,
|
||||
RRMScheduler scheduler
|
||||
) {
|
||||
this.service = Service.ignite();
|
||||
this.params = params;
|
||||
this.serviceConfig = serviceConfig;
|
||||
this.serviceKey = Utils.generateServiceKey(serviceConfig);
|
||||
this.serviceKey = UCentralUtils.generateServiceKey(serviceConfig);
|
||||
this.deviceDataManager = deviceDataManager;
|
||||
this.configManager = configManager;
|
||||
this.modeler = modeler;
|
||||
@@ -219,116 +194,64 @@ public class ApiServer implements Runnable {
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until initialization finishes. Just calls the method on the
|
||||
* underlying service.
|
||||
*/
|
||||
public void awaitInitialization() {
|
||||
service.awaitInitialization();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.startTimeMs = System.currentTimeMillis();
|
||||
|
||||
if (params.internalHttpPort == -1 && params.externalHttpPort == -1) {
|
||||
if (params.httpPort == -1) {
|
||||
logger.info("API server is disabled.");
|
||||
return;
|
||||
} else if (params.internalHttpPort == -1) {
|
||||
logger.info("Internal API server is disabled");
|
||||
} else if (params.externalHttpPort == -1) {
|
||||
logger.info("External API server is disabled");
|
||||
}
|
||||
|
||||
if (params.internalHttpPort == params.externalHttpPort) {
|
||||
logger.error(
|
||||
"Internal and external port cannot be the same - not starting API server"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
EmbeddedServers.add(
|
||||
SPARK_EMBEDDED_SERVER_IDENTIFIER,
|
||||
new EmbeddedJettyFactory(
|
||||
new CustomJettyServerFactory(
|
||||
params.internalHttpPort,
|
||||
params.externalHttpPort
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// use the embedded server factory added above, this is required so that we
|
||||
// don't mess up the default factory which can and will be used for
|
||||
// additional Spark services in testing
|
||||
service.embeddedServerIdentifier(SPARK_EMBEDDED_SERVER_IDENTIFIER);
|
||||
|
||||
// Usually you would call this with an actual port and Spark would spin up a
|
||||
// port on it. However, since we're putting our own connectors in so that we
|
||||
// can use two ports and Spark has logic to use connectors that already exist
|
||||
// so it doesn't matter what port we pass in here as long as it's not one of
|
||||
// the actual ports we're using (Spark has some weird logic where it still
|
||||
// tries to bind to the port).
|
||||
// @see EmbeddedJettyServer
|
||||
service.port(0);
|
||||
Spark.port(params.httpPort);
|
||||
|
||||
// Configure API docs hosting
|
||||
service.staticFiles.location("/public");
|
||||
service.get("/openapi.yaml", this::getOpenApiYaml);
|
||||
service.get("/openapi.json", this::getOpenApiJson);
|
||||
Spark.staticFiles.location("/public");
|
||||
Spark.get("/openapi.yaml", this::getOpenApiYaml);
|
||||
Spark.get("/openapi.json", this::getOpenApiJson);
|
||||
|
||||
// Install routes
|
||||
service.before(this::beforeFilter);
|
||||
service.after(this::afterFilter);
|
||||
service.options("/*", this::options);
|
||||
service.get("/api/v1/system", new SystemEndpoint());
|
||||
service.post("/api/v1/system", new SetSystemEndpoint());
|
||||
service.get("/api/v1/provider", new ProviderEndpoint());
|
||||
service.get("/api/v1/algorithms", new AlgorithmsEndpoint());
|
||||
service.put("/api/v1/runRRM", new RunRRMEndpoint());
|
||||
service.get("/api/v1/getTopology", new GetTopologyEndpoint());
|
||||
service.post("/api/v1/setTopology", new SetTopologyEndpoint());
|
||||
service.get(
|
||||
Spark.before(this::beforeFilter);
|
||||
Spark.after(this::afterFilter);
|
||||
Spark.options("/*", this::options);
|
||||
Spark.get("/api/v1/system", new SystemEndpoint());
|
||||
Spark.post("/api/v1/system", new SetSystemEndpoint());
|
||||
Spark.get("/api/v1/provider", new ProviderEndpoint());
|
||||
Spark.get("/api/v1/algorithms", new AlgorithmsEndpoint());
|
||||
Spark.put("/api/v1/runRRM", new RunRRMEndpoint());
|
||||
Spark.get("/api/v1/getTopology", new GetTopologyEndpoint());
|
||||
Spark.post("/api/v1/setTopology", new SetTopologyEndpoint());
|
||||
Spark.get(
|
||||
"/api/v1/getDeviceLayeredConfig",
|
||||
new GetDeviceLayeredConfigEndpoint()
|
||||
);
|
||||
service.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
|
||||
service.post(
|
||||
Spark.get("/api/v1/getDeviceConfig", new GetDeviceConfigEndpoint());
|
||||
Spark.post(
|
||||
"/api/v1/setDeviceNetworkConfig",
|
||||
new SetDeviceNetworkConfigEndpoint()
|
||||
);
|
||||
service.post(
|
||||
Spark.post(
|
||||
"/api/v1/setDeviceZoneConfig",
|
||||
new SetDeviceZoneConfigEndpoint()
|
||||
);
|
||||
service.post(
|
||||
Spark.post(
|
||||
"/api/v1/setDeviceApConfig",
|
||||
new SetDeviceApConfigEndpoint()
|
||||
);
|
||||
service.post(
|
||||
Spark.post(
|
||||
"/api/v1/modifyDeviceApConfig",
|
||||
new ModifyDeviceApConfigEndpoint()
|
||||
);
|
||||
service.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
|
||||
service.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
|
||||
service.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
|
||||
Spark.get("/api/v1/currentModel", new GetCurrentModelEndpoint());
|
||||
Spark.get("/api/v1/optimizeChannel", new OptimizeChannelEndpoint());
|
||||
Spark.get("/api/v1/optimizeTxPower", new OptimizeTxPowerEndpoint());
|
||||
|
||||
logger.info(
|
||||
"API server listening for HTTP internal on port {} and external on port {}",
|
||||
params.internalHttpPort,
|
||||
params.externalHttpPort
|
||||
);
|
||||
logger.info("API server listening on HTTP port {}", params.httpPort);
|
||||
}
|
||||
|
||||
/** Stop the server. */
|
||||
public void shutdown() {
|
||||
service.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Block until stop finishes. Just calls the method on the underlying service.
|
||||
*/
|
||||
public void awaitStop() {
|
||||
service.awaitStop();
|
||||
Spark.stop();
|
||||
}
|
||||
|
||||
/** Reconstructs a URL. */
|
||||
@@ -346,17 +269,15 @@ public class ApiServer implements Runnable {
|
||||
* HTTP 403 response and return false.
|
||||
*/
|
||||
private boolean performOpenWifiAuth(Request request, Response response) {
|
||||
int port = request.port();
|
||||
boolean internal = port > 0 && port == params.internalHttpPort;
|
||||
if (internal) {
|
||||
String internalName = request.headers("X-INTERNAL-NAME");
|
||||
if (internalName != null) {
|
||||
// Internal request, validate "X-API-KEY"
|
||||
String apiKey = request.headers("X-API-KEY");
|
||||
if (apiKey != null && apiKey.equals(serviceKey)) {
|
||||
// auth success
|
||||
return true;
|
||||
}
|
||||
// TODO check if request came from internal endpoint
|
||||
boolean internal = true;
|
||||
String internalName = request.headers("X-INTERNAL-NAME");
|
||||
if (internal && internalName != null) {
|
||||
// Internal request, validate "X-API-KEY"
|
||||
String apiKey = request.headers("X-API-KEY");
|
||||
if (apiKey != null && apiKey.equals(serviceKey)) {
|
||||
// auth success
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// External request, validate token:
|
||||
@@ -376,7 +297,7 @@ public class ApiServer implements Runnable {
|
||||
}
|
||||
|
||||
// auth failure
|
||||
service.halt(403, "Forbidden");
|
||||
Spark.halt(403, "Forbidden");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -404,11 +325,10 @@ public class ApiServer implements Runnable {
|
||||
private void beforeFilter(Request request, Response response) {
|
||||
// Log requests
|
||||
logger.debug(
|
||||
"[{}] {} {} on port {}",
|
||||
"[{}] {} {}",
|
||||
request.ip(),
|
||||
request.requestMethod(),
|
||||
getFullUrl(request.pathInfo(), request.queryString()),
|
||||
request.port()
|
||||
getFullUrl(request.pathInfo(), request.queryString())
|
||||
);
|
||||
|
||||
// Remove "Server: Jetty" header
|
||||
@@ -6,8 +6,9 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -20,13 +21,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ConfigManagerParams;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ConfigManagerParams;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
|
||||
|
||||
/**
|
||||
* Device configuration manager module.
|
||||
@@ -46,6 +47,9 @@ public class ConfigManager implements Runnable {
|
||||
|
||||
/** Runtime per-device data. */
|
||||
private class DeviceData {
|
||||
/** Last received device config. */
|
||||
public UCentralApConfiguration config;
|
||||
|
||||
/** Last config time (in monotonic ns). */
|
||||
public Long lastConfigTimeNs;
|
||||
}
|
||||
@@ -167,6 +171,7 @@ public class ConfigManager implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// Fetch device list
|
||||
List<DeviceWithStatus> devices = client.getDevices();
|
||||
@@ -179,7 +184,7 @@ public class ConfigManager implements Runnable {
|
||||
long now = System.nanoTime();
|
||||
|
||||
// Apply any config updates locally
|
||||
Map<String, String> devicesNeedingUpdate = new HashMap<>();
|
||||
List<String> devicesNeedingUpdate = new ArrayList<>();
|
||||
final long CONFIG_DEBOUNCE_INTERVAL_NS =
|
||||
params.configDebounceIntervalSec * 1_000_000_000L;
|
||||
Set<String> zonesToUpdateCopy = new HashSet<>(zonesToUpdate);
|
||||
@@ -200,12 +205,11 @@ public class ConfigManager implements Runnable {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
UCentralApConfiguration config =
|
||||
new UCentralApConfiguration(device.configuration);
|
||||
data.config = new UCentralApConfiguration(device.configuration);
|
||||
|
||||
// Call receive listeners
|
||||
for (ConfigListener listener : configListeners.values()) {
|
||||
listener.receiveDeviceConfig(device.serialNumber, config);
|
||||
listener.receiveDeviceConfig(device.serialNumber, data.config);
|
||||
}
|
||||
// Check if there are requested updates for this zone
|
||||
String deviceZone =
|
||||
@@ -233,7 +237,7 @@ public class ConfigManager implements Runnable {
|
||||
for (ConfigListener listener : configListeners.values()) {
|
||||
boolean wasModified = listener.processDeviceConfig(
|
||||
device.serialNumber,
|
||||
config
|
||||
data.config
|
||||
);
|
||||
if (wasModified) {
|
||||
modified = true;
|
||||
@@ -254,8 +258,7 @@ public class ConfigManager implements Runnable {
|
||||
);
|
||||
continue;
|
||||
} else {
|
||||
devicesNeedingUpdate
|
||||
.put(device.serialNumber, config.toString());
|
||||
devicesNeedingUpdate.add(device.serialNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -273,26 +276,19 @@ public class ConfigManager implements Runnable {
|
||||
devicesNeedingUpdate.size()
|
||||
);
|
||||
} else {
|
||||
// TODO: Replace with the newer owprov API to send only deltas, not
|
||||
// the full configuration blobs:
|
||||
// PUT /configurationOverrides/{serialNumber}?source=owrrm
|
||||
logger.info(
|
||||
"Sending config to {} device(s): {}",
|
||||
devicesNeedingUpdate.size(),
|
||||
String.join(", ", devicesNeedingUpdate.keySet())
|
||||
String.join(", ", devicesNeedingUpdate)
|
||||
);
|
||||
for (
|
||||
Map.Entry<String, String> entry : devicesNeedingUpdate
|
||||
.entrySet()
|
||||
) {
|
||||
String serialNumber = entry.getKey();
|
||||
for (String serialNumber : devicesNeedingUpdate) {
|
||||
DeviceData data = deviceDataMap.get(serialNumber);
|
||||
logger.info(
|
||||
"Device {}: sending new configuration...",
|
||||
serialNumber
|
||||
);
|
||||
data.lastConfigTimeNs = System.nanoTime();
|
||||
client.configure(serialNumber, entry.getValue());
|
||||
client.configure(serialNumber, data.config.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
@@ -23,22 +23,22 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.CommandInfo;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.Utils;
|
||||
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.DataCollectorParams;
|
||||
import com.facebook.openwifi.rrm.mysql.DatabaseManager;
|
||||
import com.facebook.openwifi.rrm.mysql.StateRecord;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.DataCollectorParams;
|
||||
import com.facebook.openwifirrm.Utils;
|
||||
import com.facebook.openwifirrm.mysql.DatabaseManager;
|
||||
import com.facebook.openwifirrm.mysql.StateRecord;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.CommandInfo;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceWithStatus;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.ServiceEvent;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
@@ -218,6 +218,7 @@ public class DataCollector implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// Fetch device list
|
||||
List<DeviceWithStatus> devices = client.getDevices();
|
||||
@@ -6,9 +6,8 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -21,24 +20,23 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.StateInfo;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralApConfiguration;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifi.cloudsdk.kafka.UCentralKafkaConsumer.KafkaRecord;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.UCentralSchema;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceCapabilities;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.DeviceWithStatus;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.ServiceEvent;
|
||||
import com.facebook.openwifi.cloudsdk.models.gw.StatisticsRecords;
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ModelerParams;
|
||||
import com.facebook.openwifi.rrm.Utils;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ModelerParams;
|
||||
import com.facebook.openwifirrm.Utils;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralApConfiguration;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralKafkaConsumer.KafkaRecord;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.gw.models.DeviceCapabilities;
|
||||
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.models.State;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
@@ -95,16 +93,15 @@ public class Modeler implements Runnable {
|
||||
public Map<String, List<List<WifiScanEntry>>> latestWifiScans =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/** List of latest states per device. */
|
||||
public Map<String, List<StateInfo>> latestStates =
|
||||
new ConcurrentHashMap<>();
|
||||
/** List of latest state per device. */
|
||||
public Map<String, State> latestState = new ConcurrentHashMap<>();
|
||||
|
||||
/** List of radio info per device. */
|
||||
public Map<String, List<UCentralSchema.Radio>> latestDeviceStatusRadios =
|
||||
public Map<String, JsonArray> latestDeviceStatusRadios =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
/** List of capabilities per device. */
|
||||
public Map<String, Map<String, Capabilities.Phy>> latestDeviceCapabilitiesPhy =
|
||||
public Map<String, JsonObject> latestDeviceCapabilities =
|
||||
new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
@@ -170,15 +167,10 @@ public class Modeler implements Runnable {
|
||||
consumer.addKafkaListener(
|
||||
getClass().getSimpleName(),
|
||||
new UCentralKafkaConsumer.KafkaListener() {
|
||||
// NOTE: list copying due to potential modification in run()
|
||||
|
||||
@Override
|
||||
public void handleStateRecords(List<KafkaRecord> records) {
|
||||
dataQueue.offer(
|
||||
new InputData(
|
||||
InputDataType.STATE,
|
||||
new ArrayList<>(records)
|
||||
)
|
||||
new InputData(InputDataType.STATE, records)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,10 +179,7 @@ public class Modeler implements Runnable {
|
||||
List<KafkaRecord> records
|
||||
) {
|
||||
dataQueue.offer(
|
||||
new InputData(
|
||||
InputDataType.WIFISCAN,
|
||||
new ArrayList<>(records)
|
||||
)
|
||||
new InputData(InputDataType.WIFISCAN, records)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -249,6 +238,7 @@ public class Modeler implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// TODO: backfill data from database?
|
||||
|
||||
@@ -275,16 +265,10 @@ public class Modeler implements Runnable {
|
||||
continue;
|
||||
}
|
||||
JsonObject state = records.data.get(0).data;
|
||||
long timestamp = records.data.get(0).recorded;
|
||||
if (state != null) {
|
||||
try {
|
||||
StateInfo stateModel =
|
||||
gson.fromJson(state, StateInfo.class);
|
||||
stateModel.timestamp = timestamp;
|
||||
dataModel.latestStates.computeIfAbsent(
|
||||
device.serialNumber,
|
||||
k -> new LinkedList<>()
|
||||
).add(stateModel);
|
||||
State stateModel = gson.fromJson(state, State.class);
|
||||
dataModel.latestState.put(device.serialNumber, stateModel);
|
||||
logger.debug(
|
||||
"Device {}: added initial state from uCentralGw",
|
||||
device.serialNumber
|
||||
@@ -315,21 +299,9 @@ public class Modeler implements Runnable {
|
||||
JsonObject state = record.payload.getAsJsonObject("state");
|
||||
if (state != null) {
|
||||
try {
|
||||
StateInfo stateModel =
|
||||
gson.fromJson(state, StateInfo.class);
|
||||
stateModel.timestamp = record.timestampMs;
|
||||
List<StateInfo> latestStatesList =
|
||||
dataModel.latestStates
|
||||
.computeIfAbsent(
|
||||
record.serialNumber,
|
||||
k -> new LinkedList<>()
|
||||
);
|
||||
while (
|
||||
latestStatesList.size() >= params.stateBufferSize
|
||||
) {
|
||||
latestStatesList.remove(0);
|
||||
}
|
||||
latestStatesList.add(stateModel);
|
||||
State stateModel = gson.fromJson(state, State.class);
|
||||
dataModel.latestState
|
||||
.put(record.serialNumber, stateModel);
|
||||
stateUpdates.add(record.serialNumber);
|
||||
} catch (JsonSyntaxException e) {
|
||||
logger.error(
|
||||
@@ -392,9 +364,9 @@ public class Modeler implements Runnable {
|
||||
String serialNumber,
|
||||
DeviceCapabilities capabilities
|
||||
) {
|
||||
dataModel.latestDeviceCapabilitiesPhy.put(
|
||||
dataModel.latestDeviceCapabilities.put(
|
||||
serialNumber,
|
||||
capabilities.capabilities.wifi
|
||||
capabilities.capabilities.getAsJsonObject("wifi")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -406,11 +378,10 @@ public class Modeler implements Runnable {
|
||||
UCentralApConfiguration config
|
||||
) {
|
||||
// Get old vs new radios info and store the new radios info
|
||||
List<UCentralSchema.Radio> newRadioList = config.getRadioConfigList();
|
||||
JsonArray newRadioList = config.getRadioConfigList();
|
||||
Set<String> newRadioBandsSet = config.getRadioBandsSet(newRadioList);
|
||||
List<UCentralSchema.Radio> oldRadioList =
|
||||
dataModel.latestDeviceStatusRadios
|
||||
.put(serialNumber, newRadioList);
|
||||
JsonArray oldRadioList = dataModel.latestDeviceStatusRadios
|
||||
.put(serialNumber, newRadioList);
|
||||
Set<String> oldRadioBandsSet = config.getRadioBandsSet(oldRadioList);
|
||||
|
||||
// Print info only when there are any updates
|
||||
@@ -453,7 +424,7 @@ public class Modeler implements Runnable {
|
||||
logger.debug("Removed some wifi scan entries from data model");
|
||||
}
|
||||
if (
|
||||
dataModel.latestStates.entrySet()
|
||||
dataModel.latestState.entrySet()
|
||||
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
||||
) {
|
||||
logger.debug("Removed some state entries from data model");
|
||||
@@ -465,7 +436,7 @@ public class Modeler implements Runnable {
|
||||
logger.debug("Removed some status entries from data model");
|
||||
}
|
||||
if (
|
||||
dataModel.latestDeviceCapabilitiesPhy.entrySet()
|
||||
dataModel.latestDeviceCapabilities.entrySet()
|
||||
.removeIf(e -> !isRRMEnabled(e.getKey()))
|
||||
) {
|
||||
logger.debug("Removed some capabilities entries from data model");
|
||||
@@ -6,32 +6,24 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||
import com.facebook.openwifi.cloudsdk.AggregatedState;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.StateInfo;
|
||||
import com.facebook.openwifi.cloudsdk.ies.HTOperation;
|
||||
import com.facebook.openwifi.cloudsdk.ies.VHTOperation;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State.Interface.SSID.Association;
|
||||
import com.facebook.openwifi.rrm.aggregators.Aggregator;
|
||||
import com.facebook.openwifi.rrm.aggregators.MeanAggregator;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.aggregators.Aggregator;
|
||||
import com.facebook.openwifirrm.aggregators.MeanAggregator;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.HTOperation;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.VHTOperation;
|
||||
|
||||
/**
|
||||
* Modeler utilities.
|
||||
@@ -299,7 +291,7 @@ public class ModelerUtils {
|
||||
/**
|
||||
* Compute aggregated wifiscans using a given reference time.
|
||||
*
|
||||
* @see #getAggregatedWifiScans(com.facebook.openwifi.rrm.modules.Modeler.DataModel,
|
||||
* @see #getAggregatedWifiScans(com.facebook.openwifirrm.modules.Modeler.DataModel,
|
||||
* long, Aggregator)
|
||||
*/
|
||||
public static Map<String, Map<String, WifiScanEntry>> getAggregatedWifiScans(
|
||||
@@ -389,188 +381,4 @@ public class ModelerUtils {
|
||||
}
|
||||
return aggregatedWifiScans;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method converts the input State info to an AggregatedState
|
||||
* and adds it to the bssidToAggregatedStates map. If the bssid/station
|
||||
* of the input State does not exist in the map, create a new
|
||||
* AggregatedState list. If the bssid/station of the input State exists,
|
||||
* then convert State to AggregatedState and check if there exits an
|
||||
* AggregatedState of the same radio. If there does, append the value
|
||||
* of aggregation field to the existing AggregatedState, if not, create
|
||||
* a new AggregatedState and add it to the list.
|
||||
*
|
||||
* @param bssidToAggregatedStates map from bssid/station to a list of AggregatedState
|
||||
* @param state the state that is to be added
|
||||
*/
|
||||
static void addStateToAggregation(
|
||||
Map<String, List<AggregatedState>> bssidToAggregatedStates,
|
||||
StateInfo state
|
||||
) {
|
||||
for (Interface stateInterface : state.interfaces) {
|
||||
if (stateInterface.ssids == null) {
|
||||
continue;
|
||||
}
|
||||
for (SSID ssid : stateInterface.ssids) {
|
||||
for (Association association : ssid.associations) {
|
||||
if (association == null) {
|
||||
continue;
|
||||
}
|
||||
String key = getBssidStationKeyPair(
|
||||
association.bssid,
|
||||
association.station
|
||||
);
|
||||
List<AggregatedState> aggregatedStates =
|
||||
bssidToAggregatedStates
|
||||
.computeIfAbsent(key, k -> new ArrayList<>());
|
||||
AggregatedState aggState =
|
||||
new AggregatedState(
|
||||
association,
|
||||
ssid.counters,
|
||||
ssid.radio,
|
||||
state.timestamp
|
||||
);
|
||||
|
||||
/**
|
||||
* Indicate if the aggState can be merged into some old AggregatedState.
|
||||
* If true, it will be merged by appending its mcs/rssi field to the old one.
|
||||
* If false, it will be added to the list aggregatedStates.
|
||||
*/
|
||||
boolean canBeMergedToOldAggregatedState = false;
|
||||
for (
|
||||
AggregatedState oldAggregatedState : aggregatedStates
|
||||
) {
|
||||
if (oldAggregatedState.add(aggState)) {
|
||||
canBeMergedToOldAggregatedState = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!canBeMergedToOldAggregatedState) {
|
||||
aggregatedStates.add(aggState);
|
||||
}
|
||||
bssidToAggregatedStates.put(key, aggregatedStates);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method aggregates States by bssid/station key pair and radio info.
|
||||
* if two States of the same bssid/station match in channel, channel width and tx_power
|
||||
* need to be aggregated to one {@code AggregatedState}. Currently only mcs and
|
||||
* rssi fields are being aggregated. They are of {@code List<Integer>} type in AggregatedState,
|
||||
* which list all the values over the time.
|
||||
*
|
||||
* @param dataModel the data model which includes the latest recorded States
|
||||
* @param obsoletionPeriodMs the maximum amount of time (in milliseconds) it
|
||||
* is worth aggregating over, starting from the
|
||||
* most recent States and working backwards in time.
|
||||
* A State exactly {@code obsoletionPeriodMs} ms earlier
|
||||
* than the most recent State is considered non-obsolete
|
||||
* (i.e., the "non-obsolete" window is inclusive).
|
||||
* Must be non-negative.
|
||||
* @param refTimeMs the reference time were passed to make testing easier
|
||||
* @return map from serial number to a map from bssid_station String pair to a list of AggregatedState
|
||||
*/
|
||||
public static Map<String, Map<String, List<AggregatedState>>> getAggregatedStates(
|
||||
Modeler.DataModel dataModel,
|
||||
long obsoletionPeriodMs,
|
||||
long refTimeMs
|
||||
) {
|
||||
if (obsoletionPeriodMs < 0) {
|
||||
throw new IllegalArgumentException(
|
||||
"obsoletionPeriodMs must be non-negative."
|
||||
);
|
||||
}
|
||||
Map<String, Map<String, List<AggregatedState>>> aggregatedStates =
|
||||
new HashMap<>();
|
||||
|
||||
for (
|
||||
Map.Entry<String, List<StateInfo>> deviceToStateList : dataModel.latestStates
|
||||
.entrySet()
|
||||
) {
|
||||
String serialNumber = deviceToStateList.getKey();
|
||||
List<StateInfo> states = deviceToStateList.getValue();
|
||||
|
||||
if (states.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort in reverse chronological order. Sorting is done just in case the
|
||||
* States in the original list are not chronological already - although
|
||||
* they are inserted chronologically, perhaps latency, synchronization, etc.
|
||||
*/
|
||||
states.sort(
|
||||
(state1, state2) -> -Long.compare(state1.unit.localtime, state2.unit.localtime)
|
||||
);
|
||||
|
||||
Map<String, List<AggregatedState>> bssidToAggregatedStates =
|
||||
aggregatedStates
|
||||
.computeIfAbsent(serialNumber, k -> new HashMap<>());
|
||||
|
||||
for (StateInfo state : states) {
|
||||
if (refTimeMs - state.unit.localtime > obsoletionPeriodMs) {
|
||||
// discard obsolete entries
|
||||
break;
|
||||
}
|
||||
|
||||
addStateToAggregation(bssidToAggregatedStates, state);
|
||||
}
|
||||
}
|
||||
return aggregatedStates;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method gets the most recent State from latestStates per device.
|
||||
*
|
||||
* @param latestStates list of latest States per device
|
||||
* @return map from device String to latest State
|
||||
*/
|
||||
public static Map<String, StateInfo> getLatestState(
|
||||
Map<String, List<StateInfo>> latestStates
|
||||
) {
|
||||
Map<String, StateInfo> latestState = new ConcurrentHashMap<>();
|
||||
for (
|
||||
Map.Entry<String, List<StateInfo>> stateEntry : latestStates
|
||||
.entrySet()
|
||||
) {
|
||||
String key = stateEntry.getKey();
|
||||
List<StateInfo> value = stateEntry.getValue();
|
||||
if (value.isEmpty()) {
|
||||
latestState.put(key, null);
|
||||
} else {
|
||||
latestState.put(key, value.get(value.size() - 1));
|
||||
}
|
||||
}
|
||||
return latestState;
|
||||
}
|
||||
|
||||
/** Create a key pair consisted of bssid and station string */
|
||||
public static String getBssidStationKeyPair(String bssid, String station) {
|
||||
return String.format(
|
||||
"bssid: %s, station: %s",
|
||||
bssid,
|
||||
station
|
||||
);
|
||||
}
|
||||
|
||||
/** Return the radio's band, or null if band cannot be found */
|
||||
public static String getBand(
|
||||
State.Radio radio,
|
||||
Map<String, Capabilities.Phy> capabilityPhy
|
||||
) {
|
||||
if (radio.phy == null) {
|
||||
return null;
|
||||
}
|
||||
Capabilities.Phy phy = capabilityPhy.get(radio.phy);
|
||||
if (phy == null) {
|
||||
return null;
|
||||
}
|
||||
String[] bands = phy.band;
|
||||
if (bands == null || bands.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return bands[0];
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
@@ -18,19 +18,19 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralClient;
|
||||
import com.facebook.openwifi.cloudsdk.models.prov.InventoryTag;
|
||||
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.Venue;
|
||||
import com.facebook.openwifi.cloudsdk.models.prov.VenueList;
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.DeviceTopology;
|
||||
import com.facebook.openwifi.rrm.RRMAlgorithm;
|
||||
import com.facebook.openwifi.rrm.RRMSchedule;
|
||||
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.ProvMonitorParams;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.DeviceTopology;
|
||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.ProvMonitorParams;
|
||||
import com.facebook.openwifirrm.RRMSchedule;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralClient;
|
||||
import com.facebook.openwifirrm.ucentral.prov.models.InventoryTag;
|
||||
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.Venue;
|
||||
import com.facebook.openwifirrm.ucentral.prov.models.VenueList;
|
||||
|
||||
/**
|
||||
* owprov monitor module.
|
||||
@@ -103,6 +103,7 @@ public class ProvMonitor implements Runnable {
|
||||
return;
|
||||
}
|
||||
}
|
||||
client.refreshAccessToken();
|
||||
|
||||
// Fetch data from owprov
|
||||
// TODO: this may change later - for now, we only fetch inventory and
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.modules;
|
||||
package com.facebook.openwifirrm.modules;
|
||||
|
||||
import java.text.ParseException;
|
||||
import java.util.Arrays;
|
||||
@@ -32,11 +32,11 @@ import org.quartz.impl.StdSchedulerFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.RRMAlgorithm;
|
||||
import com.facebook.openwifi.rrm.RRMSchedule;
|
||||
import com.facebook.openwifi.rrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.RRMAlgorithm;
|
||||
import com.facebook.openwifirrm.RRMSchedule;
|
||||
import com.facebook.openwifirrm.RRMConfig.ModuleConfig.RRMSchedulerParams;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.mysql;
|
||||
package com.facebook.openwifirrm.mysql;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
@@ -26,9 +26,9 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.rrm.Utils;
|
||||
import com.facebook.openwifirrm.Utils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.mysql;
|
||||
package com.facebook.openwifirrm.mysql;
|
||||
|
||||
/**
|
||||
* Representation of a record in the "state" table.
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.channel;
|
||||
package com.facebook.openwifirrm.optimizers.channel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -18,18 +18,16 @@ import java.util.Map;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralConstants;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.ies.HTOperation;
|
||||
import com.facebook.openwifi.cloudsdk.ies.VHTOperation;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.ConfigManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.ConfigManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.HTOperation;
|
||||
import com.facebook.openwifirrm.ucentral.informationelement.VHTOperation;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
|
||||
/**
|
||||
* Channel optimizer base class.
|
||||
@@ -41,6 +39,24 @@ public abstract class ChannelOptimizer {
|
||||
/** Minimum supported channel width (MHz), inclusive. */
|
||||
public static final int MIN_CHANNEL_WIDTH = 20;
|
||||
|
||||
/** List of available channels per band for use. */
|
||||
public static final Map<String, List<Integer>> AVAILABLE_CHANNELS_BAND =
|
||||
new HashMap<>();
|
||||
static {
|
||||
AVAILABLE_CHANNELS_BAND.put(
|
||||
UCentralConstants.BAND_5G,
|
||||
Collections.unmodifiableList(
|
||||
Arrays.asList(36, 40, 44, 48, 149, 153, 157, 161, 165)
|
||||
)
|
||||
);
|
||||
AVAILABLE_CHANNELS_BAND.put(
|
||||
UCentralConstants.BAND_2G,
|
||||
Collections.unmodifiableList(
|
||||
Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/** Map of channel width (MHz) to available (primary) channels */
|
||||
protected static final Map<Integer, List<Integer>> AVAILABLE_CHANNELS_WIDTH =
|
||||
new HashMap<>();
|
||||
@@ -138,11 +154,11 @@ public abstract class ChannelOptimizer {
|
||||
// Remove model entries not in the given zone
|
||||
this.model.latestWifiScans.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
this.model.latestStates.keySet()
|
||||
this.model.latestState.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
this.model.latestDeviceStatusRadios.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
this.model.latestDeviceCapabilitiesPhy.keySet()
|
||||
this.model.latestDeviceCapabilities.keySet()
|
||||
.removeIf(serialNumber -> !deviceConfigs.containsKey(serialNumber));
|
||||
}
|
||||
|
||||
@@ -181,7 +197,7 @@ public abstract class ChannelOptimizer {
|
||||
String vhtOper
|
||||
) {
|
||||
if (
|
||||
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G)
|
||||
AVAILABLE_CHANNELS_BAND.get(UCentralConstants.BAND_2G)
|
||||
.contains(channel)
|
||||
) {
|
||||
// 2.4G, it only supports 20 MHz
|
||||
@@ -300,28 +316,25 @@ public abstract class ChannelOptimizer {
|
||||
List<WifiScanEntry> scanRespsFiltered =
|
||||
new ArrayList<WifiScanEntry>();
|
||||
for (WifiScanEntry entry : scanResps) {
|
||||
final String entryBand = UCentralUtils
|
||||
.freqToBand(entry.frequency);
|
||||
if (entryBand == null || !entryBand.equals(band)) {
|
||||
continue;
|
||||
}
|
||||
int channelWidth = getChannelWidthFromWiFiScan(
|
||||
entry.channel,
|
||||
entry.ht_oper,
|
||||
entry.vht_oper
|
||||
);
|
||||
int primaryChannel =
|
||||
getPrimaryChannel(entry.channel, channelWidth);
|
||||
List<Integer> coveredChannels =
|
||||
getCoveredChannels(
|
||||
if (UCentralUtils.isChannelInBand(entry.channel, band)) {
|
||||
int channelWidth = getChannelWidthFromWiFiScan(
|
||||
entry.channel,
|
||||
primaryChannel,
|
||||
channelWidth
|
||||
entry.ht_oper,
|
||||
entry.vht_oper
|
||||
);
|
||||
for (Integer newChannel : coveredChannels) {
|
||||
WifiScanEntry newEntry = new WifiScanEntry(entry);
|
||||
newEntry.channel = newChannel;
|
||||
scanRespsFiltered.add(newEntry);
|
||||
int primaryChannel =
|
||||
getPrimaryChannel(entry.channel, channelWidth);
|
||||
List<Integer> coveredChannels =
|
||||
getCoveredChannels(
|
||||
entry.channel,
|
||||
primaryChannel,
|
||||
channelWidth
|
||||
);
|
||||
for (Integer newChannel : coveredChannels) {
|
||||
WifiScanEntry newEntry = new WifiScanEntry(entry);
|
||||
newEntry.channel = newChannel;
|
||||
scanRespsFiltered.add(newEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,7 +362,6 @@ public abstract class ChannelOptimizer {
|
||||
* @param band the operational band (e.g., "2G")
|
||||
* @param serialNumber the device's serial number
|
||||
* @param state the latest state of all the devices
|
||||
* @param latestDeviceCapabilitiesPhy latest device phy from capabilities
|
||||
* @return the current channel and channel width (MHz) of the device in the
|
||||
* given band; returns a current channel of 0 if no channel in the given
|
||||
* band is found.
|
||||
@@ -357,8 +369,7 @@ public abstract class ChannelOptimizer {
|
||||
protected static int[] getCurrentChannel(
|
||||
String band,
|
||||
String serialNumber,
|
||||
State state,
|
||||
Map<String, Map<String, Capabilities.Phy>> latestDeviceCapabilitiesPhy
|
||||
State state
|
||||
) {
|
||||
int currentChannel = 0;
|
||||
int currentChannelWidth = MIN_CHANNEL_WIDTH;
|
||||
@@ -368,40 +379,27 @@ public abstract class ChannelOptimizer {
|
||||
radioIndex < state.radios.length;
|
||||
radioIndex++
|
||||
) {
|
||||
State.Radio radio = state.radios[radioIndex];
|
||||
// check if radio is in band of interest
|
||||
Map<String, Capabilities.Phy> capabilitiesPhy =
|
||||
latestDeviceCapabilitiesPhy.get(serialNumber);
|
||||
if (capabilitiesPhy == null) {
|
||||
continue;
|
||||
}
|
||||
final String radioBand = ModelerUtils.getBand(
|
||||
radio,
|
||||
capabilitiesPhy
|
||||
);
|
||||
if (radioBand == null || !radioBand.equals(band)) {
|
||||
continue;
|
||||
}
|
||||
int tempChannel = state.radios[radioIndex].channel;
|
||||
if (UCentralUtils.isChannelInBand(tempChannel, band)) {
|
||||
currentChannel = tempChannel;
|
||||
// treat as two separate 80MHz channel and only assign to one
|
||||
// TODO: support 80p80 properly
|
||||
Integer parsedChannelWidth = UCentralUtils
|
||||
.parseChannelWidth(
|
||||
state.radios[radioIndex].channel_width,
|
||||
true
|
||||
);
|
||||
if (parsedChannelWidth != null) {
|
||||
currentChannelWidth = parsedChannelWidth;
|
||||
break;
|
||||
}
|
||||
|
||||
int tempChannel = radio.channel;
|
||||
currentChannel = tempChannel;
|
||||
// treat as two separate 80MHz channel and only assign to one
|
||||
// TODO: support 80p80 properly
|
||||
Integer parsedChannelWidth = UCentralUtils
|
||||
.parseChannelWidth(
|
||||
radio.channel_width,
|
||||
true
|
||||
logger.error(
|
||||
"Invalid channel width {}",
|
||||
state.radios[radioIndex].channel_width
|
||||
);
|
||||
if (parsedChannelWidth != null) {
|
||||
currentChannelWidth = parsedChannelWidth;
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.error(
|
||||
"Invalid channel width {}",
|
||||
radio.channel_width
|
||||
);
|
||||
continue;
|
||||
}
|
||||
return new int[] { currentChannel, currentChannelWidth };
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.channel;
|
||||
package com.facebook.openwifirrm.optimizers.channel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -20,13 +20,12 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralConstants;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
|
||||
/**
|
||||
* Least used channel optimizer.
|
||||
@@ -90,13 +89,13 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
||||
protected static Map<Integer, Integer> getOccupiedOverlapChannels(
|
||||
Map<Integer, Integer> occupiedChannels
|
||||
) {
|
||||
final int maxChannel =
|
||||
UCentralUtils.getUpperChannelLimit(UCentralConstants.BAND_2G);
|
||||
final int minChannel =
|
||||
UCentralUtils.getLowerChannelLimit(UCentralConstants.BAND_2G);
|
||||
int maxChannel =
|
||||
UCentralUtils.UPPER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
||||
int minChannel =
|
||||
UCentralUtils.LOWER_CHANNEL_LIMIT.get(UCentralConstants.BAND_2G);
|
||||
Map<Integer, Integer> occupiedOverlapChannels = new TreeMap<>();
|
||||
for (
|
||||
int overlapChannel : UCentralUtils.AVAILABLE_CHANNELS_BAND
|
||||
int overlapChannel : AVAILABLE_CHANNELS_BAND
|
||||
.get(UCentralConstants.BAND_2G)
|
||||
) {
|
||||
int occupancy = 0;
|
||||
@@ -337,14 +336,12 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
||||
Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
|
||||
UCentralUtils.getDeviceAvailableChannels(
|
||||
model.latestDeviceStatusRadios,
|
||||
model.latestDeviceCapabilitiesPhy,
|
||||
UCentralUtils.AVAILABLE_CHANNELS_BAND
|
||||
model.latestDeviceCapabilities,
|
||||
AVAILABLE_CHANNELS_BAND
|
||||
);
|
||||
|
||||
Map<String, ? extends State> latestState =
|
||||
ModelerUtils.getLatestState(model.latestStates);
|
||||
Map<String, String> bssidsMap =
|
||||
UCentralUtils.getBssidsMap(latestState);
|
||||
UCentralUtils.getBssidsMap(model.latestState);
|
||||
|
||||
for (String band : bandsMap.keySet()) {
|
||||
// Performance metrics
|
||||
@@ -372,12 +369,11 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
||||
availableChannelsList == null ||
|
||||
availableChannelsList.isEmpty()
|
||||
) {
|
||||
availableChannelsList =
|
||||
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band);
|
||||
availableChannelsList = AVAILABLE_CHANNELS_BAND.get(band);
|
||||
}
|
||||
|
||||
// Get current channel of the device
|
||||
State state = latestState.get(serialNumber);
|
||||
State state = model.latestState.get(serialNumber);
|
||||
if (state == null) {
|
||||
logger.debug(
|
||||
"Device {}: No state found, skipping...",
|
||||
@@ -393,12 +389,7 @@ public class LeastUsedChannelOptimizer extends ChannelOptimizer {
|
||||
continue;
|
||||
}
|
||||
int[] currentChannelInfo =
|
||||
getCurrentChannel(
|
||||
band,
|
||||
serialNumber,
|
||||
state,
|
||||
model.latestDeviceCapabilitiesPhy
|
||||
);
|
||||
getCurrentChannel(band, serialNumber, state);
|
||||
int currentChannel = currentChannelInfo[0];
|
||||
// Filter out APs if the radios in the state do not contain a
|
||||
// channel in a band given by the state. This can happen when
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.channel;
|
||||
package com.facebook.openwifirrm.optimizers.channel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -19,12 +19,11 @@ import java.util.TreeSet;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
|
||||
/**
|
||||
* Random channel initializer.
|
||||
@@ -125,14 +124,12 @@ public class RandomChannelInitializer extends ChannelOptimizer {
|
||||
Map<String, Map<String, List<Integer>>> deviceAvailableChannels =
|
||||
UCentralUtils.getDeviceAvailableChannels(
|
||||
model.latestDeviceStatusRadios,
|
||||
model.latestDeviceCapabilitiesPhy,
|
||||
UCentralUtils.AVAILABLE_CHANNELS_BAND
|
||||
model.latestDeviceCapabilities,
|
||||
AVAILABLE_CHANNELS_BAND
|
||||
);
|
||||
|
||||
Map<String, ? extends State> latestState =
|
||||
ModelerUtils.getLatestState(model.latestStates);
|
||||
Map<String, String> bssidsMap =
|
||||
UCentralUtils.getBssidsMap(latestState);
|
||||
UCentralUtils.getBssidsMap(model.latestState);
|
||||
|
||||
for (Map.Entry<String, List<String>> entry : bandsMap.entrySet()) {
|
||||
// Performance metrics
|
||||
@@ -152,7 +149,7 @@ public class RandomChannelInitializer extends ChannelOptimizer {
|
||||
// to get the valid result for single channel assignment
|
||||
// If the intersection is empty, then turn back to the default channels list
|
||||
List<Integer> availableChannelsList = new ArrayList<>(
|
||||
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band)
|
||||
AVAILABLE_CHANNELS_BAND.get(band)
|
||||
);
|
||||
for (String serialNumber : entry.getValue()) {
|
||||
List<Integer> deviceChannelsList = deviceAvailableChannels
|
||||
@@ -161,16 +158,14 @@ public class RandomChannelInitializer extends ChannelOptimizer {
|
||||
if (
|
||||
deviceChannelsList == null || deviceChannelsList.isEmpty()
|
||||
) {
|
||||
deviceChannelsList =
|
||||
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band);
|
||||
deviceChannelsList = AVAILABLE_CHANNELS_BAND.get(band);
|
||||
}
|
||||
availableChannelsList.retainAll(deviceChannelsList);
|
||||
}
|
||||
if (
|
||||
availableChannelsList == null || availableChannelsList.isEmpty()
|
||||
) {
|
||||
availableChannelsList =
|
||||
UCentralUtils.AVAILABLE_CHANNELS_BAND.get(band);
|
||||
availableChannelsList = AVAILABLE_CHANNELS_BAND.get(band);
|
||||
logger.debug(
|
||||
"The intersection of the device channels lists is empty!!! " +
|
||||
"Fall back to the default channels list"
|
||||
@@ -188,7 +183,7 @@ public class RandomChannelInitializer extends ChannelOptimizer {
|
||||
? rng.nextInt(availableChannelsList.size()) : defaultChannelIndex
|
||||
);
|
||||
|
||||
State state = latestState.get(serialNumber);
|
||||
State state = model.latestState.get(serialNumber);
|
||||
if (state == null) {
|
||||
logger.debug(
|
||||
"Device {}: No state found, skipping...",
|
||||
@@ -204,12 +199,7 @@ public class RandomChannelInitializer extends ChannelOptimizer {
|
||||
continue;
|
||||
}
|
||||
int[] currentChannelInfo =
|
||||
getCurrentChannel(
|
||||
band,
|
||||
serialNumber,
|
||||
state,
|
||||
model.latestDeviceCapabilitiesPhy
|
||||
);
|
||||
getCurrentChannel(band, serialNumber, state);
|
||||
int currentChannel = currentChannelInfo[0];
|
||||
int currentChannelWidth = currentChannelInfo[1];
|
||||
if (currentChannel == 0) {
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.channel;
|
||||
package com.facebook.openwifirrm.optimizers.channel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@@ -17,10 +17,10 @@ import java.util.TreeMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralConstants;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralConstants;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
|
||||
/**
|
||||
* Unmanaged AP aware least used channel optimizer.
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.tpc;
|
||||
package com.facebook.openwifirrm.optimizers.tpc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -18,11 +18,12 @@ import java.util.stream.Collectors;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.rrm.DeviceConfig;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||
import com.facebook.openwifirrm.DeviceConfig;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.modules.ModelerUtils;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
|
||||
/**
|
||||
* Location-based optimal TPC algorithm.
|
||||
@@ -152,7 +153,6 @@ public class LocationBasedOptimalTPC extends TPC {
|
||||
/**
|
||||
* Calculate new tx powers for the given band.
|
||||
*
|
||||
* @param band band (e.g., "2G")
|
||||
* @param channel channel
|
||||
* @param serialNumbers The serial numbers of the APs with the channel
|
||||
* @param txPowerMap this map from serial number to band to new tx power
|
||||
@@ -160,13 +160,13 @@ public class LocationBasedOptimalTPC extends TPC {
|
||||
* this method with the new tx powers.
|
||||
*/
|
||||
private void buildTxPowerMapForChannel(
|
||||
String band,
|
||||
int channel,
|
||||
List<String> serialNumbers,
|
||||
Map<String, Map<String, Integer>> txPowerMap
|
||||
) {
|
||||
int numOfAPs = 0;
|
||||
int boundary = 100;
|
||||
String band = UCentralUtils.getBandFromChannel(channel);
|
||||
Map<String, Integer> validAPs = new TreeMap<>();
|
||||
List<Double> apLocX = new ArrayList<>();
|
||||
List<Double> apLocY = new ArrayList<>();
|
||||
@@ -175,8 +175,7 @@ public class LocationBasedOptimalTPC extends TPC {
|
||||
// Filter out the invalid APs (e.g., no radio, no location data)
|
||||
// Update txPowerChoices, boundary, apLocX, apLocY for the optimization
|
||||
for (String serialNumber : serialNumbers) {
|
||||
List<? extends State> states = model.latestStates.get(serialNumber);
|
||||
State state = states.get(states.size() - 1);
|
||||
State state = model.latestState.get(serialNumber);
|
||||
|
||||
// Ignore the device if its radio is not active
|
||||
if (state.radios == null || state.radios.length == 0) {
|
||||
@@ -283,27 +282,9 @@ public class LocationBasedOptimalTPC extends TPC {
|
||||
@Override
|
||||
public Map<String, Map<String, Integer>> computeTxPowerMap() {
|
||||
Map<String, Map<String, Integer>> txPowerMap = new TreeMap<>();
|
||||
Map<String, Map<Integer, List<String>>> bandToChannelToAps =
|
||||
getApsPerChannel();
|
||||
for (
|
||||
Map.Entry<String, Map<Integer, List<String>>> bandEntry : bandToChannelToAps
|
||||
.entrySet()
|
||||
) {
|
||||
final String band = bandEntry.getKey();
|
||||
Map<Integer, List<String>> channelToAps = bandEntry.getValue();
|
||||
for (
|
||||
Map.Entry<Integer, List<String>> channelEntry : channelToAps
|
||||
.entrySet()
|
||||
) {
|
||||
final int channel = channelEntry.getKey();
|
||||
List<String> serialNumbers = channelEntry.getValue();
|
||||
buildTxPowerMapForChannel(
|
||||
band,
|
||||
channel,
|
||||
serialNumbers,
|
||||
txPowerMap
|
||||
);
|
||||
}
|
||||
Map<Integer, List<String>> apsPerChannel = getApsPerChannel();
|
||||
for (Map.Entry<Integer, List<String>> e : apsPerChannel.entrySet()) {
|
||||
buildTxPowerMapForChannel(e.getKey(), e.getValue(), txPowerMap);
|
||||
}
|
||||
return txPowerMap;
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.tpc;
|
||||
package com.facebook.openwifirrm.optimizers.tpc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -20,14 +20,11 @@ import java.util.TreeMap;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.WifiScanEntry;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.cloudsdk.StateInfo;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import com.facebook.openwifirrm.ucentral.WifiScanEntry;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
|
||||
/**
|
||||
* Measurement-based AP-AP TPC algorithm.
|
||||
@@ -157,11 +154,8 @@ public class MeasurementBasedApApTPC extends TPC {
|
||||
*/
|
||||
protected static Set<String> getManagedBSSIDs(DataModel model) {
|
||||
Set<String> managedBSSIDs = new HashSet<>();
|
||||
for (
|
||||
Map.Entry<String, List<StateInfo>> e : model.latestStates.entrySet()
|
||||
) {
|
||||
List<StateInfo> states = e.getValue();
|
||||
State state = states.get(states.size() - 1);
|
||||
for (Map.Entry<String, State> e : model.latestState.entrySet()) {
|
||||
State state = e.getValue();
|
||||
if (state.interfaces == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -222,14 +216,7 @@ public class MeasurementBasedApApTPC extends TPC {
|
||||
|
||||
// At a given AP, if we receive a signal from ap_2, then it gets added to the rssi list for ap_2
|
||||
latestScan.stream()
|
||||
.filter((entry) -> {
|
||||
if (!managedBSSIDs.contains(entry.bssid)) {
|
||||
return false;
|
||||
}
|
||||
String entryBand = UCentralUtils
|
||||
.freqToBand(entry.frequency);
|
||||
return entryBand != null && entryBand.equals(band);
|
||||
})
|
||||
.filter(entry -> (managedBSSIDs.contains(entry.bssid) && UCentralUtils.isChannelInBand(entry.channel, band)))
|
||||
.forEach(
|
||||
entry -> {
|
||||
bssidToRssiValues.get(entry.bssid).add(entry.signal);
|
||||
@@ -308,25 +295,23 @@ public class MeasurementBasedApApTPC extends TPC {
|
||||
/**
|
||||
* Calculate new tx powers for the given channel on the given APs .
|
||||
*
|
||||
* @param band band (e.g., "2G")
|
||||
* @param channel channel
|
||||
* @param serialNumbers the serial numbers of the APs with the channel
|
||||
* @param txPowerMap this maps from serial number to band to new tx power (dBm)
|
||||
* and is updated by this method with the new tx powers.
|
||||
*/
|
||||
protected void buildTxPowerMapForChannel(
|
||||
String band,
|
||||
int channel,
|
||||
List<String> serialNumbers,
|
||||
Map<String, Map<String, Integer>> txPowerMap
|
||||
) {
|
||||
String band = UCentralUtils.getBandFromChannel(channel);
|
||||
Set<String> managedBSSIDs = getManagedBSSIDs(model);
|
||||
Map<String, List<Integer>> bssidToRssiValues =
|
||||
buildRssiMap(managedBSSIDs, model.latestWifiScans, band);
|
||||
logger.debug("Starting TPC for the {} band", band);
|
||||
for (String serialNumber : serialNumbers) {
|
||||
List<? extends State> states = model.latestStates.get(serialNumber);
|
||||
State state = states.get(states.size() - 1);
|
||||
State state = model.latestState.get(serialNumber);
|
||||
if (
|
||||
state == null || state.radios == null ||
|
||||
state.radios.length == 0
|
||||
@@ -376,17 +361,9 @@ public class MeasurementBasedApApTPC extends TPC {
|
||||
State.Radio radio = state.radios[idx];
|
||||
|
||||
// this specific SSID is not on the band of interest
|
||||
Map<String, Capabilities.Phy> capabilitiesPhy =
|
||||
model.latestDeviceCapabilitiesPhy
|
||||
.get(serialNumber);
|
||||
if (capabilitiesPhy == null) {
|
||||
continue;
|
||||
}
|
||||
final String radioBand = ModelerUtils.getBand(
|
||||
radio,
|
||||
capabilitiesPhy
|
||||
);
|
||||
if (radioBand == null || !radioBand.equals(band)) {
|
||||
if (
|
||||
!UCentralUtils.isChannelInBand(radio.channel, band)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -430,27 +407,9 @@ public class MeasurementBasedApApTPC extends TPC {
|
||||
@Override
|
||||
public Map<String, Map<String, Integer>> computeTxPowerMap() {
|
||||
Map<String, Map<String, Integer>> txPowerMap = new TreeMap<>();
|
||||
Map<String, Map<Integer, List<String>>> bandToChannelToAps =
|
||||
getApsPerChannel();
|
||||
for (
|
||||
Map.Entry<String, Map<Integer, List<String>>> bandEntry : bandToChannelToAps
|
||||
.entrySet()
|
||||
) {
|
||||
final String band = bandEntry.getKey();
|
||||
Map<Integer, List<String>> channelToAps = bandEntry.getValue();
|
||||
for (
|
||||
Map.Entry<Integer, List<String>> channelEntry : channelToAps
|
||||
.entrySet()
|
||||
) {
|
||||
final int channel = channelEntry.getKey();
|
||||
List<String> serialNumbers = channelEntry.getValue();
|
||||
buildTxPowerMapForChannel(
|
||||
band,
|
||||
channel,
|
||||
serialNumbers,
|
||||
txPowerMap
|
||||
);
|
||||
}
|
||||
Map<Integer, List<String>> apsPerChannel = getApsPerChannel();
|
||||
for (Map.Entry<Integer, List<String>> e : apsPerChannel.entrySet()) {
|
||||
buildTxPowerMapForChannel(e.getKey(), e.getValue(), txPowerMap);
|
||||
}
|
||||
return txPowerMap;
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
package com.facebook.openwifi.rrm.optimizers.tpc;
|
||||
package com.facebook.openwifirrm.optimizers.tpc;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -15,16 +15,13 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.facebook.openwifirrm.ucentral.UCentralUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.Capabilities;
|
||||
import com.facebook.openwifi.cloudsdk.UCentralUtils;
|
||||
import com.facebook.openwifi.cloudsdk.StateInfo;
|
||||
import com.facebook.openwifi.cloudsdk.models.ap.State;
|
||||
import com.facebook.openwifi.rrm.DeviceDataManager;
|
||||
import com.facebook.openwifi.rrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifi.rrm.modules.ModelerUtils;
|
||||
import com.facebook.openwifirrm.DeviceDataManager;
|
||||
import com.facebook.openwifirrm.modules.Modeler.DataModel;
|
||||
import com.facebook.openwifirrm.ucentral.models.State;
|
||||
|
||||
/**
|
||||
* Measurement-based AP-client algorithm.
|
||||
@@ -294,12 +291,10 @@ public class MeasurementBasedApClientTPC extends TPC {
|
||||
public Map<String, Map<String, Integer>> computeTxPowerMap() {
|
||||
Map<String, Map<String, Integer>> txPowerMap = new TreeMap<>();
|
||||
|
||||
for (
|
||||
Map.Entry<String, List<StateInfo>> e : model.latestStates.entrySet()
|
||||
) {
|
||||
for (Map.Entry<String, State> e : model.latestState.entrySet()) {
|
||||
String serialNumber = e.getKey();
|
||||
List<StateInfo> states = e.getValue();
|
||||
State state = states.get(states.size() - 1);
|
||||
State state = e.getValue();
|
||||
|
||||
if (state.radios == null || state.radios.length == 0) {
|
||||
logger.debug(
|
||||
"Device {}: No radios found, skipping...",
|
||||
@@ -310,16 +305,8 @@ public class MeasurementBasedApClientTPC extends TPC {
|
||||
|
||||
Map<String, Integer> radioMap = new TreeMap<>();
|
||||
for (State.Radio radio : state.radios) {
|
||||
Map<String, Capabilities.Phy> capabilityPhy =
|
||||
model.latestDeviceCapabilitiesPhy
|
||||
.get(serialNumber);
|
||||
if (capabilityPhy == null) {
|
||||
continue;
|
||||
}
|
||||
final String band = ModelerUtils.getBand(
|
||||
radio,
|
||||
capabilityPhy
|
||||
);
|
||||
int currentChannel = radio.channel;
|
||||
String band = UCentralUtils.getBandFromChannel(currentChannel);
|
||||
if (band == null) {
|
||||
continue;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user