From ba627ce3d6cc65d04b0c36aa70db2d2515d6f99e Mon Sep 17 00:00:00 2001 From: Hanshuo Zeng Date: Wed, 4 Mar 2020 21:32:28 +0800 Subject: [PATCH] basically functional --- .gitignore | 3 + adminhelp.txt | 250 ++++ cert.txt | 32 + fsd.conf | 103 ++ motd.txt | 6 + pom.xml | 35 +- src/main/java/com/hans0924/fsd/Fsd.java | 448 ++++++- .../java/com/hans0924/fsd/FsdLauncher.java | 37 +- .../fsd/constants/CertificateConstants.java | 14 + .../fsd/constants/ClientConstants.java | 62 + .../com/hans0924/fsd/constants/FsdPath.java | 21 + .../fsd/constants/GlobalConstants.java | 57 + .../hans0924/fsd/constants/ManageVarType.java | 2 +- .../hans0924/fsd/constants/MetarSource.java | 15 + .../fsd/constants/NetworkConstants.java | 43 + .../fsd/constants/ProtocolConstants.java | 126 ++ .../fsd/constants/ServerConstants.java | 13 + .../fsd/constants/SupportConstants.java | 12 + .../fsd/constants/SystemConstants.java | 96 ++ .../fsd/constants/WeatherConstants.java | 18 + .../java/com/hans0924/fsd/manager/Manage.java | 13 +- .../com/hans0924/fsd/manager/ManageVar.java | 5 +- .../hans0924/fsd/manager/ManageVarValue.java | 2 +- .../com/hans0924/fsd/model/Certificate.java | 125 ++ .../java/com/hans0924/fsd/model/Client.java | 350 ++++++ .../com/hans0924/fsd/model/Flightplan.java | 181 +++ .../java/com/hans0924/fsd/model/Server.java | 239 ++++ .../java/com/hans0924/fsd/process/PMan.java | 38 +- .../com/hans0924/fsd/process/Process.java | 29 +- .../fsd/process/config/ConfigEntry.java | 12 +- .../fsd/process/config/ConfigGroup.java | 6 +- .../fsd/process/config/ConfigManager.java | 20 +- .../fsd/process/metar/MetarManage.java | 615 +++++++++ .../com/hans0924/fsd/process/metar/Mmq.java | 69 + .../hans0924/fsd/process/metar/Station.java | 29 + .../hans0924/fsd/process/network/Allow.java | 17 + .../fsd/process/network/ClientInterface.java | 240 ++++ .../hans0924/fsd/process/network/Guard.java | 37 + .../fsd/process/network/ServerInterface.java | 521 ++++++++ .../fsd/process/network/SystemInterface.java | 98 ++ .../fsd/process/network/TcpInterface.java | 525 ++++++++ .../com/hans0924/fsd/support/Support.java | 146 +++ .../com/hans0924/fsd/user/AbstractUser.java | 448 +++++++ .../com/hans0924/fsd/user/ClientUser.java | 440 +++++++ .../com/hans0924/fsd/user/ServerUser.java | 726 +++++++++++ .../com/hans0924/fsd/user/SystemUser.java | 1114 +++++++++++++++++ .../com/hans0924/fsd/weather/CloudLayer.java | 61 + .../com/hans0924/fsd/weather/TempLayer.java | 33 + .../com/hans0924/fsd/weather/WProfile.java | 702 +++++++++++ .../com/hans0924/fsd/weather/Weather.java | 22 + .../com/hans0924/fsd/weather/WindLayer.java | 70 ++ src/main/resources/log4j.properties | 37 +- 52 files changed, 8244 insertions(+), 119 deletions(-) create mode 100644 adminhelp.txt create mode 100644 cert.txt create mode 100644 fsd.conf create mode 100644 motd.txt create mode 100644 src/main/java/com/hans0924/fsd/constants/CertificateConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/ClientConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/FsdPath.java create mode 100644 src/main/java/com/hans0924/fsd/constants/GlobalConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/MetarSource.java create mode 100644 src/main/java/com/hans0924/fsd/constants/NetworkConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/ProtocolConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/ServerConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/SupportConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/SystemConstants.java create mode 100644 src/main/java/com/hans0924/fsd/constants/WeatherConstants.java create mode 100644 src/main/java/com/hans0924/fsd/model/Certificate.java create mode 100644 src/main/java/com/hans0924/fsd/model/Client.java create mode 100644 src/main/java/com/hans0924/fsd/model/Flightplan.java create mode 100644 src/main/java/com/hans0924/fsd/model/Server.java create mode 100644 src/main/java/com/hans0924/fsd/process/metar/MetarManage.java create mode 100644 src/main/java/com/hans0924/fsd/process/metar/Mmq.java create mode 100644 src/main/java/com/hans0924/fsd/process/metar/Station.java create mode 100644 src/main/java/com/hans0924/fsd/process/network/Allow.java create mode 100644 src/main/java/com/hans0924/fsd/process/network/ClientInterface.java create mode 100644 src/main/java/com/hans0924/fsd/process/network/Guard.java create mode 100644 src/main/java/com/hans0924/fsd/process/network/ServerInterface.java create mode 100644 src/main/java/com/hans0924/fsd/process/network/SystemInterface.java create mode 100644 src/main/java/com/hans0924/fsd/process/network/TcpInterface.java create mode 100644 src/main/java/com/hans0924/fsd/support/Support.java create mode 100644 src/main/java/com/hans0924/fsd/user/AbstractUser.java create mode 100644 src/main/java/com/hans0924/fsd/user/ClientUser.java create mode 100644 src/main/java/com/hans0924/fsd/user/ServerUser.java create mode 100644 src/main/java/com/hans0924/fsd/user/SystemUser.java create mode 100644 src/main/java/com/hans0924/fsd/weather/CloudLayer.java create mode 100644 src/main/java/com/hans0924/fsd/weather/TempLayer.java create mode 100644 src/main/java/com/hans0924/fsd/weather/WProfile.java create mode 100644 src/main/java/com/hans0924/fsd/weather/Weather.java create mode 100644 src/main/java/com/hans0924/fsd/weather/WindLayer.java diff --git a/.gitignore b/.gitignore index d51e236..e00e079 100644 --- a/.gitignore +++ b/.gitignore @@ -87,6 +87,7 @@ cmake-build-*/ # IntelliJ out/ +.idea/* # mpeltonen/sbt-idea plugin .idea_modules/ @@ -108,3 +109,5 @@ fabric.properties # Android studio 3.1+ serialized cache file .idea/caches/build_file_checksums.ser + +*.iml diff --git a/adminhelp.txt b/adminhelp.txt new file mode 100644 index 0000000..46439b9 --- /dev/null +++ b/adminhelp.txt @@ -0,0 +1,250 @@ +%topic global:MAIN +FSD server version 1.0 + +Try 'help ' to get help on a topic. A list of topics is available +with the 'help topics' command. + +Commands available: + +%topic connect:CONNECT TO A SERVER +Syntax: + connect [ []] + +Will connect the local server to the specified server. Without arguments, +the command will show the current connections. For every connection +some information is shown: +fd : The file descriptor that can be used for "disconnect" +Out-Q : The amount of bytes waiting to be sent on this connection. +In-Q : The amount of bytes waiting to be processed from this connection. +Feed : The throughput of this connection is bytes/second (last 10 min.). +Peer : The peer of this connection. + +%topic disconnect:DISCONNECT FROM A SERVER +Syntax: + disconnect + +Will disconnect the indicated server connection. Use 'connect' to get a list +of active server connections. + +%topic servers:SERVER OVERVIEW(1) +Syntax: + servers [] + +This command will show an overview of the servers in the network. +When the optional , is specified, only servers with in their +ident code or in their name will be shown. + +The following fields are shown: +ID : The server ID. +Host : The hostname of the server. +Email : The maintainer's email address. +Fl : The flags associated with the server. M = Metar capable server, + S = silent server, not for clients. +Hops : The amount of hops needed to reach the host. +Lag : The amount of seconds currently needed for a round trip. +Name : The description of a server. + + +%topic servers2:SERVER OVERVIEW(2) +Syntax: + servers2 [] + +This command will show an overview of the servers in the network. +When the optional , is specified, only servers with in their +ident code or in their name will be shown. + +The following fields are shown: +ID : The server ID. +Version : The server version. +Email : The maintainer's email address. +Location: The location of the server + + +%topic ping:PING A NETWORK SERVER +Syntax: + ping + +This command sends a PING request to the specified server. When this servers +receives the ping, it will send it back to the originating host. +When a reply has been received, the reply time will be shown. + +%topic route:SHOW ROUTING TABLES +Syntax + route [] + +Will show the internal routing table to all servers, or only to the servers +which have the in their ident. + +The routing table is configured automatically and does not need human +intervention. + +%topic say:SEND A MESSAGE TO A CLIENT +Syntax + say + +This command sends a message to a client. + can be either: +- A broadcast address (* = all clients, *P = all pilots, *A = all ATC) +- A callsign, like "KL348" +- A frequency address, like "@11240" + +%topic clients:CLIENTS OVERVIEW +Syntax + clients [] + +This command will show a list of all clients matching the given callsign. +If only one client matches the callsign, a detailed overview of this +client is shown. + +%topic distance:CALCULATE DISTANCE +Syntax + distance + +This command can be used to calculate the distance between two clients. +It only works if both client have reported their positions. The distance +will be shown in Nautical Miles. + +%topic cert:CERTIFICATE CONTROL +Syntax: + cert + cert add + cert delete + cert modify + +This command will add/delete or modify the certificate in the network. + can either be 'P' for a Pilot certificate or 'A' for an ATC certificate. +The maximum level of this certificate is . + can be one of: + + Observer Instructor1 + Student1 Instructor2 + Student2 Instructor3 + Student3 Supervisor + Controller1 Administrator + Controller2 + Controller3 + +%topic cert:SHOW CERTIFICATES +Syntax: + cert [] + +This command will show an overview of the current certificates. If +is specified, only certificates which have in their code will be +shown. + +%topic time:SHOW CURRENT TIME +Syntax: + time + +This command gives the current time. The local time will be shown, as well +as the UTC time. + +%topic range:SHOW COM RANGE +Syntax: + range + +This command shows the communication range of the client with +in NM. + +%topic weather:SEND WEATHER REQUEST +Syntax: + weather + +This command will send out a weather request to the nearest METAR capable +server. If no such server exists in the network, the command will fail. + is the name of the requested weather profile. Usually +this is an ICAO station code like KJFK, but it can also be a hand made +profile. + +Note that because the server has to send out a request, the prompt will +be returned immediately. When a response has been received, it will be +displayed. + +%topic metar:SEND METAR REQUEST +Syntax: + metar + +This command will send out a weather request to the nearest METAR capable +server. If no such server exists in the network, the command will fail. + is the name of the requested weather profile. Usually +this is an ICAO station code like KJFK, but it can also be a hand made +profile. + +Note that because the server has to send out a request, the prompt will +be returned immediately. When a response has been received, it will be +displayed. + + +%topic log:ACCESS SYSTEM LOGS +Syntax: + log (show|delete) [] + +This command will show or delete log messages. If is specified, +only messages will be shown/deleted. + +The importance of the log messages can be specified with the +option. can be one if: + +0 Critical messages 3 Warning messages +1 Alert messages 4 Info messages +2 Error messages 5 Debug messages + + +%topic stat:SHOW SERVER STATISTICS +Syntax: + stat [] + +This command will show the server statistical entries. When is +specified, only the entries with in their name will be shown. + +%topic wall:BROADCAST MESSAGE +Syntax: + wall + +This command will send the text to all clients connected to the +local server. + + +%topic delguard:DELETE GUARD CONNECTIONS +Syntax: + delguard + +This command will delete all guard connections (pending connections). + + +%topic wp:CONTROL WEATHER PROFILES +Syntax: + wp show [] + wp create + wp delete + wp activate + wp set = + +Examples: + wp create eham + wp set eham visibility 6 + wp set eham pressure 2110 + wp set eham clouds.1.floor 5000 + wp set eham clouds.1.ceiling 9000 + wp set eham clouds.1.coverage 6 + wp set eham winds.1.floor 0 + wp set eham winds.1.ceiling 250 + wp set eham winds.1.speed 11 + wp set eham winds.1.direction 240 + wp set eham temperature.1.temperature 12 + wp activate eham + +%topic pos:SET LOCAL POSITION +Syntax: + pos + +This command is used to set the local position for the weather system. +The command 'weather' uses this position to calculate the upper winds. + +%topic refreshmetar:DOWNLOAD LATEST METAR +Syntax: + refreshmetar + +This command downloads the latest weather reports and places these in the +file 'metar.txt'. This command is not available if the metar source was set +to 'network'. diff --git a/cert.txt b/cert.txt new file mode 100644 index 0000000..1f1779d --- /dev/null +++ b/cert.txt @@ -0,0 +1,32 @@ +;ID Password Rating +; +; 1 Observer +; 2 Student1 +; 3 Student2 +; 4 Student3 +; 5 Controller1 +; 6 Controller2 +; 7 Controller3 +; 8 Instructor1 +; 9 Instructor2 +; 10 Instructor3 +; 11 Supervisor +; 12 Administrator +; +; +; adminrank : de 0 a 12 (0 account desactiv�) +; +111111 password 12 +222222 password 12 +333333 password 12 +444444 password 12 +555555 password 12 +666666 password 12 +777777 password 12 +CCA001 password 12 + +888888 password 12 +999999 password 12 +122222 122222 12 + + diff --git a/fsd.conf b/fsd.conf new file mode 100644 index 0000000..46aa6ef --- /dev/null +++ b/fsd.conf @@ -0,0 +1,103 @@ +############################################################################### +# Sample configuration file for FSD +# + +############################################################################### +# The system group holds information about your server. +# +# clientport & serverport: +# The ports where clients and servers will connect to +# systemport: +# The port where the system management services will be located +# ident: +# The ident of your server. This ident has to be unique. It is used to +# identify your server in the global network. It should not contain spaces. +# Please use a small ident code, it will be sent in every packet +# email: +# The email address that can be used to mail the maintainer of this server. +# name: +# The name(description) of your server. It may contain spaces. +# hostname: +# The hostname that can be used to reach this server. +# password: +# The password you need to specify before you can execute privileged +# commands on the system port. +# location: +# The (physical) location of the server in the world, and the internet. +# Example: 'Delft, The netherlands (SURFnet)' +# mode: +# The mode of the server; can be 'normal' or 'silent'. Use 'normal' +# for normal operation. +# certificates: +# The file to read certificates from. +# maxclients: +# The maximum amount of clients this server will allow +# whazzup: +# The file to put WhazzUp data in. + +[system] +clientport=6809 +serverport=3011 +systemport=3010 +ident=FSD +email=nobody@nowhere.com +name=FSFDT FSD Unix Windows server +hostname=localhost +password=disable +location=Nowhere +mode=normal +certificates=cert.txt +maxclients=200 +whazzup=whazzup.txt + +############################################################################### +# The connections group holds information about the (server) connections this +# server wil establish and accept. +# +# connectto: +# Contains the hostname and port numbers of the servers to connect to. +# Multiple servers can be used here. For example: +# connectto=server.hinttech.com:5001,server.flightsim.com:4006 +# allowfrom: +# Contains the IP addresses from which servers can connect to this server. +# Multiple IP addresses can be used, separated by commas. For example: +# allowfrom=server.flightsim.com,atc.aol.com + +[connections] +#connectto= +#allowfrom= + +############################################################################### +# The hosts group contains a list of hosts that are trusted for some activity. +# There are 2 entries: +# certificates : contains a list of server ID's that are allowed to change +# certificates +# weather : contains a list of server ID's that are allowed to change +# weather profiles +#[hosts] +#certificates= +#weather= + +############################################################################### +# This group controls the weather system. +# The 'source' variable determines the source of the METAR data. +# For normal operation, set this to 'network'. +# There are 3 possible values here: +# 'file' : Read the METAR data from the file 'metar.txt' +# and allow weather requests from other servers. +# 'download' : Like 'file', but refresh metar.txt every hour by downloading +# the latest weather observations from metlab. The server has +# to be connected to the internet for this to work. +# 'network' : Relay weather requests to the closest METAR capable server. +# +# 'server','dir' and 'ftpmode' are only used when the METAR source is 'download'. These +# fields determine the host name and the directory from where metar data is +# read. FSD uses the FTP protocol to get the data. ftpmode can have the value 'active' +# 'passive' that are Active and Passive FTP protocol mode, default is 'passive'. +# If you use FSD on a computer having a private IP, only use passive mode. + +[weather] +source=download +server=weather.noaa.gov +dir=data/observations/metar/cycles/ +ftpmode=passive diff --git a/motd.txt b/motd.txt new file mode 100644 index 0000000..a22748f --- /dev/null +++ b/motd.txt @@ -0,0 +1,6 @@ +Welcome to the FSD server for Unix. +This Unix version has a united source code with the Windows version +This Unix version is now able to generate whazzup file for servinfo + +Enjoy +The FSFDT Team diff --git a/pom.xml b/pom.xml index 4dde0c8..d7a66c1 100644 --- a/pom.xml +++ b/pom.xml @@ -9,8 +9,12 @@ 1.0-SNAPSHOT + UTF-8 + 13 + 13 + 1.7.30 - 6.0.0 + 3.9 @@ -20,9 +24,32 @@ ${slf4j.version} - org.javolution - javolution-core-java - ${javolution.version} + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + org.junit.jupiter + junit-jupiter-api + 5.6.0 + test + + + + + maven-compiler-plugin + 3.8.0 + + ${maven.compiler.source} + ${maven.compiler.target} + ${project.build.sourceEncoding} + + -parameters + + + + + \ No newline at end of file diff --git a/src/main/java/com/hans0924/fsd/Fsd.java b/src/main/java/com/hans0924/fsd/Fsd.java index c171424..44b1c34 100644 --- a/src/main/java/com/hans0924/fsd/Fsd.java +++ b/src/main/java/com/hans0924/fsd/Fsd.java @@ -1,20 +1,55 @@ + package com.hans0924.fsd; +import com.hans0924.fsd.constants.*; import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.model.Certificate; +import com.hans0924.fsd.model.Client; +import com.hans0924.fsd.model.Flightplan; +import com.hans0924.fsd.model.Server; import com.hans0924.fsd.process.PMan; +import com.hans0924.fsd.process.config.ConfigEntry; +import com.hans0924.fsd.process.config.ConfigGroup; import com.hans0924.fsd.process.config.ConfigManager; +import com.hans0924.fsd.process.metar.MetarManage; +import com.hans0924.fsd.process.network.ClientInterface; +import com.hans0924.fsd.process.network.ServerInterface; +import com.hans0924.fsd.process.network.SystemInterface; +import com.hans0924.fsd.support.Support; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class Fsd { + private final static Logger LOGGER = LoggerFactory.getLogger(Fsd.class); public static ConfigManager configManager; + public static ServerInterface serverInterface; + + public static SystemInterface systemInterface; + + public static ClientInterface clientInterface; private int clientPort; @@ -28,11 +63,19 @@ public class Fsd { private String whazzupFile; - private int timer; - private int prevNotify; - private int prevLagCheck; - private int certFileStat; - private int prevCertCheck; + private long timer; + + private long prevNotify; + + private long prevLagCheck; + + private long certFileStat; + + private int fileOpen; + + private long prevCertCheck; + + private long prevWhazzup; public Fsd(String configFile) { LOGGER.info("Booting Server"); @@ -43,5 +86,398 @@ public class Fsd { configManager = new ConfigManager(configFile); pManager.registerProcess(configManager); + + /* Create the METAR manager */ + MetarManage.metarManager = new MetarManage(); + pManager.registerProcess(MetarManage.metarManager); + + /* Read the system configuration */ + configure(); + + /* Create the management variables */ + createManageVars(); + + /* Create the server and the client interfaces */ + createInterfaces(); + + /* Connect to the other server */ + makeConnections(); + + LOGGER.info("We are up"); + prevNotify = prevLagCheck = timer = Support.mtime(); + + prevWhazzup = Support.mtime(); + fileOpen = 0; + } + + public void close() { + + } + + /* Here we do timeout checks. This function is triggered every second to + reduce the load on the server */ + public void doChecks() { + long now = Support.mtime(); + if ((now - prevNotify) > GlobalConstants.NOTIFY_CHECK) { + ConfigGroup sgroup = configManager.getGroup("system"); + if (sgroup != null && sgroup.isChanged()) + configMyServer(); + serverInterface.sendServerNotify("*", Server.myServer, null); + prevNotify = now; + } + if ((now - prevLagCheck) > GlobalConstants.LAG_CHECK) { + String data = String.format("-1 %d", Support.mtime()); + serverInterface.sendPing("*", data); + prevLagCheck = now; + } + if ((now - prevCertCheck) > GlobalConstants.CERT_FILE_CHECK) { + ConfigEntry entry; + ConfigGroup sysgroup = configManager.getGroup("system"); + if (sysgroup != null) { + if ((entry = sysgroup.getEntry("certificates")) != null) { + certFile = entry.getData().toUpperCase(); + Path file = Paths.get(certFile); + if (Files.exists(file)) { + try { + BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class); + long fileLastModified = attr.lastModifiedTime().toMillis(); + prevCertCheck = now; + if (certFileStat != fileLastModified) { + certFileStat = fileLastModified; + readCert(); + } + } catch (IOException e) { + LOGGER.warn("Read cert file info failed.", e); + } + } + } + } + } +// WhazzUp Start + if ((now - prevWhazzup) >= GlobalConstants.WHAZZUP_CHECK) { + ConfigEntry entry; + ConfigGroup sysgroup = configManager.getGroup("system"); + if (sysgroup != null) { + if ((entry = sysgroup.getEntry("whazzup")) != null) { + whazzupFile = entry.getData(); + String whazzupTemp = String.format("%s%s", whazzupFile, ".tmp"); + prevWhazzup = now; + if (fileOpen == 0) { + try (FileOutputStream wzfile = new FileOutputStream(whazzupTemp)) { + fileOpen = 1; + wzfile.write(String.format("%s%s\n", "![DateStamp]", Support.sprintGmtDate(now)).getBytes()); + wzfile.write(String.format("%s\n", "!GENERAL").getBytes()); + wzfile.write(String.format("%s = %d\n", "VERSION", 1).getBytes()); + wzfile.write(String.format("%s = %d\n", "RELOAD", 1).getBytes()); + wzfile.write(String.format("%s = %s\n", "UPDATE", Support.sprintGmt(now)).getBytes()); + wzfile.write(String.format("%s = %d\n", "CONNECTED CLIENTS", Client.clients.size()).getBytes()); + wzfile.write(String.format("%s = %d\n", "CONNECTED SERVERS", Server.servers.size()).getBytes()); + wzfile.write(String.format("%s\n", "!CLIENTS").getBytes()); + String dataSeg1, dataSeg2, dataSeg3, dataSeg4, dataSeg5, dataSeg6, dataSeg7; + for (Client tempClient : Client.clients) { + dataSeg1 = String.format("%s:%s:%s:%s", tempClient.getCallsign(), tempClient.getCid(), + tempClient.getRealName(), tempClient.getType() == ClientConstants.CLIENT_ATC ? "ATC" : "PILOT"); + if (tempClient.getFrequency() != 0 && tempClient.getFrequency() < 100_000 && tempClient != null) { + dataSeg2 = String.format("1%02d.%03d", tempClient.getFrequency() / 1000, tempClient.getFrequency() % 1000); + } else { + dataSeg2 = ""; + } + + Flightplan tempFlightplan = tempClient.getPlan(); + if (tempClient.getLat() != 0 && tempClient.getAltitude() < 100_000 && tempClient.getLon() != 0) { + dataSeg3 = String.format("%f:%f:%d:%d", tempClient.getLat(), tempClient.getLon(), tempClient.getAltitude(), tempClient.getGroundSpeed()); + } else { + dataSeg3 = ":::"; + } + + if (tempFlightplan != null) { + dataSeg4 = String.format("%s:%d:%s:%s:%s", tempFlightplan.getAircraft(), + tempFlightplan.getTasCruise(), tempFlightplan.getDepAirport(), + tempFlightplan.getAlt(), tempFlightplan.getDestAirport()); + } else { + dataSeg4 = "::::"; + } + dataSeg5 = String.format("%s:%s:%d:%d:%d:%d", tempClient.getLocation().getIdent(), tempClient.getProtocol(), tempClient.getRating(), tempClient.getTransponder(), tempClient.getFacilityType(), tempClient.getVisualRange()); + + if (tempFlightplan != null) { + dataSeg6 = String.format("%d:%c:%d:%d:%d:%d:%d:%d:%s:%s:%s", tempFlightplan.getRevision(), + tempFlightplan.getType(), tempFlightplan.getDepTime(), tempFlightplan.getActDepTime(), + tempFlightplan.getHrsEnroute(), tempFlightplan.getMinEnroute(), tempFlightplan.getHrsFuel(), + tempFlightplan.getMinFuel(), tempFlightplan.getAltAirport(), tempFlightplan.getRemarks(), tempFlightplan.getRoute()); + } else { + dataSeg6 = "::::::::::"; + } + + dataSeg7 = String.format("::::::%s", Support.sprintGmt(tempClient.getStartTime())); + wzfile.write(String.format("%s:%s:%s:%s:%s:%s:%s\n", dataSeg1, dataSeg2, dataSeg3, dataSeg4, dataSeg5, dataSeg6, dataSeg7).getBytes()); + } + wzfile.write("!SERVERS\n".getBytes()); + for (Server tempServer : Server.servers) { + if ("n/a".equals(tempServer.getHostName())) { + String dataLine = String.format("%s:%s:%s:%s:%d", tempServer.getIdent(), tempServer.getHostName(), tempServer.getLocation(), tempServer.getName(), (tempServer.getFlags() & ServerConstants.SERVER_SILENT) != 0 ? 0 : 1); + wzfile.write(String.format("%s\n", dataLine).getBytes()); + } + } + } catch (IOException e) { + LOGGER.warn("Open whazzup file failed.", e); + } + File realFile = new File(whazzupFile); + realFile.delete(); + File tempFile = new File(whazzupTemp); + tempFile.renameTo(realFile); + fileOpen = 0; + } else { + fileOpen = 0; + } + } + } + } +// WhazzUp End + for (Server tempServer : Server.servers) { + if (now - tempServer.getAlive() > GlobalConstants.SERVER_TIMEOUT && tempServer != Server.myServer) { + tempServer.close(); + } + } + + /* Check for client timeouts. We should not drop clients if we are in + silent mode; If we are in silent mode, we won't receive updates, so + every client would timeout. When we are a silent server, the limit + will be SILENTCLIENTTIMEOUT, which is about 10 hours */ + + int limit = (Server.myServer.getFlags() & ServerConstants.SERVER_SILENT) != 0 ? GlobalConstants.SILENT_CLIENT_TIMEOUT : GlobalConstants.CLIENT_TIMEOUT; + for (Client client : Client.clients) { + if (client.getLocation() != Server.myServer) { + if ((now - client.getAlive()) > limit) { + client.close(); + } + } + } + + } + + public void run() { + pManager.run(); + if (timer != Support.mtime()) { + timer = Support.mtime(); + doChecks(); + } + } + + private void configMyServer() { + int mode = 0; + String serverIdent = null, serverName = null; + String serverMail = null, serverHostName = null; + String serverLocation = null; + ConfigEntry entry; + ConfigGroup system = configManager.getGroup("system"); + if (system != null) { + system.setChanged(false); + if ((entry = system.getEntry("ident")) != null) { + serverIdent = entry.getData(); + } + if ((entry = system.getEntry("name")) != null) { + serverName = entry.getData(); + } + if ((entry = system.getEntry("email")) != null) { + serverMail = entry.getData(); + } + if ((entry = system.getEntry("hostname")) != null) { + serverHostName = entry.getData(); + } + if ((entry = system.getEntry("location")) != null) { + serverLocation = entry.getData(); + } + if ((entry = system.getEntry("mode")) != null) { + if ("silent".equalsIgnoreCase(entry.getData())) { + mode = ServerConstants.SERVER_SILENT; + } + } + } + if (serverIdent == null) { + serverIdent = String.format("%d", ProcessHandle.current().pid()); + LOGGER.error("No serverident specified"); + } + if (serverMail == null) { + serverMail = ""; + LOGGER.error("No server mail address specified"); + } + if (serverHostName == null) { + try { + serverHostName = InetAddress.getLocalHost().getHostName(); + LOGGER.error("No server host name specified"); + } catch (UnknownHostException e) { + LOGGER.error("Get localhost name failed."); + } + } + if (serverName == null) { + LOGGER.error("No servername specified"); + serverName = serverHostName; + } + if (serverLocation == null) { + LOGGER.error("No serverlocation specified"); + serverLocation = ""; + } + int flags = mode; + if (MetarManage.metarManager.getSource() == MetarSource.SOURCE_NETWORK) { + flags |= ServerConstants.SERVER_METAR; + } + if (Server.myServer != null) { + Server.myServer.configure(serverName, serverMail, serverHostName, GlobalConstants.VERSION, serverLocation); + } else { + Server.myServer = new Server(serverIdent, serverName, serverMail, serverHostName, GlobalConstants.VERSION, flags, serverLocation); + } + } + + private void configure() { + clientPort = 6809; + serverPort = 3011; + systemPort = 3012; + ConfigEntry entry; + ConfigGroup system = configManager.getGroup("system"); + if (system != null) { + if ((entry = system.getEntry("clientport")) != null) { + clientPort = entry.getInt(); + } + if ((entry = system.getEntry("serverport")) != null) { + serverPort = entry.getInt(); + } + if ((entry = system.getEntry("systemport")) != null) { + systemPort = entry.getInt(); + } + if ((entry = system.getEntry("certificates")) != null) { + certFile = entry.getData(); + } + if ((entry = system.getEntry("whazzup")) != null) { + whazzupFile = entry.getData(); + } + } + + configMyServer(); + readCert(); + } + + void handleCidLine(String line) { + Certificate tempcert; + int mode, level; + String cid, pwd; + List array = new ArrayList<>(); + if (StringUtils.isEmpty(line)) { + return; + } + if (line.charAt(0) == ';' || line.charAt(0) == '#') return; + if (Support.breakArgs(line, array, 4) < 3) return; + cid = array.get(0); + level = NumberUtils.toInt(array.get(2)); + pwd = array.get(1); + tempcert = Certificate.getCert(cid); + if (tempcert == null) { + tempcert = new Certificate(cid, pwd, level, Support.mgmtime(), Server.myServer.getIdent()); + mode = ProtocolConstants.CERT_ADD; + } else { + tempcert.setLiveCheck(1); + if (tempcert.getPassword().equalsIgnoreCase(pwd) && level == tempcert.getLevel()) + return; + tempcert.configure(pwd, level, Support.mgmtime(), Server.myServer.getIdent()); + mode = ProtocolConstants.CERT_MODIFY; + } + if (serverInterface != null) serverInterface.sendCert("*", mode, tempcert, null); + } + + void readCert() { + if (StringUtils.isBlank(certFile)) return; + + List lines; + File file = new File(certFile); + try { + lines = Files.readAllLines(Paths.get(certFile), StandardCharsets.UTF_8); + } catch (IOException e) { + LOGGER.error(String.format("Could not open certificate file '%s'", + file.getAbsolutePath()), e); + return; + } + + for (Certificate temp : Certificate.certs) { + temp.setLiveCheck(0); + } + LOGGER.info(String.format("Reading certificates from '%s'", certFile)); + for (String line : lines) { + handleCidLine(line); + } + for (Certificate temp : Certificate.certs) { + if (temp.getLiveCheck() == 0) { + serverInterface.sendCert("*", ProtocolConstants.CERT_DELETE, temp, null); + temp.close(); + } + } + } + + private void createManageVars() { + int varNum = Manage.manager.addVar("system.boottime", ManageVarType.ATT_DATE); + Manage.manager.setVar(varNum, Support.mtime()); + varNum = Manage.manager.addVar("version.system", ManageVarType.ATT_VARCHAR); + Manage.manager.setVar(varNum, GlobalConstants.VERSION); + } + + private void createInterfaces() { + String prompt = String.format("%s>", Server.myServer.getIdent()); + clientInterface = new ClientInterface(clientPort, "client", "client interface"); + serverInterface = new ServerInterface(serverPort, "server", "server interface"); + systemInterface = new SystemInterface(systemPort, "system", "system management interface"); + systemInterface.setPrompt(prompt); + + serverInterface.setFeedStrategy(NetworkConstants.FEED_BOTH); + + /* Clients may send a maximum of 100000 bytes/second */ + clientInterface.setFloodLimit(100000); + clientInterface.setFeedStrategy(NetworkConstants.FEED_IN); + + /* Clients may have a buffer of 100000 bytes */ + clientInterface.setOutBufLimit(100000); + + pManager.registerProcess(clientInterface); + pManager.registerProcess(serverInterface); + pManager.registerProcess(systemInterface); + } + + private void makeConnections() { + ConfigGroup cgroup = configManager.getGroup("connections"); + if (cgroup == null) return; + ConfigEntry centry = cgroup.getEntry("connectto"); + if (centry != null) { + /* Connect to the configured servers */ + int x, nParts = centry.getNParts(); + for (x = 0; x < nParts; x++) { + int portNum = 3011; + String data = centry.getPart(x); + int index = data.indexOf(":"); + if (index != -1) { + String sportNum = data.substring(index + 1); + Scanner scanner = new Scanner(sportNum); + if (scanner.hasNextInt()) { + portNum = scanner.nextInt(); + } + data = data.substring(0, index); + } + if (serverInterface.addUser(data, portNum, null) != 1) { + LOGGER.error(String.format("Connection to %s port %d failed!", data, portNum)); + } else { + LOGGER.error(String.format("Connected to %s port %d", data, portNum)); + } + } + } + + + centry = cgroup.getEntry("allowfrom"); + if (centry != null) { + /* Allow the configured servers */ + int nParts = centry.getNParts(); + for (int x = 0; x < nParts; x++) { + serverInterface.allow(centry.getPart(x)); + } + } else { + LOGGER.warn("No 'allowfrom' found, allowing everybody on the server port"); + } + + serverInterface.sendReset(); } } diff --git a/src/main/java/com/hans0924/fsd/FsdLauncher.java b/src/main/java/com/hans0924/fsd/FsdLauncher.java index 354bc5c..4857f9f 100644 --- a/src/main/java/com/hans0924/fsd/FsdLauncher.java +++ b/src/main/java/com/hans0924/fsd/FsdLauncher.java @@ -1,11 +1,44 @@ package com.hans0924.fsd; +import com.hans0924.fsd.constants.FsdPath; +import com.hans0924.fsd.support.Support; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; + /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class FsdLauncher { - public static void main(String[] args) { + private static final Logger LOGGER = LoggerFactory.getLogger(FsdLauncher.class); + + public static void main(String[] args) { + String property = System.getProperty("user.dir"); + Path path = Paths.get(FsdPath.PATH_FSD_CONF); + File file = new File(FsdPath.PATH_FSD_CONF); + LOGGER.info(String.format("Using config file: %s", file.getAbsolutePath())); + String configFile = FsdPath.PATH_FSD_CONF; + doSignals(); + run(configFile); + } + + private static void run(String configFile) { + Fsd fsd = new Fsd(configFile); + while (true) { +// try { + fsd.run(); +// } catch (Exception e) { +// LOGGER.error("Unhandled exception: ", e); +// } + } + } + + private static void doSignals() { + Support.startTimer(); } } diff --git a/src/main/java/com/hans0924/fsd/constants/CertificateConstants.java b/src/main/java/com/hans0924/fsd/constants/CertificateConstants.java new file mode 100644 index 0000000..c63aaae --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/CertificateConstants.java @@ -0,0 +1,14 @@ +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class CertificateConstants { + public static final String[] CERT_LEVEL = + { + "SUSPENDED", "OBSPILOT", "STUDENT1", "STUDENT2", "STUDENT3", + "CONTROLLER1", "CONTROLLER2", "CONTROLLER3", "INSTRUCTOR1", + "INSTRUCTOR2", "INSTRUCTOR3", "SUPERVISOR", "ADMINISTRATOR" + }; +} diff --git a/src/main/java/com/hans0924/fsd/constants/ClientConstants.java b/src/main/java/com/hans0924/fsd/constants/ClientConstants.java new file mode 100644 index 0000000..b3ac392 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/ClientConstants.java @@ -0,0 +1,62 @@ +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class ClientConstants { + + public static final int CLIENT_PILOT = 1; + + public static final int CLIENT_ATC = 2; + + public static final int CLIENT_ALL = 3; + + public static final String[] CL_CMD_NAMES = { + "#AA", + "#DA", + "#AP", + "#DP", + "$HO", + "#TM", + "#RW", + "@", + "%", + "$PI", + "$PO", + "$HA", + "$FP", + "#SB", + "#PC", + "#WX", + "#CD", + "#WD", + "#TD", + "$C?", + "$CI", + "$AX", + "$AR", + "$ER", + "$CQ", + "$CR", + "$!!", + "#DL" + }; + + public static final String[] ERR_STR = { + "No error", + "Callsign in use", + "Invalid callsign", + "Already registerd", + "Syntax error", + "Invalid source callsign", + "Invalid CID/password", + "No such callsign", + "No flightplan", + "No such weather profile", + "Invalid protocol revision", + "Requested level too high", + "Too many clients connected", + "CID/PID was suspended" + }; +} diff --git a/src/main/java/com/hans0924/fsd/constants/FsdPath.java b/src/main/java/com/hans0924/fsd/constants/FsdPath.java new file mode 100644 index 0000000..ba6948e --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/FsdPath.java @@ -0,0 +1,21 @@ + +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020/03/04 + */ +public class FsdPath { + + public static final String PATH_FSD_CONF = "fsd.conf"; + + public static final String PATH_FSD_HELP = "help.txt"; + + public static final String PATH_FSD_MOTD = "motd.txt"; + + public static final String LOGFILE = "log.txt"; + + public static final String METARFILE = "metar.txt"; + + public static final String METARFILENEW = "metarnew.txt"; +} diff --git a/src/main/java/com/hans0924/fsd/constants/GlobalConstants.java b/src/main/java/com/hans0924/fsd/constants/GlobalConstants.java new file mode 100644 index 0000000..7751003 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/GlobalConstants.java @@ -0,0 +1,57 @@ + +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020/03/05 + */ +public class GlobalConstants { + + public static final String PRODUCT = "FSFDT Windows FSD Beta from FSD V3.000 draft 9"; + + public static final String VERSION = "V3.000 d9"; + + public static final int NEED_REVISION = 9; + + /** + * WARNING!!!: The USER_TIMEOUT (idle time of a SOCKET before it's dropped) + * should not be higher than the SERVER_TIMEOUT (idle time of a + * server) + */ + public static final int USER_TIMEOUT = 500_000; + public static final int SERVER_TIMEOUT = 800_000; + public static final int CLIENT_TIMEOUT = 800_000; + public static final int SILENT_CLIENT_TIMEOUT = 36000_000; + public static final int WIND_DELTA_TIMEOUT = 70_000; + + public static final int USER_PING_TIMEOUT = 200_000; + public static final int USER_FEED_CHECK = 3000; + public static final int LAG_CHECK = 60_000; + public static final int NOTIFY_CHECK = 300_000; + public static final int SYNC_TIMEOUT = 120_000; + public static final int SERVER_MAX_TOOK = 240; + public static final int MAX_HOPS = 10; + public static final int GUARD_RETRY = 120_000; + public static final int CALLSIGN_BYTES = 12; + public static final int MAX_LINE_LENGTH = 512; + public static final int MAX_METAR_DOWNLOAD_TIME = 900_000; + public static final int CERT_FILE_CHECK = 120_000; + + public static final int WHAZZUP_CHECK = 30_000; + public static final int CONNECT_DELAY = 20_000; + + public static final int LEV_SUSPENDED = 0; + public static final int LEV_OBSPILOT = 1; + public static final int LEV_STUDENT1 = 2; + public static final int LEV_STUDENT2 = 3; + public static final int LEV_STUDENT3 = 4; + public static final int LEV_CONTROLLER1 = 5; + public static final int LEV_CONTROLLER2 = 6; + public static final int LEV_CONTROLLER3 = 7; + public static final int LEV_INSTRUCTOR1 = 8; + public static final int LEV_INSTRUCTOR2 = 9; + public static final int LEV_INSTRUCTOR3 = 10; + public static final int LEV_SUPERVISOR = 11; + public static final int LEV_ADMINISTRATOR = 12; + public static final int LEV_MAX = 12; +} diff --git a/src/main/java/com/hans0924/fsd/constants/ManageVarType.java b/src/main/java/com/hans0924/fsd/constants/ManageVarType.java index 0b00377..3a017e1 100644 --- a/src/main/java/com/hans0924/fsd/constants/ManageVarType.java +++ b/src/main/java/com/hans0924/fsd/constants/ManageVarType.java @@ -1,7 +1,7 @@ package com.hans0924.fsd.constants; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public final class ManageVarType { diff --git a/src/main/java/com/hans0924/fsd/constants/MetarSource.java b/src/main/java/com/hans0924/fsd/constants/MetarSource.java new file mode 100644 index 0000000..10a4178 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/MetarSource.java @@ -0,0 +1,15 @@ + +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020/03/04 + */ +public class MetarSource { + + public final static int SOURCE_NETWORK = 0; + + public final static int SOURCE_FILE = 1; + + public final static int SOURCE_DOWNLOAD = 2; +} diff --git a/src/main/java/com/hans0924/fsd/constants/NetworkConstants.java b/src/main/java/com/hans0924/fsd/constants/NetworkConstants.java new file mode 100644 index 0000000..b1325f2 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/NetworkConstants.java @@ -0,0 +1,43 @@ +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020-03-05 + */ +public class NetworkConstants { + public static final int KILL_NONE = 0; + + public static final int KILL_COMMAND = 1; + + public static final int KILL_FLOOD = 2; + + public static final int KILL_INIT_TIMEOUT = 3; + + public static final int KILL_DATA_TIMEOUT = 4; + + public static final int KILL_CLOSED = 5; + + public static final int KILL_WRITE_ERR = 6; + + public static final int KILL_KILL = 7; + + public static final int KILL_PROTOCOL = 8; + + public static final int FEED_IN = 1; + + public static final int FEED_OUT = 2; + + public static final int FEED_BOTH = 3; + + public static final String[] KILL_REASONS = { + "", + "closed on command", + "flooding", + "initial timeout", + "socket stalled", + "connection closed", + "write error", + "killed on command", + "protocol revision error" + }; +} diff --git a/src/main/java/com/hans0924/fsd/constants/ProtocolConstants.java b/src/main/java/com/hans0924/fsd/constants/ProtocolConstants.java new file mode 100644 index 0000000..e8657a6 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/ProtocolConstants.java @@ -0,0 +1,126 @@ +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class ProtocolConstants { + + public static final int CMD_NOTIFY = 0; + public static final int CMD_REQ_METAR = 1; + public static final int CMD_PING = 2; + public static final int CMD_PONG = 3; + public static final int CMD_SYNC = 4; + public static final int CMD_LINK_DOWN = 5; + public static final int CMD_NO_WX = 6; + public static final int CMD_ADD_CLIENT = 7; + public static final int CMD_RM_CLIENT = 8; + public static final int CMD_PLAN = 9; + public static final int CMD_PD = 10; + public static final int CMD_AD = 11; + public static final int CMD_CERT = 12; + public static final int CMD_MULTICAST = 13; + public static final int CMD_WEATHER = 14; + public static final int CMD_METAR = 15; + public static final int CMD_ADD_WPROFILE = 16; + public static final int CMD_DEL_WPROFILE = 17; + public static final int CMD_KILL = 18; + public static final int CMD_RESET = 19; + + public static final int CL_ADDATC = 0; + public static final int CL_RMATC = 1; + public static final int CL_ADDPILOT = 2; + public static final int CL_RMPILOT = 3; + public static final int CL_REQHANDOFF = 4; + public static final int CL_MESSAGE = 5; + public static final int CL_REQWEATHER = 6; + public static final int CL_PILOTPOS = 7; + public static final int CL_ATCPOS = 8; + public static final int CL_PING = 9; + public static final int CL_PONG = 10; + public static final int CL_ACHANDOFF = 11; + public static final int CL_PLAN = 12; + public static final int CL_SB = 13; + public static final int CL_PC = 14; + public static final int CL_WEATHER = 15; + public static final int CL_CLOUDDATA = 16; + public static final int CL_WINDDATA = 17; + public static final int CL_TEMPDATA = 18; + public static final int CL_REQCOM = 19; + public static final int CL_REPCOM = 20; + public static final int CL_REQACARS = 21; + public static final int CL_REPACARS = 22; + public static final int CL_ERROR = 23; + public static final int CL_CQ = 24; + public static final int CL_CR = 25; + public static final int CL_KILL = 26; + public static final int CL_WDELTA = 27; + + public static final int CL_MAX = 27; + + public static final int CERT_ADD = 0; + public static final int CERT_DELETE = 1; + public static final int CERT_MODIFY = 2; + + public static final int ERR_OK = 0; /* No error */ + public static final int ERR_CSINUSE = 1; /* Callsign in use */ + public static final int ERR_CSINVALID = 2; /* Callsign invalid */ + public static final int ERR_REGISTERED = 3; /* Already registered */ + public static final int ERR_SYNTAX = 4; /* Syntax error */ + public static final int ERR_SRCINVALID = 5; /* Invalid source in packet */ + public static final int ERR_CIDINVALID = 6; /* Invalid CID/password */ + public static final int ERR_NOSUCHCS = 7; /* No such callsign */ + public static final int ERR_NOFP = 8; /* No flightplan */ + public static final int ERR_NOWEATHER = 9; /* No such weather profile*/ + public static final int ERR_REVISION = 10; /* Invalid protocol revision */ + public static final int ERR_LEVEL = 11; /* Requested level too high */ + public static final int ERR_SERVFULL = 12; /* No more clients */ + public static final int ERR_CSSUSPEND = 13; /* CID/PID suspended */ + + + public static final String[] CMD_NAMES = { + "NOTIFY", + "REQMETAR", + "PING", + "PONG", + "SYNC", + "LINKDOWN", + "NOWX", + "ADDCLIENT", + "RMCLIENT", + "PLAN", + "PD", /* Pilot data */ + "AD", /* ATC data */ + "ADDCERT", + "MC", + "WX", + "METAR", + "AWPROF", + "DWPROF", + "KILL", + "RESET" + }; + + public static final int[] SILENT_OK = { + 1, /* notify */ + 1, /* reqmetar */ + 1, /* ping */ + 1, /* pong */ + 1, /* sync */ + 1, /* linkdown */ + 1, /* nowx */ + 1, /* addclient */ + 1, /* rmclient */ + 1, /* plan */ + 0, /* pd */ + 0, /* ad */ + 1, /* addcert */ + 0, /* mc */ + 1, /* wx */ + 1, /* metar */ + 1, /* add w profile */ + 1, /* del w profile */ + 1, /* kill client */ + 1 /* reset */ + }; +} diff --git a/src/main/java/com/hans0924/fsd/constants/ServerConstants.java b/src/main/java/com/hans0924/fsd/constants/ServerConstants.java new file mode 100644 index 0000000..c2e25a3 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/ServerConstants.java @@ -0,0 +1,13 @@ + +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020/03/05 + */ +public class ServerConstants { + + public static final int SERVER_METAR = 1; + + public static final int SERVER_SILENT = 2; +} diff --git a/src/main/java/com/hans0924/fsd/constants/SupportConstants.java b/src/main/java/com/hans0924/fsd/constants/SupportConstants.java new file mode 100644 index 0000000..1e4fa7c --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/SupportConstants.java @@ -0,0 +1,12 @@ +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class SupportConstants { + + public static final int L_MAX = 7; + + +} diff --git a/src/main/java/com/hans0924/fsd/constants/SystemConstants.java b/src/main/java/com/hans0924/fsd/constants/SystemConstants.java new file mode 100644 index 0000000..b28b6e1 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/SystemConstants.java @@ -0,0 +1,96 @@ +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class SystemConstants { + + public static final int SYS_SERVERS = 0; + public static final int SYS_INFORMATION = 1; + public static final int SYS_PING = 2; + public static final int SYS_CONNECT = 3; + public static final int SYS_TIME = 4; + public static final int SYS_ROUTE = 5; + public static final int SYS_WEATHER = 6; + public static final int SYS_DISCONNECT = 7; + public static final int SYS_HELP = 8; + public static final int SYS_QUIT = 9; + public static final int SYS_STAT = 10; + public static final int SYS_SAY = 11; + public static final int SYS_CLIENTS = 12; + public static final int SYS_CERT = 13; + public static final int SYS_PWD = 14; + public static final int SYS_DISTANCE = 15; + public static final int SYS_RANGE = 16; + public static final int SYS_LOG = 17; + public static final int SYS_WALL = 18; + public static final int SYS_DELGUARD = 19; + public static final int SYS_METAR = 20; + public static final int SYS_WP = 21; + public static final int SYS_KILL = 22; + public static final int SYS_POS = 23; + public static final int SYS_DUMP = 24; + public static final int SYS_SERVERS2 = 25; + public static final int SYS_REFMETAR = 26; + + public static final String[] SYS_CMDS = { + "servers", + "info", + "ping", + "connect", + "time", + "route", + "weather", + "disconnect", + "help", + "quit", + "stat", + "say", + "clients", + "cert", + "pwd", + "distance", + "range", + "log", + "wall", + "delguard", + "metar", + "wp", + "kill", + "pos", + "dump", + "servers2", + "refreshmetar" + }; + + public static final int[] NEED_AUTHORIZATION = { + 0, /* servers */ + 1, /* info */ + 1, /* ping */ + 1, /* connect */ + 0, /* time */ + 0, /* route */ + 0, /* weather */ + 1, /* disconnect */ + 0, /* help */ + 0, /* quit */ + 1, /* stat */ + 1, /* say */ + 1, /* clients */ + 1, /* cert */ + 0, /* pwd */ + 1, /* distance */ + 1, /* range */ + 1, /* log */ + 1, /* wall */ + 1, /* delguard */ + 0, /* metar */ + 1, /* wp */ + 1, /* kill */ + 0, /* pos */ + 1, /* dump */ + 0, /* servers2 */ + 1 /* refresh metar */ + }; +} diff --git a/src/main/java/com/hans0924/fsd/constants/WeatherConstants.java b/src/main/java/com/hans0924/fsd/constants/WeatherConstants.java new file mode 100644 index 0000000..0ca60c9 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/constants/WeatherConstants.java @@ -0,0 +1,18 @@ +package com.hans0924.fsd.constants; + +/** + * @author Hanshuo Zeng + * @since 2020-03-05 + */ +public class WeatherConstants { + public final static int VAR_UPDIRECTION = 0; + public final static int VAR_MIDCOR = 1; + public final static int VAR_LOWCOR = 2; + public final static int VAR_MIDDIRECTION = 3; + public final static int VAR_MIDSPEED = 4; + public final static int VAR_LOWDIRECTION = 5; + public final static int VAR_LOWSPEED = 6; + public final static int VAR_UPTEMP = 7; + public final static int VAR_MIDTEMP = 8; + public final static int VAR_LOWTEMP = 9; +} diff --git a/src/main/java/com/hans0924/fsd/manager/Manage.java b/src/main/java/com/hans0924/fsd/manager/Manage.java index d0b1b66..0e6c27c 100644 --- a/src/main/java/com/hans0924/fsd/manager/Manage.java +++ b/src/main/java/com/hans0924/fsd/manager/Manage.java @@ -8,7 +8,7 @@ import java.util.List; import java.util.Objects; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class Manage { @@ -21,15 +21,17 @@ public class Manage { public Manage() { nVars = 0; - variables = new ArrayList(); + variables = new ArrayList<>(); } public int addVar(String name, int type) { ManageVar var = new ManageVar(); var.setType(type); var.setName(name); + var.setValue(new ManageVarValue()); + ++nVars; variables.add(var); - return variables.size(); + return variables.size() - 1; } public void delVar(int num) { @@ -68,12 +70,11 @@ public class Manage { value.setTimeVal(timeVal); } - public ManageVarValue getVar(int num) { + public ManageVar getVar(int num) { if (num >= nVars) { return null; } - ManageVar var = variables.get(num); - return var.getValue(); + return variables.get(num); } public int getNVars() { diff --git a/src/main/java/com/hans0924/fsd/manager/ManageVar.java b/src/main/java/com/hans0924/fsd/manager/ManageVar.java index 79d72ac..a8b0657 100644 --- a/src/main/java/com/hans0924/fsd/manager/ManageVar.java +++ b/src/main/java/com/hans0924/fsd/manager/ManageVar.java @@ -1,10 +1,7 @@ package com.hans0924.fsd.manager; -import javolution.io.Struct; -import javolution.io.Union; - /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class ManageVar { diff --git a/src/main/java/com/hans0924/fsd/manager/ManageVarValue.java b/src/main/java/com/hans0924/fsd/manager/ManageVarValue.java index 737bf4e..e331a87 100644 --- a/src/main/java/com/hans0924/fsd/manager/ManageVarValue.java +++ b/src/main/java/com/hans0924/fsd/manager/ManageVarValue.java @@ -1,7 +1,7 @@ package com.hans0924.fsd.manager; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class ManageVarValue { diff --git a/src/main/java/com/hans0924/fsd/model/Certificate.java b/src/main/java/com/hans0924/fsd/model/Certificate.java new file mode 100644 index 0000000..9fd32ce --- /dev/null +++ b/src/main/java/com/hans0924/fsd/model/Certificate.java @@ -0,0 +1,125 @@ +package com.hans0924.fsd.model; + +import com.hans0924.fsd.constants.GlobalConstants; +import com.hans0924.fsd.support.Support; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class Certificate { + public static List certs = new ArrayList<>(); + + private String cid, password, origin; + + private int level, liveCheck; + + private long prevVisit, creation; + + public Certificate(String c, String p, int l, long crea, String o) { + certs.add(this); + liveCheck = 1; + cid = c; + password = p; + level = l; + creation = crea; + origin = o; + if (level > GlobalConstants.LEV_MAX) { + level = GlobalConstants.LEV_MAX; + } + prevVisit = 0; + } + + public void close() { + certs.remove(this); + } + + public void configure(String pwd, int l, long c, String o) { + level = l; + if (pwd != null) { + password = pwd; + } + if (o != null) { + origin = o; + } + creation = c; + } + + public static int maxLevel(String id, String p, int[] max /* pass by ref */) { + Certificate cert = getCert(id); + if (cert == null) { + max[0] = GlobalConstants.LEV_OBSPILOT; + return 0; + } + if (cert.getPassword().equals(p)) { + max[0] = cert.getLevel(); + cert.setPrevVisit(Support.mtime()); + return 1; + } + max[0] = GlobalConstants.LEV_OBSPILOT; + return 0; + } + + public static Certificate getCert(String cid) { + return certs.stream().filter(e -> e.getCid().equals(cid)).findFirst().orElse(null); + } + + public String getCid() { + return cid; + } + + public void setCid(String cid) { + this.cid = cid; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public int getLevel() { + return level; + } + + public void setLevel(int level) { + this.level = level; + } + + public int getLiveCheck() { + return liveCheck; + } + + public void setLiveCheck(int liveCheck) { + this.liveCheck = liveCheck; + } + + public long getPrevVisit() { + return prevVisit; + } + + public void setPrevVisit(long prevVisit) { + this.prevVisit = prevVisit; + } + + public long getCreation() { + return creation; + } + + public void setCreation(long creation) { + this.creation = creation; + } +} diff --git a/src/main/java/com/hans0924/fsd/model/Client.java b/src/main/java/com/hans0924/fsd/model/Client.java new file mode 100644 index 0000000..d36f693 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/model/Client.java @@ -0,0 +1,350 @@ +package com.hans0924.fsd.model; + +import com.hans0924.fsd.Fsd; +import com.hans0924.fsd.constants.ClientConstants; +import com.hans0924.fsd.support.Support; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class Client { + + public static List clients = new ArrayList<>(); + + private static final Logger LOGGER = LoggerFactory.getLogger(Client.class); + + private long startTime; + + private Flightplan plan; + + private int type; + + private int rating; + + private long pbh; + + private int flags; + + private long alive; + + private String cid, callsign, protocol, realName, sector, identFlag; + + private double lat, lon; + + private int transponder, altitude, groundSpeed, frequency, facilityType; + + private int positionOk, visualRange, simType; + + private Server location; + + public Client(String i, Server where, String cs, int t, int reqRating, String rev, String real, int st) { + clients.add(this); + cid = i; + location = where; + callsign = cs; + type = t; + protocol = rev; + rating = reqRating; + visualRange = 40; + realName = real; + simType = st; + startTime = alive = Support.mtime(); + } + + public void close() { + Fsd.serverInterface.clientDropped(this); + clients.remove(this); + } + + public void handleFp(String[] array) { + int revision = plan != null ? plan.getRevision() + 1 : 0; + plan = new Flightplan(callsign, array[0].charAt(0), array[1], + NumberUtils.toInt(array[2]), array[3], NumberUtils.toInt(array[4]), + NumberUtils.toInt(array[5]), array[6], array[7], NumberUtils.toInt(array[8]), + NumberUtils.toInt(array[9]), NumberUtils.toInt(array[10]), NumberUtils.toInt(array[11]), array[12], + array[13], array[14]); + plan.setRevision(revision); + } + + /* Update the client fields, given a packet array */ + public void updatePilot(String[] array) { + transponder = NumberUtils.toInt(array[2]); + identFlag = array[0]; + lat = NumberUtils.toDouble(array[4]); + lon = NumberUtils.toDouble(array[5]); + if (lat > 90.0 || lat < -90.0 || lon > 180.0 || lon < -180.0) { + LOGGER.debug(String.format("POSERR: s=(%s,%s) got(%f,%f)", array[4], array[5], lat, lon)); + } + + altitude = NumberUtils.toInt(array[6]); + groundSpeed = NumberUtils.toInt(array[7]); + pbh = NumberUtils.toLong(array[8]); + + flags = NumberUtils.toInt(array[9]); + setAlive(); + positionOk = 1; + } + + public void updateAtc(String[] array) { + frequency = NumberUtils.toInt(array[0]); + facilityType = NumberUtils.toInt(array[1]); + visualRange = NumberUtils.toInt(array[2]); + lat = NumberUtils.toDouble(array[4]); + lon = NumberUtils.toDouble(array[5]); + altitude = NumberUtils.toInt(array[6]); + setAlive(); + positionOk = 1; + } + + public void setAlive() { + alive = Support.mtime(); + } + + public double distance(Client other) { + if (other == null) { + return -1; + } + if (positionOk == 0 || other.getPositionOk() == 0) { + return -1; + } + return Support.dist(lat, lon, other.lat, other.lon); + } + + public int getRange() { + if (type == ClientConstants.CLIENT_PILOT) { + if (altitude < 0) { + altitude = 0; + } + return (int) (10 + 1.414 * Math.sqrt(altitude)); + } + + switch (facilityType) { + case 0: + return 40; /* Unknown */ + case 1: + return 1500; /* FSS */ + case 2: + return 5; /* CLR_DEL */ + case 3: + return 5; /* GROUND */ + case 4: + return 30; /* TOWER */ + case 5: + return 100; /* APP/DEP */ + case 6: + return 400; /* CENTER */ + case 7: + return 1500; /* MONITOR */ + default: + return 40; + } + } + + public static Client getClient(String ident) { + for (Client temp : clients) { + if (temp.getCallsign().equals(ident)) { + return temp; + } + } + return null; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public Flightplan getPlan() { + return plan; + } + + public void setPlan(Flightplan plan) { + this.plan = plan; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getRating() { + return rating; + } + + public void setRating(int rating) { + this.rating = rating; + } + + public long getPbh() { + return pbh; + } + + public void setPbh(long pbh) { + this.pbh = pbh; + } + + public int getFlags() { + return flags; + } + + public void setFlags(int flags) { + this.flags = flags; + } + + public long getAlive() { + return alive; + } + + public void setAlive(long alive) { + this.alive = alive; + } + + public String getCid() { + return cid; + } + + public void setCid(String cid) { + this.cid = cid; + } + + public String getCallsign() { + return callsign; + } + + public void setCallsign(String callsign) { + this.callsign = callsign; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public String getRealName() { + return realName; + } + + public void setRealName(String realName) { + this.realName = realName; + } + + public String getSector() { + return sector; + } + + public void setSector(String sector) { + this.sector = sector; + } + + public String getIdentFlag() { + return identFlag; + } + + public void setIdentFlag(String identFlag) { + this.identFlag = identFlag; + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } + + public int getTransponder() { + return transponder; + } + + public void setTransponder(int transponder) { + this.transponder = transponder; + } + + public int getAltitude() { + return altitude; + } + + public void setAltitude(int altitude) { + this.altitude = altitude; + } + + public int getGroundSpeed() { + return groundSpeed; + } + + public void setGroundSpeed(int groundSpeed) { + this.groundSpeed = groundSpeed; + } + + public int getFrequency() { + return frequency; + } + + public void setFrequency(int frequency) { + this.frequency = frequency; + } + + public int getFacilityType() { + return facilityType; + } + + public void setFacilityType(int facilityType) { + this.facilityType = facilityType; + } + + public int getPositionOk() { + return positionOk; + } + + public void setPositionOk(int positionOk) { + this.positionOk = positionOk; + } + + public int getVisualRange() { + return visualRange; + } + + public void setVisualRange(int visualRange) { + this.visualRange = visualRange; + } + + public int getSimType() { + return simType; + } + + public void setSimType(int simType) { + this.simType = simType; + } + + public Server getLocation() { + return location; + } + + public void setLocation(Server location) { + this.location = location; + } +} diff --git a/src/main/java/com/hans0924/fsd/model/Flightplan.java b/src/main/java/com/hans0924/fsd/model/Flightplan.java new file mode 100644 index 0000000..63e27a8 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/model/Flightplan.java @@ -0,0 +1,181 @@ +package com.hans0924.fsd.model; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class Flightplan { + private String callsign; + private int revision; + private char type; + private String aircraft; + private int tasCruise; + private String depAirport; + private int depTime; + private int actDepTime; + private String alt; + private String destAirport; + private int hrsEnroute, minEnroute; + private int hrsFuel, minFuel; + private String altAirport; + private String remarks; + private String route; + + public Flightplan(String cs, char type, String aircraft, int tasCruise, + String depAirport, int depTime, int actDepTime, String alt, + String destAirport, int hrsEnroute, int minEnroute, int hrsFuel, + int minFuel, String altAirport, String remarks, String route) { + this.callsign = cs; + this.type = type; + this.aircraft = aircraft; + this.tasCruise = tasCruise; + this.depAirport = depAirport; + this.depTime = depTime; + this.actDepTime = actDepTime; + this.alt = alt; + this.destAirport = destAirport; + this.hrsEnroute = hrsEnroute; + this.minEnroute = minEnroute; + this.hrsFuel = hrsFuel; + this.minFuel = minFuel; + this.altAirport = altAirport; + this.remarks = remarks; + this.route = route; + } + + public String getCallsign() { + return callsign; + } + + public void setCallsign(String callsign) { + this.callsign = callsign; + } + + public int getRevision() { + return revision; + } + + public void setRevision(int revision) { + this.revision = revision; + } + + public char getType() { + return type; + } + + public void setType(char type) { + this.type = type; + } + + public String getAircraft() { + return aircraft; + } + + public void setAircraft(String aircraft) { + this.aircraft = aircraft; + } + + public int getTasCruise() { + return tasCruise; + } + + public void setTasCruise(int tasCruise) { + this.tasCruise = tasCruise; + } + + public String getDepAirport() { + return depAirport; + } + + public void setDepAirport(String depAirport) { + this.depAirport = depAirport; + } + + public int getDepTime() { + return depTime; + } + + public void setDepTime(int depTime) { + this.depTime = depTime; + } + + public int getActDepTime() { + return actDepTime; + } + + public void setActDepTime(int actDepTime) { + this.actDepTime = actDepTime; + } + + public String getAlt() { + return alt; + } + + public void setAlt(String alt) { + this.alt = alt; + } + + public String getDestAirport() { + return destAirport; + } + + public void setDestAirport(String destAirport) { + this.destAirport = destAirport; + } + + public int getHrsEnroute() { + return hrsEnroute; + } + + public void setHrsEnroute(int hrsEnroute) { + this.hrsEnroute = hrsEnroute; + } + + public int getMinEnroute() { + return minEnroute; + } + + public void setMinEnroute(int minEnroute) { + this.minEnroute = minEnroute; + } + + public int getHrsFuel() { + return hrsFuel; + } + + public void setHrsFuel(int hrsFuel) { + this.hrsFuel = hrsFuel; + } + + public int getMinFuel() { + return minFuel; + } + + public void setMinFuel(int minFuel) { + this.minFuel = minFuel; + } + + public String getAltAirport() { + return altAirport; + } + + public void setAltAirport(String altAirport) { + this.altAirport = altAirport; + } + + public String getRemarks() { + return remarks; + } + + public void setRemarks(String remarks) { + this.remarks = remarks; + } + + public String getRoute() { + return route; + } + + public void setRoute(String route) { + this.route = route; + } +} diff --git a/src/main/java/com/hans0924/fsd/model/Server.java b/src/main/java/com/hans0924/fsd/model/Server.java new file mode 100644 index 0000000..8bce1de --- /dev/null +++ b/src/main/java/com/hans0924/fsd/model/Server.java @@ -0,0 +1,239 @@ +package com.hans0924.fsd.model; + +import com.hans0924.fsd.Fsd; +import com.hans0924.fsd.user.AbstractUser; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.user.ServerUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * @author Hanshuo Zeng + * @since 2020-03-05 + */ +public class Server { + public static Server myServer; + + public static List servers = new ArrayList<>(); + + private final static Logger LOGGER = LoggerFactory.getLogger(Server.class); + + private int pCount; + + private int hops; + + private int check; + + private long lag; + + private int flags; + + private int packetDrops; + + private long alive; + + private String name; + + private String email; + + private String ident; + + private String hostName; + + private String version; + + private String location; + + private AbstractUser path; + + public Server(String ident, String name, String email, String hostName, String version, int flags, String location) { + this.ident = ident; + this.name = name; + this.email = email; + this.hostName = hostName; + this.version = version; + this.flags = flags; + this.location = location; + packetDrops = 0; + hops = -1; + pCount = -1; + lag = -1; + alive = Support.mtime(); + } + + public void configure(String name, String email, String hostName, String version, String location) { + this.name = name; + this.email = email; + this.hostName = hostName; + this.version = version; + this.location = location; + } + + public void close() { + LOGGER.info(String.format("Dropping server %s(%s)", ident, name)); + for (Client client : Client.clients) { + if (client.getLocation() == this) { + client.close(); + } + } + + List users = Fsd.serverInterface.getUsers(); + for (AbstractUser user : users) { + if (((ServerUser) user).getThisServer() == this) { + ((ServerUser) user).setThisServer(null); + } + } + } + + public static Server getServer(String ident) { + for (Server server : servers) { + if (server.getIdent().equals(ident)) { + return server; + } + } + return null; + } + + public void setPath(AbstractUser who, int hops) { + path = who; + this.hops = hops; + if (path == null) { + pCount = -1; + } + } + + public void setAlive() { + alive = Support.mtime(); + } + + public void receivePong(String data) { + Scanner scanner = new Scanner(data); + if (scanner.hasNextInt()) { + scanner.nextInt(); + if (scanner.hasNextLong()) { + long t = scanner.nextLong(); + lag = Support.mtime() - t; + } + } + } + + public void clearServerChecks() { + servers.forEach(s -> s.setCheck(0)); + } + + public int getpCount() { + return pCount; + } + + public void setpCount(int pCount) { + this.pCount = pCount; + } + + public int getHops() { + return hops; + } + + public void setHops(int hops) { + this.hops = hops; + } + + public int getCheck() { + return check; + } + + public void setCheck(int check) { + this.check = check; + } + + public long getLag() { + return lag; + } + + public void setLag(long lag) { + this.lag = lag; + } + + public int getFlags() { + return flags; + } + + public void setFlags(int flags) { + this.flags = flags; + } + + public int getPacketDrops() { + return packetDrops; + } + + public void setPacketDrops(int packetDrops) { + this.packetDrops = packetDrops; + } + + public long getAlive() { + return alive; + } + + public void setAlive(long alive) { + this.alive = alive; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getIdent() { + return ident; + } + + public void setIdent(String ident) { + this.ident = ident; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + public AbstractUser getPath() { + return path; + } + + public void setPath(AbstractUser path) { + this.path = path; + } +} diff --git a/src/main/java/com/hans0924/fsd/process/PMan.java b/src/main/java/com/hans0924/fsd/process/PMan.java index 4de6aa3..acd12c2 100644 --- a/src/main/java/com/hans0924/fsd/process/PMan.java +++ b/src/main/java/com/hans0924/fsd/process/PMan.java @@ -1,41 +1,39 @@ package com.hans0924.fsd.process; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.io.IOException; import java.nio.channels.SelectableChannel; -import java.nio.channels.SelectionKey; import java.nio.channels.Selector; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; import java.util.Set; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class PMan { + private final static Logger LOGGER = LoggerFactory.getLogger(PMan.class); + + public static List processes = new ArrayList<>(); + private boolean busy; - private Selector selector; - - public PMan() throws IOException { - selector = Selector.open(); + public PMan() { busy = false; } - public void registerProcess(Process process) throws IOException { - process.getChannel().configureBlocking(false); - SelectionKey selectionKey = process.getChannel().register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE, process); - process.setSelectionKey(selectionKey); + public void registerProcess(Process process) { + processes.add(process); } - public void run() throws IOException { - long timeOut = busy ? 0 : 1000L; - busy = false; - if (selector.select(timeOut) > 0) { - Set selectionKeys = selector.selectedKeys(); - for (SelectionKey selectionKey : selectionKeys) { - Process attachment = (Process) selectionKey.attachment(); - if (attachment.run()) { - busy = true; - } + public void run() { + for (Process process : processes) { + if (process.run()) { + busy = true; } } } diff --git a/src/main/java/com/hans0924/fsd/process/Process.java b/src/main/java/com/hans0924/fsd/process/Process.java index b4feccf..bd8e366 100644 --- a/src/main/java/com/hans0924/fsd/process/Process.java +++ b/src/main/java/com/hans0924/fsd/process/Process.java @@ -1,40 +1,17 @@ package com.hans0924.fsd.process; import java.nio.channels.SelectableChannel; -import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Set; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public abstract class Process { - private SelectableChannel channel; - - private SelectionKey selectionKey; - public Process() { } - public int calcMask() { - return 0; - } - public abstract boolean run(); - - public SelectableChannel getChannel() { - return channel; - } - - public void setChannel(SelectableChannel channel) { - this.channel = channel; - } - - public SelectionKey getSelectionKey() { - return selectionKey; - } - - public void setSelectionKey(SelectionKey selectionKey) { - this.selectionKey = selectionKey; - } } diff --git a/src/main/java/com/hans0924/fsd/process/config/ConfigEntry.java b/src/main/java/com/hans0924/fsd/process/config/ConfigEntry.java index 1b086d7..11741d5 100644 --- a/src/main/java/com/hans0924/fsd/process/config/ConfigEntry.java +++ b/src/main/java/com/hans0924/fsd/process/config/ConfigEntry.java @@ -1,11 +1,13 @@ package com.hans0924.fsd.process.config; +import org.apache.commons.lang3.math.NumberUtils; + import java.util.ArrayList; import java.util.List; import java.util.Objects; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class ConfigEntry { @@ -36,7 +38,7 @@ public class ConfigEntry { } public int getInt() { - return Integer.parseInt(data); + return NumberUtils.toInt(data); } public void fillParts() { @@ -47,16 +49,16 @@ public class ConfigEntry { nParts = split.length; } - public boolean inList(String entry) { + public int inList(String entry) { if (parts.isEmpty()) { fillParts(); } for (String part : parts) { if (Objects.equals(part, entry)) { - return true; + return 1; } } - return false; + return 0; } public int getNParts() { diff --git a/src/main/java/com/hans0924/fsd/process/config/ConfigGroup.java b/src/main/java/com/hans0924/fsd/process/config/ConfigGroup.java index 9ff8a1c..537024d 100644 --- a/src/main/java/com/hans0924/fsd/process/config/ConfigGroup.java +++ b/src/main/java/com/hans0924/fsd/process/config/ConfigGroup.java @@ -5,7 +5,7 @@ import java.util.List; import java.util.Objects; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-04 */ public class ConfigGroup { @@ -68,4 +68,8 @@ public class ConfigGroup { public boolean isChanged() { return changed; } + + public void setChanged(boolean changed) { + this.changed = changed; + } } diff --git a/src/main/java/com/hans0924/fsd/process/config/ConfigManager.java b/src/main/java/com/hans0924/fsd/process/config/ConfigManager.java index 75401f4..a09b78d 100644 --- a/src/main/java/com/hans0924/fsd/process/config/ConfigManager.java +++ b/src/main/java/com/hans0924/fsd/process/config/ConfigManager.java @@ -3,19 +3,23 @@ package com.hans0924.fsd.process.config; import com.hans0924.fsd.constants.ManageVarType; import com.hans0924.fsd.manager.Manage; import com.hans0924.fsd.process.Process; +import com.hans0924.fsd.support.Support; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; +import java.nio.channels.Selector; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** - * @author 曾韩铄 + * @author Hanshuo Zeng * @since 2020-03-03 */ public class ConfigManager extends Process { @@ -66,12 +70,12 @@ public class ConfigManager extends Process { @Override public boolean run() { - long now = System.currentTimeMillis(); + long now = Support.mtime(); if (now - prevCheck < CONFIG_INTERVAL) { return false; } prevCheck = now; - Path file = Path.of(fileName); + Path file = Paths.get(fileName); if (Files.notExists(file)) { return false; } @@ -95,14 +99,16 @@ public class ConfigManager extends Process { if (!file.isFile() || !file.exists()) { return; } - Manage.manager.setVar(varAccess, System.currentTimeMillis()); + Manage.manager.setVar(varAccess, Support.mtime()); ConfigGroup current = null; try (InputStreamReader read = new InputStreamReader(new FileInputStream(file)); BufferedReader bufferedReader = new BufferedReader(read)) { String line; - while ((line = bufferedReader.readLine()) != null) - { + while ((line = bufferedReader.readLine()) != null) { + if (StringUtils.isEmpty(line)) { + continue; + } if (line.charAt(0) == '#' || line.charAt(0) == '\r' || line.charAt(0) == '\n') { continue; } @@ -133,6 +139,6 @@ public class ConfigManager extends Process { } catch (IOException e) { LOGGER.error("Something went wrong when parse config file: ", e); } - prevCheck = System.currentTimeMillis(); + prevCheck = Support.mtime(); } } diff --git a/src/main/java/com/hans0924/fsd/process/metar/MetarManage.java b/src/main/java/com/hans0924/fsd/process/metar/MetarManage.java new file mode 100644 index 0000000..4ced7a2 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/metar/MetarManage.java @@ -0,0 +1,615 @@ + +package com.hans0924.fsd.process.metar; + +import com.hans0924.fsd.Fsd; +import com.hans0924.fsd.constants.FsdPath; +import com.hans0924.fsd.constants.ManageVarType; +import com.hans0924.fsd.constants.MetarSource; +import com.hans0924.fsd.constants.ServerConstants; +import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.model.Server; +import com.hans0924.fsd.process.Process; +import com.hans0924.fsd.process.config.ConfigEntry; +import com.hans0924.fsd.process.config.ConfigGroup; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.weather.WProfile; +import com.hans0924.fsd.weather.Weather; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.channels.Selector; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.*; + +/** + * @author Hanshuo Zeng + * @since 2020/03/04 + */ +public class MetarManage extends Process { + + private final static Logger LOGGER = LoggerFactory.getLogger(MetarManage.class); + + public static MetarManage metarManager; + + public static final int VAR_AMOUNT = 10; + public static final int MAX_METAR_DOWNLOAD_TIME = 900_000; + + private BufferedWriter ioIn; + + private BufferedReader ioOut; + + private Socket sock; + + private BufferedReader dataReadSock; + + private BufferedWriter dataSock; + + private Mmq rootQ; + + private long prevDownload; + + private long metarFileTime; + + private int nStations; + + private List stationList; + + private int prevHour; + + private int metarSize; + + private boolean newFileReady; + + private int varPrev; + + private int varTotal; + + private int varStations; + + private int[] variation; + + private String metarHost; + + private String metarDir; + + private String ftpEmail; + + private boolean passiveMode; + + private boolean downloading; + + private int source; + + public MetarManage() { + passiveMode = true; + source = MetarSource.SOURCE_NETWORK; + rootQ = null; + prevHour = 1; + nStations = 0; + stationList = new ArrayList(); + variation = new int[VAR_AMOUNT]; + + parseWeatherConfig(); + parseSystemConfig(); + + if (source == MetarSource.SOURCE_DOWNLOAD) { + if (metarHost == null) { + metarHost = "weather.noaa.gov"; + } + + if (metarDir == null) { + metarDir = "data/observations/metar/cycles/"; + } + } + + int var = Manage.manager.addVar("metar.method", ManageVarType.ATT_VARCHAR); + if (source == MetarSource.SOURCE_NETWORK) { + Manage.manager.setVar(var, "network"); + } else if (source == MetarSource.SOURCE_FILE) { + Manage.manager.setVar(var, "file"); + } else if (source == MetarSource.SOURCE_DOWNLOAD) { + Manage.manager.setVar(var, "download"); + } + + varPrev = Manage.manager.addVar("metar.current", ManageVarType.ATT_DATE); + varTotal = Manage.manager.addVar("metar.requests", ManageVarType.ATT_INT); + varStations = Manage.manager.addVar("metar.stations", ManageVarType.ATT_INT); + + Manage.manager.setVar(varPrev, Support.mtime()); + + if (source == MetarSource.SOURCE_FILE) { + buildList(); + } + } + + private void parseWeatherConfig() { + ConfigGroup weatherGroup = Fsd.configManager.getGroup("weather"); + ConfigEntry sourceEntry = null, serverEntry = null, dirEntry = null, ftpModeEntry = null; + if (weatherGroup != null) { + sourceEntry = weatherGroup.getEntry("source"); + serverEntry = weatherGroup.getEntry("server"); + dirEntry = weatherGroup.getEntry("dir"); + ftpModeEntry = weatherGroup.getEntry("ftpmode"); + } + + if (sourceEntry != null) { + String source = sourceEntry.getData(); + if ("network".equals(source)) { + this.source = MetarSource.SOURCE_NETWORK; + } else if ("file".equals(source)) { + this.source = MetarSource.SOURCE_FILE; + } else if ("download".equals(source)) { + this.source = MetarSource.SOURCE_DOWNLOAD; + } else { + LOGGER.error(String.format("Unknown METAR source %s in config file", source)); + } + } + + if (serverEntry != null) { + metarHost = serverEntry.getData(); + } + + if (dirEntry != null) { + metarDir = dirEntry.getData(); + } + + if (ftpModeEntry != null) { + String mode = ftpModeEntry.getData(); + passiveMode = "passive".equals(mode); + } + } + + private void parseSystemConfig() { + ConfigGroup systemGroup = Fsd.configManager.getGroup("system"); + ConfigEntry emailEntry = null; + if (systemGroup != null) { + emailEntry = systemGroup.getEntry("email"); + } + + if (emailEntry != null) { + ftpEmail = emailEntry.getData(); + } + } + + private void buildList() { + Path file = Paths.get(FsdPath.METARFILE); + if (Files.notExists(file)) { + return; + } + try { + BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class); + metarFileTime = attr.lastModifiedTime().toMillis(); + } catch (IOException e) { + return; + } + + nStations = 0; + stationList.clear(); + parseMetar(); + } + + private void parseMetar() { + File file = new File(FsdPath.METARFILE); + if (!file.isFile() || !file.exists()) { + return; + } + try (InputStreamReader read = new InputStreamReader(new FileInputStream(file)); + BufferedReader bufferedReader = new BufferedReader(read)) { + String line; + List arr = new ArrayList<>(); + for (long offset = 0; (line = bufferedReader.readLine()) != null; offset += line.length()) { + if (line.length() < 30) { + continue; + } + + int count = Support.breakArgs(line, arr, 3); + if (count < 3) { + continue; + } + if (line.startsWith(" ")) { + continue; + } + String stationName = null; + if (arr.get(0).length() == 4) { + stationName = arr.get(0); + } else if (arr.get(1).length() == 4) { + stationName = arr.get(1); + } + if (stationName == null) { + continue; + } + Station station = new Station(); + station.setName(stationName); + station.setLocation(offset); + stationList.add(station); + nStations++; + } + stationList.sort(Comparator.comparing(Station::getName)); + Manage.manager.setVar(varStations, nStations); + } catch (FileNotFoundException e) { + LOGGER.error("Config file not found: " + FsdPath.METARFILE); + } catch (IOException e) { + LOGGER.error("Something went wrong when parse metar file: ", e); + } + } + + private void setupNewFile() { + newFileReady = false; + File metarFile = new File(FsdPath.METARFILE); + File metarNewFile = new File(FsdPath.METARFILENEW); + if (metarSize < 10_0000) { + metarNewFile.delete(); + LOGGER.warn(String.format("METAR: Size of new METAR file (%d) is too small, dropping.", metarSize)); + } + + if (!metarNewFile.renameTo(metarFile)) { + LOGGER.warn(String.format("METAR: Can't move %s to %s", FsdPath.METARFILENEW, FsdPath.METARFILE)); + } else { + LOGGER.info("METAR: Installed new METAR data."); + } + buildList(); + Manage.manager.setVar(varPrev, Support.mtime()); + } + + @Override + public boolean run() { + if (source == MetarSource.SOURCE_NETWORK) { + return true; + } + + if (downloading) { + doDownload(); + } + + while (rootQ != null) { + if (doParse(rootQ) == 0) { + break; + } + } + + if (!downloading && newFileReady) { + setupNewFile(); + } + + return true; + } + + public void startDownload() { + if (downloading) { + LOGGER.error("METAR: server seems to be still loading"); + return; + } + + prevDownload = Support.mtime(); + try { + File metarFileNew = new File(FsdPath.METARFILENEW); + if (!metarFileNew.exists()) { + metarFileNew.createNewFile(); + } + ioIn = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(metarFileNew))); + } catch (IOException e) { + LOGGER.error("METAR: Open metarnew.txt failed."); + return; + } + InetAddress address; + try { + address = InetAddress.getByName(metarHost); + } catch (UnknownHostException e) { + LOGGER.error(String.format("METAR: Could not lookup METAR host name %s.", metarHost)); + stopDownload(); + return; + } + + try { + sock = new Socket(address, 21); + dataReadSock = new BufferedReader(new InputStreamReader(sock.getInputStream())); + dataSock = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream())); + } catch (IOException e) { + LOGGER.error("METAR: Could not connect to ftp port on METAR host"); + stopDownload(); + return; + } + + try { + if (passiveMode) { + String data = String.format("USER anonymous\nPASS %s\nCWD %s\nPASV\n", ftpEmail, metarDir); + dataSock.write(data); + downloading = true; + metarSize = 0; + dataReadSock.close(); + dataReadSock = null; + } else { + String url = "127.0.0.1"; + int dataPort = (int) (Math.random() * 100000 % 9999) + 1024; + String port = String.format("127,0,0,1,%d", dataPort); + String data = String.format("USER anonymous\nPASS %s\nCWD %s\nPORT %s\nRETR %02dZ.TXT\n", + ftpEmail, metarDir, port, (Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + 21) % 24); + dataSock.write(data); + LOGGER.info("METAR: Starting download of METAR data"); + downloading = true; + metarSize = 0; + dataReadSock.close(); + dataReadSock = null; + } + } catch (IOException e) { + LOGGER.error("METAR: Socket operation failed."); + stopDownload(); + } + } + + private void doDownload() { + long now = Support.mtime(); + if (now - prevDownload > MAX_METAR_DOWNLOAD_TIME) { + LOGGER.warn("METAR download interrupted due to timeout"); + stopDownload(); + startDownload(); + return; + } + try { + if (dataReadSock.ready()) { + char[] buf = new char[4096]; + int bytes = dataReadSock.read(buf); + if (bytes <= 0) { + stopDownload(); + newFileReady = true; + } else if (passiveMode) { + String response = new String(buf); + String passHost; + int passPort; + if (!response.startsWith("2271 ")) { + LOGGER.error(String.format("METAR: Could not request passive mode: %s", response)); + stopDownload(); + return; + } + int opening = response.indexOf('('); + int closing = response.indexOf(')', opening + 1); + if (closing > 0) { + String dataLink = response.substring(opening + 1, closing); + StringTokenizer tokenizer = new StringTokenizer(dataLink, ","); + try { + passHost = tokenizer.nextToken() + "." + tokenizer.nextToken() + "." + + tokenizer.nextToken() + "." + tokenizer.nextToken(); + passPort = NumberUtils.toInt(tokenizer.nextToken()) * 256 + + NumberUtils.toInt(tokenizer.nextToken()); + } catch (Exception e) { + LOGGER.error( + "METAR: Received bad data link information: " + + response); + stopDownload(); + return; + } + Socket dataSocket; + try { + dataSocket = new Socket(passHost, passPort); + dataReadSock = new BufferedReader(new InputStreamReader(dataSocket.getInputStream())); + String data = String.format("RETR %02dZ.TXT\n", (Calendar.getInstance().get(Calendar.HOUR_OF_DAY) + 21) % 24); + dataSock.write(data); + LOGGER.info("METAR: Starting download of METAR data"); + } catch (IOException e) { + LOGGER.error("METAR: Could not connect to ftp port on METAR host"); + stopDownload(); + return; + } + } + } + try { + String data = dataReadSock.readLine(); + metarSize = data.length(); + Files.writeString(Paths.get(FsdPath.METARFILENEW), data); + } catch (IOException e) { + LOGGER.error("METAR: Download METAR data error."); + stopDownload(); + return; + } + stopDownload(); + newFileReady = true; + } + } catch (IOException e) { + LOGGER.error("METAR: Start to download of METAR data failed."); + stopDownload(); + } + } + + public void stopDownload() { + try { + if (dataReadSock != null) { + dataReadSock.close(); + dataReadSock = null; + } + + if (dataSock != null) { + dataSock.close(); + dataSock = null; + } + + if (sock != null) { + sock.close(); + sock = null; + } + ioIn = null; + downloading = false; + } catch (IOException e) { + LOGGER.error("METAR: Close socket failed."); + } + } + + private void checkMetarFile() { + try { + Path file = Paths.get(FsdPath.METARFILE); + BasicFileAttributes attr = Files.readAttributes(file, BasicFileAttributes.class); + long modifyTime = attr.lastModifiedTime().toMillis(); + if (modifyTime != metarFileTime) { + buildList(); + } + } catch (IOException e) { + LOGGER.error("METAR: Check metar file error."); + } + } + + private int doParse(Mmq q) { + Station key = new Station(); + checkMetarFile(); + key.setName(StringUtils.substring(q.getMetarId(), 0, 4).toUpperCase()); + int index = Collections.binarySearch(stationList, key, Comparator.comparing(Station::getName)); + if (index < 0) { + Fsd.serverInterface.sendNoWx(q.getDestination(), q.getFd(), q.getMetarId()); + delQ(q); + return 1; + } + Station location = stationList.get(index); + + try { + ioOut = new BufferedReader(new InputStreamReader(new FileInputStream(new File(FsdPath.METARFILE)))); + } catch (IOException e) { + LOGGER.error("METAR: Open metar.txt error. "); + return 0; + } + + String line; + + try { + ioOut.skip(location.getLocation()); + boolean first = true; + StringBuilder sb = new StringBuilder(); + do { + String piece = getLine(); + if (piece == null) { + break; + } + boolean addition = piece.startsWith(" "); + if (addition && first) { + break; + } + if (!addition && !first) { + break; + } + first = false; + if (addition) { + sb.append(piece.substring(4)); + } else { + sb.append(piece); + } + } while (true); + line = sb.toString(); + ioOut.close(); + ioOut = null; + } catch (IOException e) { + LOGGER.error("METAR: Read metar.txt error."); + return 0; + } + + if (q.isParsed() == 1) { + List arr = new ArrayList<>(); + int count = Support.breakArgs(line, arr, 100); + WProfile profile = new WProfile(location.getName(), 0, null); + profile.parseMetar(arr.toArray(new String[0]), count); + Fsd.serverInterface.sendWeather(q.getDestination(), q.getFd(), profile); + } else { + Fsd.serverInterface.sendMetar(q.getDestination(), q.getFd(), location.getName(), line); + } + + Manage.manager.incVar(varTotal); + delQ(q); + return 1; + } + + private String getLine() throws IOException { + String line = ioOut.readLine(); + if (line == null) { + return null; + } + return prepareLine(line); + } + + private String prepareLine(String line) { + return StringUtils.stripEnd(line, "=\r\n"); + } + + public Server requestMetar(String client, String metar, int parsed, int fd) { + if (source == MetarSource.SOURCE_NETWORK) { + int hops = -1; + Server best = null; + for (Server tempServer : Server.servers) { + if (tempServer != Server.myServer && ((tempServer.getFlags() & ServerConstants.SERVER_METAR) != 0)) { + if (hops == -1 || (tempServer.getHops() < hops && tempServer.getHops() != -1)) { + best = tempServer; + hops = tempServer.getHops(); + } + } + } + + if (best == null) { + return null; + } + Fsd.serverInterface.sendReqMetar(client, metar, fd, parsed, best); + return best; + } else { + addQ(client, metar, parsed, fd); + } + return Server.myServer; + } + + private void addQ(String dest, String metar, int parsed, int fd) { + WProfile prof = Weather.getWProfile(metar); + if (prof != null && prof.getActive() == 1) { + if (parsed == 1) { + Fsd.serverInterface.sendWeather(dest, fd, prof); + } else { + Fsd.serverInterface.sendMetar(dest, fd, metar, prof.getRawCode()); + } + return; + } + Mmq temp = new Mmq(); + temp.setDestination(dest); + temp.setMetarId(metar); + temp.setFd(fd); + temp.setParsed(parsed); + temp.setNext(rootQ); + temp.setPrev(null); + if (temp.getNext() != null) { + rootQ = temp; + } + } + + private void delQ(Mmq p) { + if (p.getNext() != null) { + p.getNext().setPrev(p.getPrev()); + } + if (p.getPrev() != null) { + p.getPrev().setNext(p.getNext()); + } else { + rootQ = p.getNext(); + } + } + + public int getVariation(int num, int min, int max) { + int val = variation[num], range = max - min + 1; + val = (Math.abs(val) % range) + min; + return val; + } + + public boolean isDownloading() { + return downloading; + } + + public void setDownloading(boolean downloading) { + this.downloading = downloading; + } + + public int getSource() { + return source; + } + + public void setSource(int source) { + this.source = source; + } +} diff --git a/src/main/java/com/hans0924/fsd/process/metar/Mmq.java b/src/main/java/com/hans0924/fsd/process/metar/Mmq.java new file mode 100644 index 0000000..c1b1cd7 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/metar/Mmq.java @@ -0,0 +1,69 @@ + +package com.hans0924.fsd.process.metar; + +/** + * @author Hanshuo Zeng + * @since 2020/03/04 + */ +public class Mmq { + + private String destination; + + private String metarId; + + private int fd; + + private int parsed; + + private Mmq prev; + + private Mmq next; + + public String getDestination() { + return destination; + } + + public void setDestination(String destination) { + this.destination = destination; + } + + public String getMetarId() { + return metarId; + } + + public void setMetarId(String metarId) { + this.metarId = metarId; + } + + public int getFd() { + return fd; + } + + public void setFd(int fd) { + this.fd = fd; + } + + public int isParsed() { + return parsed; + } + + public void setParsed(int parsed) { + this.parsed = parsed; + } + + public Mmq getPrev() { + return prev; + } + + public void setPrev(Mmq prev) { + this.prev = prev; + } + + public Mmq getNext() { + return next; + } + + public void setNext(Mmq next) { + this.next = next; + } +} diff --git a/src/main/java/com/hans0924/fsd/process/metar/Station.java b/src/main/java/com/hans0924/fsd/process/metar/Station.java new file mode 100644 index 0000000..22e2bf3 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/metar/Station.java @@ -0,0 +1,29 @@ + +package com.hans0924.fsd.process.metar; + +/** + * @author Hanshuo Zeng + * @since 2020/03/04 + */ +public class Station { + + private String name; + + private long location; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public long getLocation() { + return location; + } + + public void setLocation(long location) { + this.location = location; + } +} diff --git a/src/main/java/com/hans0924/fsd/process/network/Allow.java b/src/main/java/com/hans0924/fsd/process/network/Allow.java new file mode 100644 index 0000000..9147c6f --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/network/Allow.java @@ -0,0 +1,17 @@ +package com.hans0924.fsd.process.network; + +/** + * @author Hanshuo Zeng + * @since 2020-03-05 + */ +public class Allow { + private String ip; + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } +} diff --git a/src/main/java/com/hans0924/fsd/process/network/ClientInterface.java b/src/main/java/com/hans0924/fsd/process/network/ClientInterface.java new file mode 100644 index 0000000..b27d413 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/network/ClientInterface.java @@ -0,0 +1,240 @@ +package com.hans0924.fsd.process.network; + +import com.hans0924.fsd.constants.ClientConstants; +import com.hans0924.fsd.constants.GlobalConstants; +import com.hans0924.fsd.constants.NetworkConstants; +import com.hans0924.fsd.constants.ProtocolConstants; +import com.hans0924.fsd.model.Client; +import com.hans0924.fsd.model.Flightplan; +import com.hans0924.fsd.model.Server; +import com.hans0924.fsd.user.AbstractUser; +import com.hans0924.fsd.user.ClientUser; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.weather.CloudLayer; +import com.hans0924.fsd.weather.TempLayer; +import com.hans0924.fsd.weather.WProfile; +import com.hans0924.fsd.weather.WindLayer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.channels.SocketChannel; +import java.util.Random; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class ClientInterface extends TcpInterface { + private final static Logger LOGGER = LoggerFactory.getLogger(ClientInterface.class); + + private long prevWindDelta; + + public ClientInterface(int port, String code, String d) { + super(port, code, d); + prevWindDelta = Support.mtime(); + } + + public boolean run() { + boolean busy = super.run(); + if ((Support.mtime() - prevWindDelta) > GlobalConstants.WIND_DELTA_TIMEOUT) { + prevWindDelta = Support.mtime(); + sendWindDelta(); + } + + return busy; + } + + public void newUser(SocketChannel fd, String peer, int portnum, int g) { + insertUser(new ClientUser(fd, this, peer, portnum, g)); + } + + public void sendAa(Client who, AbstractUser ex) { + + String data = String.format("%s:SERVER:%s:%s::%d", who.getCallsign(), who.getRealName(), + who.getCid(), who.getRating());//, who.getProtocol()); + sendPacket(null, null, ex, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_ADDATC, data); + } + + public void sendAp(Client who, AbstractUser ex) { + String data = String.format("%s:SERVER:%s::%d:%s:%d", who.getCallsign(), who.getCid(), who.getRating(), + who.getProtocol(), who.getSimType()); + sendPacket(null, null, ex, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_ADDPILOT, data); + } + + public void sendDa(Client who, AbstractUser ex) { + + String data = String.format("%s:%s", who.getCallsign(), who.getCid()); + sendPacket(null, null, ex, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_RMATC, data); + } + + public void sendDp(Client who, AbstractUser ex) { + + String data = String.format("%s:%s", who.getCallsign(), who.getCid()); + sendPacket(null, null, ex, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_RMPILOT, data); + } + + public void sendWeather(Client who, WProfile p) { + int x; + StringBuilder buf = new StringBuilder(); + String part; + p.fix(who.getLat(), who.getLon()); + buf = new StringBuilder(String.format("%s:%s", "server", who.getCallsign())); + for (x = 0; x < 4; x++) { + TempLayer l = p.getTemps().get(x); + part = String.format(":%d:%d", l.getCeiling(), l.getTemp()); + buf.append(part); + } + part = String.format(":%d", p.getBarometer()); + buf.append(part); + sendPacket(who, null, null, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_TEMPDATA, buf.toString()); + + buf = new StringBuilder(String.format("%s:%s", "server", who.getCallsign())); + for (x = 0; x < 4; x++) { + WindLayer l = p.getWinds().get(x); + part = String.format(":%d:%d:%d:%d:%d:%d", l.getCeiling(), l.getFloor(), l.getDirection(), + l.getSpeed(), l.getGusting(), l.getTurbulence()); + buf.append(part); + } + sendPacket(who, null, null, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_WINDDATA, buf.toString()); + + buf = new StringBuilder(String.format("%s:%s", "server", who.getCallsign())); + for (x = 0; x < 3; x++) { + CloudLayer c = (x == 2 ? p.getTstorm() : p.getClouds().get(x)); + part = String.format(":%d:%d:%d:%d:%d", c.getCeiling(), c.getFloor(), c.getCoverage(), + c.getIcing(), c.getTurbulence()); + buf.append(part); + } + part = String.format(":%.2f", p.getVisibility()); + buf.append(part); + sendPacket(who, null, null, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_CLOUDDATA, buf.toString()); + } + + public void sendMetar(Client who, String data) { + String buf = String.format("server:%s:METAR:%s", who.getCallsign(), data); + sendPacket(who, null, null, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_REPACARS, buf); + } + + public void sendNoWx(Client who, String station) { + for (AbstractUser temp : users) { + ClientUser ctemp = (ClientUser) temp; + if (ctemp.getThisClient() == who) { + ctemp.showError(ProtocolConstants.ERR_NOWEATHER, station); + break; + } + } + } + + public int getBroad(String s) { + int broad = ClientConstants.CLIENT_ALL; + if ("*P".equals(s)) broad = ClientConstants.CLIENT_PILOT; + else if ("*A".equals(s)) broad = ClientConstants.CLIENT_ATC; + return broad; + } + + public void sendGeneric(String to, Client dest, AbstractUser ex, + Client source, String from, String s, int cmd) { + int range = -1; + String buf = String.format("%s:%s:%s", from, to, s); + if (to.charAt(0) == '@' && source != null) + range = source.getRange(); + sendPacket(dest, source, ex, getBroad(to), range, cmd, buf); + } + + public void sendPilotPos(Client who, AbstractUser ex) { + String data = String.format("%s:%s:%d:%d:%.5f:%.5f:%d:%d:%d:%d", who.getIdentFlag(), + who.getCallsign(), who.getTransponder(), who.getRating(), who.getLat(), who.getLon(), + who.getAltitude(), who.getGroundSpeed(), who.getPbh(), who.getFlags()); +//dolog(L_INFO,"PBH unsigned value is %u",who.getpbh); +//dolog(L_INFO,"SendPilotPos is sending: %s",data); + sendPacket(null, who, ex, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_PILOTPOS, data); + } + + public void sendAtcPos(Client who, AbstractUser ex) { + String data = String.format("%s:%d:%d:%d:%d:%.5f:%.5f:%d", who.getCallsign(), + who.getFrequency(), who.getFacilityType(), who.getVisualRange(), who.getRating(), + who.getLat(), who.getLon(), who.getAltitude()); + sendPacket(null, who, ex, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_ATCPOS, data); + } + + public void sendPlan(Client dest, Client who, int range) { + String cs = (dest != null ? dest.getCallsign() : "*A"); + Flightplan plan = who.getPlan(); + String buf = String.format("%s:%s:%c:%s:%d:%s:%d:%d:%s:%s:%d:%d:%d:%d:%s:%s:%s", + who.getCallsign(), cs, plan.getType(), plan.getAircraft(), + plan.getTasCruise(), plan.getDepAirport(), plan.getDepTime(), plan.getActDepTime(), + plan.getAlt(), plan.getDestAirport(), plan.getHrsEnroute(), plan.getMinEnroute(), + plan.getHrsFuel(), plan.getMinFuel(), plan.getAltAirport(), plan.getRemarks(), + plan.getRoute()); + sendPacket(dest, null, null, ClientConstants.CLIENT_ATC, range, ProtocolConstants.CL_PLAN, buf); + } + + public void handleKill(Client who, String reason) { + if (who.getLocation() != Server.myServer) return; + for (AbstractUser temp : users) { + ClientUser ctemp = (ClientUser) temp; + if (ctemp.getThisClient() == who) { + String buf = String.format("SERVER:%s:%s", who.getCallsign(), reason); + sendPacket(who, null, null, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_KILL, buf); + temp.kill(NetworkConstants.KILL_KILL); + } + } + } + + public void sendWindDelta() { + Random random = new Random(Support.mtime()); + String buf; + int speed = random.nextInt() % 11 - 5; + int direction = random.nextInt() % 21 - 10; + buf = String.format("SERVER:*:%d:%d", speed, direction); + sendPacket(null, null, null, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_WDELTA, buf); + } + + public int calcRange(Client from, Client to, int type, int range) { + int x, y; + switch (type) { + case ProtocolConstants.CL_PILOTPOS: + case ProtocolConstants.CL_ATCPOS: + if (to.getType() == ClientConstants.CLIENT_ATC) return to.getVisualRange(); + x = to.getRange(); + y = from.getRange(); + if (from.getType() == ClientConstants.CLIENT_PILOT) return x + y; + return Math.max(x, y); + case ProtocolConstants.CL_MESSAGE: + x = to.getRange(); + y = from.getRange(); + if (from.getType() == ClientConstants.CLIENT_PILOT && to.getType() == ClientConstants.CLIENT_PILOT) + return x + y; + return Math.max(x, y); + default: + return range; + } + } + + /* Send a packet to a client. + If is specified, only the client will receive the message. + indicates if only pilot, only atc, or both will receive the + message + */ + public void sendPacket(Client dest, Client source, AbstractUser exclude, + int broad, int range, int cmd, String data) { + if (dest != null) { + if (dest.getLocation() != Server.myServer) return; + } + for (AbstractUser temp : users) + if (temp.getKillFlag() == 0) { + Client cl = ((ClientUser) temp).getThisClient(); + if (cl == null) continue; + if (exclude == temp) continue; + if (dest != null && cl != dest) continue; + if ((cl.getType() & broad) == 0) continue; + if (source != null && (range != -1 || cmd == ProtocolConstants.CL_PILOTPOS || cmd == ProtocolConstants.CL_ATCPOS)) { + int checkRange = calcRange(source, cl, cmd, range); + double distance = cl.distance(source); + if (distance == -1 || distance > checkRange) continue; + } + temp.uslprintf("%s%s\r\n", cmd == ProtocolConstants.CL_ATCPOS || cmd == ProtocolConstants.CL_PILOTPOS ? 1 : 0, + ClientConstants.CL_CMD_NAMES[cmd], data); + } + } + +} diff --git a/src/main/java/com/hans0924/fsd/process/network/Guard.java b/src/main/java/com/hans0924/fsd/process/network/Guard.java new file mode 100644 index 0000000..9a83c2b --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/network/Guard.java @@ -0,0 +1,37 @@ +package com.hans0924.fsd.process.network; + +/** + * @author Hanshuo Zeng + * @since 2020-03-05 + */ +public class Guard { + private long prevTry; + + private String host; + + private int port; + + public long getPrevTry() { + return prevTry; + } + + public void setPrevTry(long prevTry) { + this.prevTry = prevTry; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } +} diff --git a/src/main/java/com/hans0924/fsd/process/network/ServerInterface.java b/src/main/java/com/hans0924/fsd/process/network/ServerInterface.java new file mode 100644 index 0000000..14a3f62 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/network/ServerInterface.java @@ -0,0 +1,521 @@ +package com.hans0924.fsd.process.network; + +import com.hans0924.fsd.Fsd; +import com.hans0924.fsd.constants.*; +import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.model.Certificate; +import com.hans0924.fsd.model.Client; +import com.hans0924.fsd.model.Flightplan; +import com.hans0924.fsd.model.Server; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.user.AbstractUser; +import com.hans0924.fsd.user.ServerUser; +import com.hans0924.fsd.weather.WProfile; + +import java.nio.channels.SocketChannel; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class ServerInterface extends TcpInterface { + private int packetCount, varMcDrops, varIntErr; + + private int varMcHandled, varUcHandled, varUcOverRun, varFailed, varShape, varBounce; + + private String serverIdent; + + private long lastSync; + + public ServerInterface(int port, String code, String d) { + super(port, code, d); + packetCount = 0; + varMcHandled = Manage.manager.addVar("protocol.multicast.handled", ManageVarType.ATT_INT); + varMcDrops = Manage.manager.addVar("protocol.multicast.dropped", ManageVarType.ATT_INT); + varUcHandled = Manage.manager.addVar("protocol.unicast.handled", ManageVarType.ATT_INT); + varUcOverRun = Manage.manager.addVar("protocol.unicast.overruns", ManageVarType.ATT_INT); + varFailed = Manage.manager.addVar("protocol.errors.invalidcommand", ManageVarType.ATT_INT); + varBounce = Manage.manager.addVar("protocol.errors.bounce", ManageVarType.ATT_INT); + varShape = Manage.manager.addVar("protocol.errors.shape", ManageVarType.ATT_INT); + varIntErr = Manage.manager.addVar("protocol.errors.integer", ManageVarType.ATT_INT); + serverIdent = Server.myServer.getIdent(); + } + + @Override + public boolean run() { + long now = Support.mtime(); + boolean busy = super.run(); + if (now - lastSync > GlobalConstants.SYNC_TIMEOUT) { + sendSync(); + } + for (AbstractUser temp : users) { + ServerUser stemp = (ServerUser) temp; + if (stemp.getClientOk() == 0 && now - stemp.getStartupTime() > GlobalConstants.SERVER_MAX_TOOK) { + stemp.kill(NetworkConstants.KILL_INIT_TIMEOUT); + } + } + return busy; + } + + @Override + public void newUser(SocketChannel client, String peerName, int port, int g) { + insertUser(new ServerUser(client, this, peerName, port, g)); + } + + public void incPacketCount() { + packetCount++; + /* Now check if the integer looped */ + if (packetCount < 0 || packetCount > 2_000_000_000) { + sendReset(); + } + } + + /* Send a SYNC packet */ + public void sendSync() { + sendPacket(null, null, ProtocolConstants.CMD_SYNC, "*", serverIdent, packetCount, 0, 0, ""); + lastSync = Support.mtime(); + incPacketCount(); + } + + /* Send a NOTIFY packet to a destination */ + public void sendServerNotify(String dest, Server subject, AbstractUser toWho) { + String buf = String.format("%d:%s:%s:%s:%s:%s:%d:%s", subject == Server.myServer ? 0 : 1, subject.getIdent(), + subject.getName(), subject.getEmail(), subject.getHostName(), subject.getVersion(), + subject.getFlags(), subject.getLocation()); + sendPacket(null, toWho, ProtocolConstants.CMD_NOTIFY, dest, serverIdent, packetCount, 0, 1, buf); + incPacketCount(); + } + + /* Send a REQMETAR packet */ + public void sendReqMetar(String client, String metar, int fd, int parsed, Server dest) { + String buf = String.format("%s:%s:%d:%d", client, metar, parsed, fd); + sendPacket(null, null, ProtocolConstants.CMD_REQ_METAR, dest.getIdent(), serverIdent, + packetCount, 0, 0, buf); + incPacketCount(); + } + + /* Send a LINKDOWN packet to a destination */ + public void sendLinkDown(String data) { + sendPacket(null, null, ProtocolConstants.CMD_LINK_DOWN, "*", serverIdent, packetCount, + 0, 1, data); + incPacketCount(); + } + + /* Send a PONG packet to a destination */ + public void sendPong(String dest, String data) { + sendPacket(null, null, ProtocolConstants.CMD_PONG, dest, serverIdent, packetCount, 0, 0, data); + incPacketCount(); + } + + /* Broadcast a PING packet to a destination*/ + public void sendPing(String dest, String data) { + sendPacket(null, null, ProtocolConstants.CMD_PING, dest, serverIdent, packetCount, 0, 0, data); + incPacketCount(); + } + + /* Send an ADDCLIENT packet to a destination */ + public void sendAddClient(String dest, Client who, AbstractUser direction, AbstractUser source, int feed) { + String buf = String.format("%s:%s:%s:%d:%d:%s:%s:%d", who.getCid(), who.getLocation().getIdent(), + who.getCallsign(), who.getType(), who.getRating(), who.getProtocol(), who.getRealName(), who.getSimType()); + sendPacket(null, direction, ProtocolConstants.CMD_ADD_CLIENT, dest, serverIdent, packetCount, 0, 0, buf); + incPacketCount(); + /* This command is important for clients as well, so if it's not a feed, + clients should get it as well */ + if (feed == 0) { + if (who.getType() == ClientConstants.CLIENT_ATC) { + Fsd.clientInterface.sendAa(who, source); + } else if (who.getType() == ClientConstants.CLIENT_PILOT) { + Fsd.clientInterface.sendAp(who, source); + } + } + } + + /* Send an RMCLIENT packet to a destination */ + public void sendRmClient(AbstractUser direction, String dest, Client who, AbstractUser ex) { + String buf = who.getCallsign(); + sendPacket(null, direction, ProtocolConstants.CMD_RM_CLIENT, dest, serverIdent, packetCount, 0, 0, buf); + incPacketCount(); + /* This command is important for clients as well, so if it's send to the + broadcast address, clients should get it as well */ + if (dest.equals("*")) { + if (who.getType() == ClientConstants.CLIENT_ATC) { + Fsd.clientInterface.sendDa(who, ex); + } else { + Fsd.clientInterface.sendDp(who, ex); + } + } + } + + public void sendPilotData(Client who, AbstractUser ex) { + String data = String.format("%s:%s:%d:%d:%.5f:%.5f:%d:%d:%d:%d", who.getIdentFlag(), + who.getCallsign(), who.getTransponder(), who.getRating(), who.getLat(), who.getLon(), + who.getAltitude(), who.getGroundSpeed(), who.getPbh(), who.getFlags()); + sendPacket(null, null, ProtocolConstants.CMD_PD, "*", serverIdent, packetCount, 0, 0, data); + Fsd.clientInterface.sendPilotPos(who, ex); + incPacketCount(); + } + + public void sendAtcData(Client who, AbstractUser ex) { + String data = String.format("%s:%d:%d:%d:%d:%.5f:%.5f:%d", who.getCallsign(), + who.getFrequency(), who.getFacilityType(), who.getVisualRange(), who.getRating(), who.getLat(), who.getLon(), + who.getAltitude()); + sendPacket(null, null, ProtocolConstants.CMD_AD, "*", serverIdent, packetCount, 0, 0, data); + Fsd.clientInterface.sendAtcPos(who, ex); + incPacketCount(); + } + + public void sendCert(String dest, int cmd, Certificate who, AbstractUser direction) { + String data = String.format("%d:%s:%s:%d:%d:%s", cmd, who.getCid(), who.getPassword(), who.getLevel(), + who.getCreation(), who.getOrigin()); + sendPacket(null, direction, ProtocolConstants.CMD_CERT, dest, serverIdent, packetCount, 0, 0, data); + incPacketCount(); + } + + public void sendReset() { + sendPacket(null, null, ProtocolConstants.CMD_RESET, "*", serverIdent, packetCount, 0, 0, ""); + packetCount = 0; + } + + public int sendMulticast(Client source, String dest, String s, int cmd, int multiOk, AbstractUser ex) { + Client destination = null; + String servDest; + String sourceStr = source != null ? source.getCallsign() : "server"; + if (source != null && dest.equalsIgnoreCase("server")) { + switch (cmd) { + case ProtocolConstants.CL_PING: + Fsd.clientInterface.sendGeneric(source.getCallsign(), source, null, + null, "server", s, ProtocolConstants.CL_PONG); + break; + } + return 1; + } + switch (dest.charAt(0)) { + case '@': + case '*': + if (multiOk == 0) { + return 0; + } + servDest = dest; + break; + default: + servDest = String.format("%%%s", dest); + destination = Client.getClient(dest); + if (destination == null) { + return 0; + } + break; + } + String data = String.format("%d:%s:%s", cmd, sourceStr, s); + sendPacket(null, null, ProtocolConstants.CMD_MULTICAST, servDest, serverIdent, packetCount, 0, 0, data); + incPacketCount(); + Fsd.clientInterface.sendGeneric(dest, destination, ex, source, sourceStr, s, cmd); + return 1; + } + + public void sendPlan(String dest, Client who, AbstractUser direction) { + Flightplan plan = who.getPlan(); + if (plan == null) { + return; + } + String buf = String.format("%s:%d:%c:%s:%d:%s:%d:%d:%s:%s:%d:%d:%d:%d:%s:%s:%s", + who.getCallsign(), plan.getRevision(), plan.getType(), plan.getAircraft(), + plan.getTasCruise(), plan.getDepAirport(), plan.getDepTime(), + plan.getActDepTime(), plan.getAlt(), plan.getDestAirport(), plan.getHrsEnroute(), + plan.getMinEnroute(), plan.getHrsFuel(), plan.getMinFuel(), plan.getAltAirport(), + plan.getRemarks(), plan.getRoute()); + sendPacket(null, direction, ProtocolConstants.CMD_PLAN, dest, serverIdent, packetCount, 0, 0, buf); + incPacketCount(); + Fsd.clientInterface.sendPlan(null, who, 400); + } + + public void sendWeather(String dest, int fd, WProfile w) { + if (dest.equalsIgnoreCase(serverIdent)) { + Fsd.systemInterface.receiveWeather(fd, w); + return; + } + + if (dest.charAt(0) == '%') { + Client c = Client.getClient(dest.substring(1)); + if (c == null) { + return; + } + if (c.getLocation() == Server.myServer) { + Fsd.clientInterface.sendWeather(c, w); + return; + } + } + + String weather = w.print(); + String data = String.format("%s:%d:%s", w.getName(), fd, weather); + sendPacket(null, null, ProtocolConstants.CMD_WEATHER, dest, serverIdent, packetCount, 0, 0, data); + incPacketCount(); + } + + public void sendMetar(String dest, int fd, String station, String data) { + if (dest.equalsIgnoreCase(serverIdent)) { + Fsd.systemInterface.receiveMetar(fd, station, data); + return; + } + if (dest.charAt(0) == '%') { + Client c = Client.getClient(dest.substring(1)); + if (c == null) { + return; + } + if (c.getLocation() == Server.myServer) { + Fsd.clientInterface.sendMetar(c, data); + return; + } + } + String buf = String.format("%s:%d:%s", station, fd, data); + sendPacket(null, null, ProtocolConstants.CMD_METAR, dest, serverIdent, packetCount, 0, 0, buf); + incPacketCount(); + } + + public void sendNoWx(String dest, int fd, String station) { + if (dest.equalsIgnoreCase(serverIdent)) { + Fsd.systemInterface.receiveNoWx(fd, station); + return; + } + if (dest.charAt(0) == '%') { + Client c = Client.getClient(dest.substring(1)); + if (c == null) { + return; + } + if (c.getLocation() == Server.myServer) { + Fsd.clientInterface.sendNoWx(c, station); + return; + } + } + String buf = String.format("%s:%d", station, fd); + sendPacket(null, null, ProtocolConstants.CMD_NO_WX, dest, serverIdent, packetCount, 0, 0, buf); + incPacketCount(); + } + + public void sendAddWp(AbstractUser direction, WProfile wp) { + String weather = wp.print(); + String buf = String.format("%s:%d:%s:%s", wp.getName(), wp.getVersion(), wp.getOrigin(), weather); + sendPacket(null, direction, ProtocolConstants.CMD_ADD_WPROFILE, "*", serverIdent, packetCount, 0, 0, buf); + incPacketCount(); + } + + public void sendDelWp(WProfile wp) { + sendPacket(null, null, ProtocolConstants.CMD_DEL_WPROFILE, "*", serverIdent, packetCount, 0, 0, wp.getName()); + incPacketCount(); + } + + public void sendKill(Client who, String reason) { + if (who.getLocation() == Server.myServer) { + Fsd.clientInterface.handleKill(who, reason); + return; + } + String data = String.format("%s:%s", who.getCallsign(), reason); + sendPacket(null, null, ProtocolConstants.CMD_KILL, who.getLocation().getIdent(), serverIdent, + packetCount, 0, 1, data); + incPacketCount(); + } + + /* Send the packet on its way. This function will also do the routing */ + public void sendPacket(AbstractUser exclude, AbstractUser direction, int cmdNum, + String to, String from, int pc, int hc, int bi, String data) { + StringBuilder buf = new StringBuilder(); + /* variable to determine wheter or not to do the softlimit check on the + server connection output buffer. currently only do the check on + client position updates */ + int slCheck = (cmdNum == ProtocolConstants.CMD_PD || cmdNum == ProtocolConstants.CMD_AD) ? 1 : 0; + + /* Increase the hopcount */ + hc++; + + /* Assemble the packet */ + assemble(buf, cmdNum, to, from, bi, pc, hc, data); + + if (direction != null) { + direction.uslprintf("%s\n", slCheck); + return; + } + + /* Now look at the destionation field, to determine the route for this + packet */ + + switch (to.charAt(0)) { + case '@': /* Fallthrough to broadcast */ + + /* This is a broadcast packet. + Note: '*P' and '*A' are broadcast destinations too! */ + case '*': + /* Reassemble the packet to indicate a broadcast */ + if (bi == 0) { + assemble(buf, cmdNum, to, from, 1, pc, hc, data); + } + for (AbstractUser tempUser : users) { + if (isServerOk((ServerUser) tempUser, exclude, cmdNum)) { + tempUser.uslprintf("%s\r\n", slCheck, buf); + } + } + break; + /* This is a packet for a pilot. Lookup the pilot, and send in the + direction of the appropriate server */ + case '%': + for (Client tempClient : Client.clients) { + if (tempClient.getCallsign().equals(to.substring(1))) { + /* We got the pilot, and his connected server, now if we know the + correct path, send it; otherwise we'll have to broadcast the + packet */ + Server tempServer = tempClient.getLocation(); + /* It the pilot is connected to this server, don't send out + the packet to other servers */ + if (tempServer == Server.myServer) { + break; + } + if (tempServer.getPath() != null) { + tempServer.getPath().uslprintf("%s\r\n", slCheck, buf); + } else { + /* Reassemble the packet to indicate a broadcast */ + if (bi == 0) { + assemble(buf, cmdNum, to, from, 1, pc, hc, data); + } + for (AbstractUser tempUser : users) { + if (isServerOk((ServerUser) tempUser, exclude, cmdNum)) { + tempUser.uslprintf("%s\r\n", slCheck, buf); + } + } + } + break; + } + } + break; + + /* This packet is on its way to a single server */ + default: + for (Server tempServer : Server.servers) { + if (tempServer.getIdent().equals(to)) { + if (tempServer.getPath() != null) { + tempServer.getPath().uslprintf("%s\r\n", slCheck, buf); + } else { + /* Reassemble the packet to indicate a broadcast */ + if (bi == 0) { + assemble(buf, cmdNum, to, from, 1, pc, hc, data); + } + for (AbstractUser tempUser : users) { + if (isServerOk((ServerUser) tempUser, exclude, cmdNum)) { + tempUser.uslprintf("%s\r\n", slCheck, buf); + } + } + } + } + break; + } + break; + } + } + + /* This routine is called whenever a client is dropped. We have to check + here if there's a silent server connected to us. In that case, we'll + have to send him a RMCLIENT */ + public void clientDropped(Client who) { + for (AbstractUser temp : users) { + ServerUser s = (ServerUser) temp; + if (s.getThisServer() != null && (s.getThisServer().getFlags() & ServerConstants.SERVER_SILENT) != 0) { + sendRmClient(s, s.getThisServer().getName(), who, null); + } + } + } + + private boolean isServerOk(ServerUser x, AbstractUser exclude, int cmdNum) { + return (x != exclude) && (ProtocolConstants.SILENT_OK[cmdNum] == 1 || x.getThisServer() == null || + (x.getThisServer().getFlags() & ServerConstants.SERVER_SILENT) == 0); + } + + public void assemble(StringBuilder buf, int cmdNum, String to, String from, int bi, int pc, int hc, String data) { + buf.append(String.format("%s:%s:%s:%c%d:%d:%s", ProtocolConstants.CMD_NAMES[cmdNum], to, from, + bi > 0 ? 'B' : 'U', pc, hc, data)); + } + + public int getPacketCount() { + return packetCount; + } + + public void setPacketCount(int packetCount) { + this.packetCount = packetCount; + } + + public int getVarMcDrops() { + return varMcDrops; + } + + public void setVarMcDrops(int varMcDrops) { + this.varMcDrops = varMcDrops; + } + + public int getVarIntErr() { + return varIntErr; + } + + public void setVarIntErr(int varIntErr) { + this.varIntErr = varIntErr; + } + + public int getVarMcHandled() { + return varMcHandled; + } + + public void setVarMcHandled(int varMcHandled) { + this.varMcHandled = varMcHandled; + } + + public int getVarUcHandled() { + return varUcHandled; + } + + public void setVarUcHandled(int varUcHandled) { + this.varUcHandled = varUcHandled; + } + + public int getVarUcOverRun() { + return varUcOverRun; + } + + public void setVarUcOverRun(int varUcOverRun) { + this.varUcOverRun = varUcOverRun; + } + + public int getVarFailed() { + return varFailed; + } + + public void setVarFailed(int varFailed) { + this.varFailed = varFailed; + } + + public int getVarShape() { + return varShape; + } + + public void setVarShape(int varShape) { + this.varShape = varShape; + } + + public int getVarBounce() { + return varBounce; + } + + public void setVarBounce(int varBounce) { + this.varBounce = varBounce; + } + + public String getServerIdent() { + return serverIdent; + } + + public void setServerIdent(String serverIdent) { + this.serverIdent = serverIdent; + } + + public long getLastSync() { + return lastSync; + } + + public void setLastSync(long lastSync) { + this.lastSync = lastSync; + } +} diff --git a/src/main/java/com/hans0924/fsd/process/network/SystemInterface.java b/src/main/java/com/hans0924/fsd/process/network/SystemInterface.java new file mode 100644 index 0000000..715ca1b --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/network/SystemInterface.java @@ -0,0 +1,98 @@ +package com.hans0924.fsd.process.network; + +import com.hans0924.fsd.model.Server; +import com.hans0924.fsd.user.AbstractUser; +import com.hans0924.fsd.user.SystemUser; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.weather.WProfile; + +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.Scanner; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class SystemInterface extends TcpInterface { + public SystemInterface(int port, String code, String d) { + super(port, code, d); + + } + + public boolean run() { + return super.run(); + } + + public void newUser(SocketChannel fd, String peer, int portnum, int g) { + insertUser(new SystemUser(fd, this, peer, portnum, g)); + } + + public void receivePong(String from, String data, String pc, String hops) { + int fd; + long now; + Scanner scanner = new Scanner(data); + if (!scanner.hasNextInt()) { + return; + } + fd = scanner.nextInt(); + if (!scanner.hasNextLong()) { + return; + } + now = scanner.nextLong(); + if (fd == -1) { + return; + } + for (AbstractUser temp : users) { + if (temp.getFd() == fd) { + temp.uprintf("\r\nPONG received from %s: %d seconds (%s,%s)\r\n", + from, Support.mtime() - now, pc, hops); + temp.printPrompt(); + return; + } + } + } + + public void receiveWeather(int fd, WProfile w) { + if (fd == -2) { + String buffer = w.print(); + List array = new ArrayList<>(); + int count = Support.breakPacket(buffer, array, 100); + WProfile wp = new WProfile(w.getName(), Support.mgmtime(), Server.myServer.getIdent()); + wp.loadArray(array.toArray(new String[0]), count); + return; + } + for (AbstractUser temp : users) { + if (temp.getFd() == fd) { + SystemUser st = (SystemUser) temp; + w.fix(st.getLat(), st.getLon()); + st.printWeather(w); + break; + } + } + } + + public void receiveMetar(int fd, String wp, String w) { + for (AbstractUser temp : users) { + if (temp.getFd() == fd) { + SystemUser st = (SystemUser) temp; + st.printMetar(wp, w); + break; + } + } + } + + public void receiveNoWx(int fd, String st) { + if (fd == -2) { + WProfile wp = new WProfile(st, Support.mgmtime(), Server.myServer.getIdent()); + } + for (AbstractUser temp : users) { + if (temp.getFd() == fd) { + temp.uprintf("\r\nNo weather available for %s.\r\n", st); + temp.printPrompt(); + break; + } + } + } +} diff --git a/src/main/java/com/hans0924/fsd/process/network/TcpInterface.java b/src/main/java/com/hans0924/fsd/process/network/TcpInterface.java new file mode 100644 index 0000000..e5558b4 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/process/network/TcpInterface.java @@ -0,0 +1,525 @@ +package com.hans0924.fsd.process.network; + +import com.hans0924.fsd.constants.GlobalConstants; +import com.hans0924.fsd.constants.ManageVarType; +import com.hans0924.fsd.constants.NetworkConstants; +import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.process.Process; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.user.AbstractUser; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.StandardSocketOptions; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * @author Hanshuo Zeng + * @since 2020-03-05 + */ +public class TcpInterface extends Process { + private static final Logger LOGGER = LoggerFactory.getLogger(TcpInterface.class); + + protected String description; + + protected ServerSocketChannel sock; + + private Selector selector; + + protected SelectionKey selectionKey; + + protected int varCurrent; + + protected int varTotal; + + protected int varPeak; + + protected int[] varClosed; + + protected int feedStrategy; + + protected int floodLimit; + + protected int outBufLimit; + + protected long prevChecks; + + protected String prompt; + + protected List users; + + protected List guards; + + protected List allows; + + public TcpInterface(int port, String code, String desc) { + super(); + boolean on = true; + description = desc; + allows = new ArrayList<>(); + varClosed = new int[9]; + floodLimit = -1; + outBufLimit = -1; + + makeVars(code); + + try { + selector = Selector.open(); + sock = ServerSocketChannel.open(); + } catch (IOException e) { + LOGGER.error("Could not open server socket channel.", e); + System.exit(1); + } + + try { + sock.setOption(StandardSocketOptions.SO_REUSEADDR, on); + } catch (IOException e) { + LOGGER.error(String.format("setsockopt error on port %d", port), e); + System.exit(1); + } catch (UnsupportedOperationException e) { + LOGGER.warn("SO_REUSEADDR is not supported on current platform"); + } + + try { + sock.setOption(StandardSocketOptions.TCP_NODELAY, on); + } catch (IOException e) { + LOGGER.error(String.format("Could not set TCP_NODELAY on port %d", port), e); + System.exit(1); + } catch (UnsupportedOperationException e) { + LOGGER.warn("TCP_NODELAY is not supported on current platform"); + } + + try { + sock.configureBlocking(false); + sock.bind(new InetSocketAddress("localhost", port)); + sock.register(selector, SelectionKey.OP_ACCEPT); + } catch (IOException e) { + LOGGER.error(String.format("Bind error on port %d", port), e); + System.exit(1); + } + + users = new ArrayList<>(); + guards = new ArrayList<>(); + prompt = null; + feedStrategy = 0; + prevChecks = 0; + LOGGER.info(String.format("Booting port %d (%s)", port, description)); + } + + public void close() { + try { + sock.close(); + } catch (IOException e) { + LOGGER.warn("Close tcpInterface error."); + } + } + + public void makeVars(String code) { + String varName; + varName = String.format("interface.%s.current", code); + varCurrent = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.total", code); + varTotal = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.peak", code); + varPeak = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + + varName = String.format("interface.%s.command", code); + varClosed[1] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.flood", code); + varClosed[2] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.initialtimeout", code); + varClosed[3] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.stalled", code); + varClosed[4] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.closed", code); + varClosed[5] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.writeerr", code); + varClosed[6] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.killed", code); + varClosed[7] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + varName = String.format("interface.%s.protocol", code); + varClosed[8] = Manage.manager.addVar(varName, ManageVarType.ATT_INT); + } + + public void addGuard(AbstractUser who) { + Guard guard = new Guard(); + guard.setHost(who.getPeer()); + guard.setPort(who.getPort()); + guard.setPrevTry(Support.mtime()); + } + + public void allow(String name) { + try { + InetAddress address = InetAddress.getByName(name); + Allow allow = new Allow(); + allow.setIp(address.getHostAddress()); + allows.add(allow); + } catch (UnknownHostException e) { + LOGGER.error(String.format("Server %s unknown in allowfrom", name)); + } + } + + public void newUser(SelectionKey key) { + String peer; + InetSocketAddress remoteAddress; + int ok = 0; + SocketChannel clientChannel; + try { + ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); + clientChannel = serverChannel.accept(); + if (clientChannel == null) { + return; + } + clientChannel.configureBlocking(false); + remoteAddress = (InetSocketAddress) clientChannel.getRemoteAddress(); + for (Allow allow : allows) { + if (allow.getIp().equals(remoteAddress.getAddress().getHostAddress())) { + ok = 1; + break; + } + } + peer = Support.findHostname(remoteAddress.getAddress().getHostAddress()); + if (ok == 0 && !allows.isEmpty()) { + String message = "#You are not allowed on this port.\r\n"; + clientChannel.write(ByteBuffer.wrap(message.getBytes())); + LOGGER.info(String.format("Connection rejected from %s", peer)); + clientChannel.close(); + return; + } + } catch (IOException e) { + LOGGER.error("Accept connection failed.", e); + return; + } + + LOGGER.info(String.format("Connection accepted from %s on %s", peer, description)); + + try { + clientChannel.setOption(StandardSocketOptions.TCP_NODELAY, true); + } catch (IOException e) { + LOGGER.error("Could not set off TCP_NODELAY"); + } catch (UnsupportedOperationException e) { + LOGGER.warn("TCP_NODELAY is not supported on current platform"); + } + + newUser(clientChannel, peer, remoteAddress.getPort(), 0); + } + + public void newUser(SocketChannel client, String peerName, int port, int g) { + + } + + public void insertUser(AbstractUser user) { + try { + user.getSocketChannel().register(selector, SelectionKey.OP_READ, user); + } catch (ClosedChannelException e) { + e.printStackTrace(); + } + users.add(user); + if (prompt != null) { + user.setPrompt(prompt); + user.printPrompt(); + } + Manage.manager.incVar(varTotal); + Manage.manager.incVar(varCurrent); + int peak = Manage.manager.getVar(varPeak).getValue().getNumber(); + int now = Manage.manager.getVar(varCurrent).getValue().getNumber(); + if (now > peak) { + Manage.manager.setVar(varPeak, now); + } + } + + @Override + public boolean run() { + int busy = 0; + long now = Support.mtime(); + long timeOut = 1000L; + try { + selector.select(timeOut); + Set selectionKeys = selector.selectedKeys(); + for (Iterator iterator = selectionKeys.iterator(); iterator.hasNext(); ) { + SelectionKey key = iterator.next(); + iterator.remove(); + if (!key.isValid()) { + continue; + } + if (key.isAcceptable()) { + newUser(key); + } + + Object att = key.attachment(); + if (att == null) { + continue; + } + AbstractUser temp = (AbstractUser) att; + if (temp.getKillFlag() != 0) { + users.remove(temp); + temp.close(); + if (varCurrent != -1) { + Manage.manager.decVar(varCurrent); + } + } else { + if (key.isWritable()) { + temp.output(); + } + if (key.isReadable()) { + temp.input(); + } + if (temp.run() > 0) { + busy = 1; + } + } + } + } catch (IOException e) { + LOGGER.error("run select failed", e); + } + + for (Iterator iterator = users.iterator(); iterator.hasNext(); ) { + AbstractUser temp = iterator.next(); + if (temp.getKillFlag() != 0) { + iterator.remove(); + temp.close(); + if (varCurrent != -1) { + Manage.manager.decVar(varCurrent); + } + } + } + + if (prevChecks != now) { + doChecks(); + } + return busy > 0; + } + + private void doChecks() { + long now = Support.mtime(); + prevChecks = now; + for (AbstractUser temp : users) { + if (temp.getKillFlag() == 0) { + if (temp.getTimeOut() != 0) { + temp.kill(NetworkConstants.KILL_DATA_TIMEOUT); + } + if ((feedStrategy & ((now - temp.getPrevFeedCheck() > GlobalConstants.USER_FEED_CHECK) ? 1 : 0)) != 0) { + temp.calcFeed(); + } + if (now - temp.getLastPing() >= GlobalConstants.USER_PING_TIMEOUT) { + temp.sendPing(); + temp.setLastPing(now); + } else if (now - temp.getLastActive() >= GlobalConstants.USER_TIMEOUT) { + temp.uprintf("# Timeout\r\n"); + temp.setTimeOut(1); + } + } + } + for (Iterator iterator = guards.iterator(); iterator.hasNext(); ) { + Guard guard = iterator.next(); + long time = Support.mtime(); + if (time - guard.getPrevTry() > GlobalConstants.GUARD_RETRY) { + guard.setPrevTry(time); + if (addUser(guard.getHost(), guard.getPort(), null) != 0) { + iterator.remove(); + } + } + } + } + + public int addUser(String name, int port, AbstractUser terminal) { + SocketChannel sock; + for (AbstractUser user : users) { + if (user.getPort() == port && user.getPeer().equals(name)) { + if (terminal != null) { + terminal.uprintf("Already connected to %s\r\n", name); + } + return -1; + } + } + + InetAddress address; + try { + address = InetAddress.getByName(name); + } catch (UnknownHostException e) { + if (terminal != null) { + terminal.uprintf("Unknown hostname: %s\r\n", name); + } + return 0; + } + + try { + sock = SocketChannel.open(); + } catch (IOException e) { + LOGGER.error("Could not open socket channel", e); + return 0; + } + + if (terminal != null) { + terminal.uprintf("Connecting to %s port %d.\r\n", name, port); + } + + int count = 0; + Exception error = null; + while (true) { + if (++count == 3) { + break; + } + try { + sock.connect(new InetSocketAddress(address, port)); + sock.configureBlocking(false); + } catch (ClosedByInterruptException e) { + if (count < 3) { + continue; + } + error = e; + break; + } catch (Exception e) { + error = e; + break; + } + } + if (error != null) { + if (terminal != null) { + terminal.uprintf("Could not connect to server: %s\r\n", error.getMessage()); + } + try { + sock.close(); + } catch (IOException e) { + LOGGER.error("Close socket error.", e); + } + } + if (terminal != null) { + terminal.uprintf("Connection established.\r\n"); + } + try { + sock.setOption(StandardSocketOptions.TCP_NODELAY, true); + } catch (IOException e) { + LOGGER.error("Could not set off TCP_NODELAY"); + } catch (UnsupportedOperationException e) { + LOGGER.warn("TCP_NODELAY is not supported on current platform"); + } + newUser(sock, name, port, 1); + return 1; + } + + public void delGuard() { + guards.clear(); + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public ServerSocketChannel getSock() { + return sock; + } + + public void setSock(ServerSocketChannel sock) { + this.sock = sock; + } + + public int getVarCurrent() { + return varCurrent; + } + + public void setVarCurrent(int varCurrent) { + this.varCurrent = varCurrent; + } + + public int getVarTotal() { + return varTotal; + } + + public void setVarTotal(int varTotal) { + this.varTotal = varTotal; + } + + public int getVarPeak() { + return varPeak; + } + + public void setVarPeak(int varPeak) { + this.varPeak = varPeak; + } + + public int[] getVarClosed() { + return varClosed; + } + + public void setVarClosed(int[] varClosed) { + this.varClosed = varClosed; + } + + public int getFeedStrategy() { + return feedStrategy; + } + + public void setFeedStrategy(int feedStrategy) { + this.feedStrategy = feedStrategy; + } + + public int getFloodLimit() { + return floodLimit; + } + + public void setFloodLimit(int floodLimit) { + this.floodLimit = floodLimit; + } + + public int getOutBufLimit() { + return outBufLimit; + } + + public void setOutBufLimit(int outBufLimit) { + this.outBufLimit = outBufLimit; + } + + public long getPrevChecks() { + return prevChecks; + } + + public void setPrevChecks(long prevChecks) { + this.prevChecks = prevChecks; + } + + public String getPrompt() { + return prompt; + } + + public void setPrompt(String prompt) { + this.prompt = prompt; + } + + public List getUsers() { + return users; + } + + public void setUsers(List users) { + this.users = users; + } + + public List getGuards() { + return guards; + } + + public void setGuards(List guards) { + this.guards = guards; + } + + public List getAllows() { + return allows; + } + + public void setAllows(List allows) { + this.allows = allows; + } +} diff --git a/src/main/java/com/hans0924/fsd/support/Support.java b/src/main/java/com/hans0924/fsd/support/Support.java new file mode 100644 index 0000000..0ae1c3f --- /dev/null +++ b/src/main/java/com/hans0924/fsd/support/Support.java @@ -0,0 +1,146 @@ + +package com.hans0924.fsd.support; + +import org.apache.commons.lang3.StringUtils; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.time.Clock; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * @author Hanshuo Zeng + * @since 2020/03/04 + */ +public class Support { + + public static String catCommand(List array, int n, StringBuilder buf) { + if (array.size() == 0) { + return ""; + } + for (int x = 0; x < n; x++) { + if (x > 0) { + buf.append(":"); + } + buf.append(array.get(x)); + } + return buf.toString(); + } + + public static String catArgs(List array, int n, StringBuilder buf) { + for (int x = 0; x < n; x++) { + if (x > 0) { + buf.append(" "); + } + buf.append(array.get(x)); + } + return buf.toString(); + } + + /* Takes bytes from the packet s and puts it into buf */ + public static String snapPacket(String s, String buf, int bytes) { + return buf + s.substring(0, bytes); + } + + public static int breakPacket(String s, List arr, int max) { + String[] split = s.split(":", -1); + for (String str : split) { + arr.add(StringUtils.defaultString(str, "")); + if (arr.size() == max) { + return arr.size(); + } + } + + return arr.size(); + } + + public static int breakArgs(String s, List arr, int max) { + String[] split = s.split(" "); + for (String str : split) { + str = StringUtils.strip(str); + if (StringUtils.isNotBlank(str)) { + arr.add(str); + } + if (arr.size() == max) { + return arr.size(); + } + } + + return arr.size(); + } + + public static String findHostname(String ip) { + try { + InetAddress address = InetAddress.getByName(ip); + return address.getHostName(); + } catch (UnknownHostException e) { + return ip; + } + } + + public static double dist(double lat1, double lon1, double lat2, double lon2) { + double dist, dlon = lon2 - lon1; + lat1 *= Math.PI / 180.0; + lat2 *= Math.PI / 180.0; + dlon *= Math.PI / 180.0; + dist = (Math.sin(lat1) * Math.sin(lat2)) + (Math.cos(lat1) * Math.cos(lat2) * Math.cos(dlon)); + if (dist > 1.0) dist = 1.0; + dist = Math.acos(dist) * 60 * 180 / Math.PI; + return dist; + } + + public static String printPart(double part, char p1, char p2) { + char c = part < 0.0 ? p2 : p1; + part = Math.abs(part); + double degrees = Math.floor(part), min, sec; + part -= degrees; + part *= 60; + min = Math.floor(part); + part -= min; + part *= 60; + sec = part; + + return String.format("%c %02d %02d' %02d\"", c, (int) degrees, (int) min, (int) sec); + } + + public static String printLoc(double lat, double lon) { + String north = printPart(lat, 'N', 'S'); + String east = printPart(lon, 'E', 'W'); + return String.format("%s %s", north, east); + } + + public static void startTimer() { + + } + + public static long mtime() { + return Instant.now(Clock.systemDefaultZone()).toEpochMilli(); + } + + public static long mgmtime() { + return Instant.now(Clock.systemUTC()).toEpochMilli(); + } + + public static String sprintTime(long now) { + return LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("HH:mm:ss")); + } + + public static String sprintDate(long now) { + if (now == 0) { + return ""; + } + return LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss")); + } + + public static String sprintGmtDate(long now) { + return LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")); + } + + public static String sprintGmt(long now) { + return LocalDateTime.ofInstant(Instant.ofEpochMilli(now), ZoneId.of("UTC")).format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")); + } +} diff --git a/src/main/java/com/hans0924/fsd/user/AbstractUser.java b/src/main/java/com/hans0924/fsd/user/AbstractUser.java new file mode 100644 index 0000000..94ea31b --- /dev/null +++ b/src/main/java/com/hans0924/fsd/user/AbstractUser.java @@ -0,0 +1,448 @@ +package com.hans0924.fsd.user; + +import com.hans0924.fsd.constants.GlobalConstants; +import com.hans0924.fsd.constants.NetworkConstants; +import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.process.network.TcpInterface; +import com.hans0924.fsd.support.Support; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SocketChannel; +import java.util.Set; + +/** + * @author Hanshuo Zeng + * @since 2020-03-05 + */ +public class AbstractUser { + private final static Logger LOGGER = LoggerFactory.getLogger(AbstractUser.class); + + private static int fdCount = 0; + + protected int fd; + + protected SocketChannel socketChannel; + + protected int killFlag, inSize, outSize, feed, feedCount, guardFlag; + + protected int outBufSoftLimit; + + protected long lastActive, lastPing, prevFeedCheck; + + protected int timeOut, blocked; + + protected ByteBuffer inBuf, outBuf; + + protected String peer; + + protected int port; + + protected String prompt; + + protected TcpInterface baseParent; + + public AbstractUser(SocketChannel d, TcpInterface p, String peerName, int portNum, int g) { + killFlag = 0; + socketChannel = d; + fd = ++fdCount; + inBuf = ByteBuffer.allocate(1); + outBuf = ByteBuffer.allocate(1); + inSize = outSize = 1; + blocked = 0; + feedCount = 0; + feed = -1; + baseParent = p; + timeOut = 0; + peer = peerName; + port = portNum; + prevFeedCheck = 0; + guardFlag = g; + outBufSoftLimit = -1; + setActive(); + } + + public void close() { + LOGGER.info(String.format("client from %s removed from %s:%s", peer, baseParent.getDescription(), NetworkConstants.KILL_REASONS[killFlag])); + Manage.manager.incVar(baseParent.getVarClosed()[killFlag]); + if (killFlag != NetworkConstants.KILL_CLOSED && killFlag != NetworkConstants.KILL_WRITE_ERR) { + output(); + } + if (killFlag != NetworkConstants.KILL_COMMAND && guardFlag > 0) { + baseParent.addGuard(this); + } + try { + socketChannel.close(); + } catch (IOException e) { + LOGGER.warn(String.format("Close client %s failed.", peer), e); + } + } + + public void setActive() { + lastActive = lastPing = Support.mtime(); + } + + public void setMasks(Set rmask, Set wmask) { + rmask.add(socketChannel); + if (outBuf.position() > 0) { + wmask.add(socketChannel); + } + } + + public void output() { + int bytes; + try { + bytes = socketChannel.write(outBuf); + byte[] data = new byte[bytes]; + System.arraycopy(outBuf.array(), 0, data, 0, bytes); + LOGGER.info("Send: " + new String(data)); + outBuf.clear(); + } catch (IOException e) { + kill(NetworkConstants.KILL_WRITE_ERR); + return; + } + + if ((baseParent.getFeedStrategy() & NetworkConstants.FEED_OUT) > 0) { + feedCount += bytes; + } + } + + public void input() { + int bytes; + ByteBuffer buf; + try { + buf = ByteBuffer.allocate(1024); + bytes = socketChannel.read(buf); + if (bytes == -1) { + kill(NetworkConstants.KILL_CLOSED); + return; + } + } catch (IOException e) { + kill(NetworkConstants.KILL_CLOSED); + return; + } + + if ((baseParent.getFeedStrategy() & NetworkConstants.FEED_IN) > 0) { + feedCount += bytes; + } + + int inBufBytes = inBuf.position(); + + if (inSize > 4096 && inBufBytes == 0) { + inSize = 2048; + inBuf.clear(); + } + + buf.flip(); + + if (bytes + inBufBytes + 1 > inSize) { + inSize = bytes + inBufBytes + 1000; + ByteBuffer newInBuf = ByteBuffer.allocate(inSize); + if (inBufBytes > 0) { + inBuf.flip(); + newInBuf.put(inBuf); + inBuf.compact(); + } + newInBuf.put(buf); + inBuf = newInBuf; + } else { + inBuf.put(buf); + } + buf.compact(); + } + + private int nextLine(StringBuilder dest) { + if (!inBuf.hasArray()) { + return -1; + } + byte[] data = new byte[inBuf.position()]; + System.arraycopy(inBuf.array(), 0, data, 0, inBuf.position()); + String src = new String(data); + + int len = src.indexOf("\r\n"); + if (len == -1) { + len = src.indexOf("\n"); + } + if (len == -1) { + return -1; + } + + if (len < GlobalConstants.MAX_LINE_LENGTH) { + dest.append(src, 0, len); + } + + ByteBuffer newInBuf = ByteBuffer.allocate(inSize); + if (src.length() > len + 2) { + newInBuf.put(src.substring(len + 2).getBytes()); + } + inBuf = newInBuf; + + if (len >= GlobalConstants.MAX_LINE_LENGTH) { + return -1; + } + return len; + } + + public void calcFeed() { + long now = Support.mtime(); + long elapsed = now - prevFeedCheck; + double fact1 = elapsed / 300.0, fact2 = 1.0 - fact1; + int newFeed = (int) (feedCount / elapsed); + if (feed == -1) { + feed = newFeed; + } else { + feed = (int) (newFeed * fact1 + feed * fact2); + } + feedCount = 0; + prevFeedCheck = now; + int bandWidth = feed; + if (baseParent.getFeedStrategy() == NetworkConstants.FEED_BOTH) { + bandWidth /= 2; + } + if (bandWidth > 50) { + outBufSoftLimit = bandWidth * 30; + } + } + + private int send(String buf) { + byte[] bufBytes = buf.getBytes(); + + int outBufBytes = outBuf.position(); + int bytes = bufBytes.length; + + if (outBufBytes > 0) { + if (outBufBytes + bytes + 1 > outSize) { + outSize = outBufBytes + bytes + 1000; + ByteBuffer newOutBuf = ByteBuffer.allocate(outSize); + outBuf.flip(); + newOutBuf.put(outBuf); + outBuf.compact(); + newOutBuf.put(bufBytes); + outBuf = newOutBuf; + } else { + outBuf.put(bufBytes); + } + } else { + int sendBytes; + try { + ByteBuffer tmpBuf = ByteBuffer.wrap(bufBytes); + sendBytes = socketChannel.write(tmpBuf); + } catch (IOException e) { + kill(NetworkConstants.KILL_WRITE_ERR); + return 0; + } + + if ((baseParent.getFeedStrategy() & NetworkConstants.FEED_OUT) > 0) { + feedCount += sendBytes; + } + } + + return bytes; + } + + public void uprintf(String format, Object... args) { + if (killFlag != 0) { + return; + } + + String buf = String.format(format, args); + send(buf); + } + + public void uslprintf(String format, int limit, Object... args) { + if (killFlag != 0) { + return; + } + + String buf = String.format(format, args); + if (limit > 0 && outBufSoftLimit != -1 && (buf.length() + outBuf.position()) > outBufSoftLimit) { + return; + } + send(buf); + } + + public int run() { + int count = 0, stat, ok = 0; + if (blocked == 1) { + return 0; + } + StringBuilder bufBuilder = new StringBuilder(); + while ((stat = nextLine(bufBuilder)) != -1 && (++count < 60)) { + if (baseParent.getFloodLimit() != -1 && feed > baseParent.getFloodLimit()) { + kill(NetworkConstants.KILL_FLOOD); + } + parse(bufBuilder.toString()); + ok = 1; + if (blocked == 1) { + break; + } + bufBuilder.delete(0, bufBuilder.length()); + } + + if (ok == 1 && stat == -1) { + printPrompt(); + } + + return stat == -1 ? 0 : 1; + } + + public void parse(String s) { + + } + + public void block() { + blocked = 1; + } + + public void unblock() { + blocked = 0; + printPrompt(); + } + + public void printPrompt() { + if (prompt == null || killFlag > 0 || blocked > 0) { + return; + } + + uprintf("%s", prompt); + } + + public void kill(int reason) { + killFlag = reason; + } + + public void sendPing() { + + } + + public int getFd() { + return fd; + } + + public SocketChannel getSocketChannel() { + return socketChannel; + } + + public int getKillFlag() { + return killFlag; + } + + public void setKillFlag(int killFlag) { + this.killFlag = killFlag; + } + + public int getInSize() { + return inSize; + } + + public void setInSize(int inSize) { + this.inSize = inSize; + } + + public int getOutSize() { + return outSize; + } + + public void setOutSize(int outSize) { + this.outSize = outSize; + } + + public int getFeed() { + return feed; + } + + public void setFeed(int feed) { + this.feed = feed; + } + + public int getFeedCount() { + return feedCount; + } + + public void setFeedCount(int feedCount) { + this.feedCount = feedCount; + } + + public int getGuardFlag() { + return guardFlag; + } + + public void setGuardFlag(int guardFlag) { + this.guardFlag = guardFlag; + } + + public int getOutBufSoftLimit() { + return outBufSoftLimit; + } + + public void setOutBufSoftLimit(int outBufSoftLimit) { + this.outBufSoftLimit = outBufSoftLimit; + } + + public long getLastActive() { + return lastActive; + } + + public void setLastActive(long lastActive) { + this.lastActive = lastActive; + } + + public long getLastPing() { + return lastPing; + } + + public void setLastPing(long lastPing) { + this.lastPing = lastPing; + } + + public long getPrevFeedCheck() { + return prevFeedCheck; + } + + public void setPrevFeedCheck(long prevFeedCheck) { + this.prevFeedCheck = prevFeedCheck; + } + + public int getTimeOut() { + return timeOut; + } + + public void setTimeOut(int timeOut) { + this.timeOut = timeOut; + } + + public int getBlocked() { + return blocked; + } + + public void setBlocked(int blocked) { + this.blocked = blocked; + } + + public String getPeer() { + return peer; + } + + public void setPeer(String peer) { + this.peer = peer; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getPrompt() { + return prompt; + } + + public void setPrompt(String prompt) { + this.prompt = prompt; + } +} diff --git a/src/main/java/com/hans0924/fsd/user/ClientUser.java b/src/main/java/com/hans0924/fsd/user/ClientUser.java new file mode 100644 index 0000000..f5ab463 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/user/ClientUser.java @@ -0,0 +1,440 @@ +package com.hans0924.fsd.user; + +import com.hans0924.fsd.Fsd; +import com.hans0924.fsd.constants.*; +import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.model.Certificate; +import com.hans0924.fsd.model.Client; +import com.hans0924.fsd.model.Server; +import com.hans0924.fsd.process.config.ConfigEntry; +import com.hans0924.fsd.process.config.ConfigGroup; +import com.hans0924.fsd.process.metar.MetarManage; +import com.hans0924.fsd.process.network.ClientInterface; +import com.hans0924.fsd.process.network.TcpInterface; +import com.hans0924.fsd.support.Support; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class ClientUser extends AbstractUser { + + private static final Logger LOGGER = LoggerFactory.getLogger(ClientUser.class); + + private ClientInterface parent; + + private Client thisClient; + + public ClientUser(SocketChannel channel, ClientInterface p, String pn, int portnum, int gg) { + super(channel, p, pn, portnum, gg); + parent = p; + thisClient = null; + ConfigGroup gu = Fsd.configManager.getGroup("system"); + ConfigEntry e = null; + if (gu != null) { + e = gu.getEntry("maxclients"); + } + int total = Manage.manager.getVar(p.getVarCurrent()).getValue().getNumber(); + if (e != null && NumberUtils.toInt(e.getData()) <= total) { + showError(ProtocolConstants.ERR_SERVFULL, ""); + kill(NetworkConstants.KILL_COMMAND); + } + } + + @Override + public void close() { + super.close(); + if (thisClient != null) { + int type = thisClient.getType(); + Fsd.serverInterface.sendRmClient(null, "*", thisClient, this); + thisClient.close(); + } + } + + public void readMotd() { + String line = GlobalConstants.PRODUCT; + Fsd.clientInterface.sendGeneric(thisClient.getCallsign(), thisClient, null, + null, "server", line, ProtocolConstants.CL_MESSAGE); + + List lines; + try { + lines = Files.readAllLines(Paths.get(FsdPath.PATH_FSD_MOTD), StandardCharsets.UTF_8); + } catch (IOException e) { + return; + } + + for (String msg : lines) { + Fsd.clientInterface.sendGeneric(thisClient.getCallsign(), thisClient, null, null, "server", msg, ProtocolConstants.CL_MESSAGE); + } + } + + public void parse(String s) { + setActive(); + doParse(s); + } + + /* Checks if the given callsign is OK. returns 0 on success or errorcode + on failure */ + public int callsignOk(String name) { + if (name.length() < 2 || name.length() > GlobalConstants.CALLSIGN_BYTES) return ProtocolConstants.ERR_CSINVALID; + if (StringUtils.indexOfAny(name, "!@#$%*:& \t") != -1) return ProtocolConstants.ERR_CSINVALID; + for (Client client : Client.clients) + if (client.getCallsign().equals(name)) return ProtocolConstants.ERR_CSINUSE; + return ProtocolConstants.ERR_OK; + } + + public int checkSource(String from) { + if (!from.equalsIgnoreCase(thisClient.getCallsign())) { + showError(ProtocolConstants.ERR_SRCINVALID, from); + return 0; + } + return 1; + } + + public int getComm(String cmd) { + for (int index = 0; index < ClientConstants.CL_CMD_NAMES.length; index++) { + if (cmd.startsWith(ClientConstants.CL_CMD_NAMES[index])) { + return index; + } + } + return -1; + } + + public ClientUser(SocketChannel d, TcpInterface p, String peerName, int portNum, int g) { + super(d, p, peerName, portNum, g); + } + + public int showError(int num, String env) { + uprintf("$ERserver:%s:%03d:%s:%s\r\n", thisClient != null ? thisClient.getCallsign() : + "unknown", num, env, ClientConstants.ERR_STR[num]); + return num; + } + + public int checkLogin(String id, String pwd, int req) { + if (StringUtils.isEmpty(id)) return -2; + int[] max = new int[1]; + int ok = Certificate.maxLevel(id, pwd, max); + if (ok == 0) { + showError(ProtocolConstants.ERR_CIDINVALID, id); + return -1; + } + return Math.min(req, max[0]); + } + + public void execAa(String[] s, int count) { + if (thisClient != null) { + showError(ProtocolConstants.ERR_REGISTERED, ""); + return; + } + if (count < 7) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + int err = callsignOk(s[0]); + if (err != 0) { + showError(err, ""); + kill(NetworkConstants.KILL_COMMAND); + return; + } + if (NumberUtils.toInt(s[6]) != GlobalConstants.NEED_REVISION) { + showError(ProtocolConstants.ERR_REVISION, ""); + kill(NetworkConstants.KILL_PROTOCOL); + return; + } + int req = NumberUtils.toInt(s[5]); + if (req < 0) req = 0; + int level = checkLogin(s[3], s[4], req); + if (level == 0) { + showError(ProtocolConstants.ERR_CSSUSPEND, ""); + kill(NetworkConstants.KILL_COMMAND); + return; + } else if (level == -1) { + kill(NetworkConstants.KILL_COMMAND); + return; + } else if (level == -2) level = 1; + if (level < req) { + showError(ProtocolConstants.ERR_LEVEL, s[5]); + kill(NetworkConstants.KILL_COMMAND); + return; + } + thisClient = new Client(s[3], Server.myServer, s[0], ClientConstants.CLIENT_ATC, level, s[6], s[2], + -1); + Fsd.serverInterface.sendAddClient("*", thisClient, null, this, 0); + readMotd(); + } + + public void execAp(String[] s, int count) { + if (thisClient != null) { + showError(ProtocolConstants.ERR_REGISTERED, ""); + return; + } + if (count < 8) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + int err = callsignOk(s[0]); + if (err != 0) { + showError(err, ""); + kill(NetworkConstants.KILL_COMMAND); + return; + } + if (NumberUtils.toInt(s[5]) != GlobalConstants.NEED_REVISION) { + showError(ProtocolConstants.ERR_REVISION, ""); + kill(NetworkConstants.KILL_PROTOCOL); + return; + } + int req = NumberUtils.toInt(s[4]); + if (req < 0) req = 0; + int level = checkLogin(s[2], s[3], req); + if (level < 0) { + kill(NetworkConstants.KILL_COMMAND); + return; + } else if (level == 0) { + showError(ProtocolConstants.ERR_CSSUSPEND, ""); + kill(NetworkConstants.KILL_COMMAND); + return; + } + if (level < req) { + showError(ProtocolConstants.ERR_LEVEL, s[4]); + kill(NetworkConstants.KILL_COMMAND); + return; + } + thisClient = new Client(s[2], Server.myServer, s[0], ClientConstants.CLIENT_PILOT, level, s[4], s[7], + NumberUtils.toInt(s[6])); + Fsd.serverInterface.sendAddClient("*", thisClient, null, this, 0); + readMotd(); + } + + public void execMulticast(String[] s, int count, int cmd, int nargs, int multiok) { + nargs += 2; + if (count < nargs) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + String[] subarray = ArrayUtils.subarray(s, 2, s.length); + String data = Support.catCommand(Arrays.asList(subarray), count - 2, new StringBuilder()); + String from = s[0], to = s[1]; + if (checkSource(from) == 0) return; + Fsd.serverInterface.sendMulticast(thisClient, to, data, cmd, multiok, this); + } + + public void exeCd(String[] s, int count) { + if (count == 0) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if (checkSource(s[0]) == 0) return; + kill(NetworkConstants.KILL_COMMAND); + } + + public void execPilotPos(String[] array, int count) { + if (count < 10) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if (checkSource(array[1]) == 0) return; + thisClient.updatePilot(array); + Fsd.serverInterface.sendPilotData(thisClient, this); + } + + public void execAtcPos(String[] array, int count) { + if (count < 8) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if (checkSource(array[0]) == 0) return; + thisClient.updateAtc(ArrayUtils.subarray(array, 1, array.length)); + Fsd.serverInterface.sendAtcData(thisClient, this); + } + + public void execFp(String[] array, int count) { + if (count < 17) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if (checkSource(array[0]) == 0) return; + thisClient.handleFp(ArrayUtils.subarray(array, 2, array.length)); + Fsd.serverInterface.sendPlan("*", thisClient, null); + } + + public void execWeather(String[] array, int count) { + if (count < 3) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if (checkSource(array[0]) == 0) return; + String source = String.format("%%%s", thisClient.getCallsign()); + MetarManage.metarManager.requestMetar(source, array[2], 1, -1); + } + + public void execAcars(String[] array, int count) { + if (count < 3) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if (checkSource(array[0]) == 0) return; + if ("METAR".equalsIgnoreCase(array[2]) && count > 3) { + String source = String.format("%%%s", thisClient.getCallsign()); + MetarManage.metarManager.requestMetar(source, array[3], 0, -1); + } + } + + public void execCq(String[] array, int count) { + if (count < 3) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if ("server".equalsIgnoreCase(array[1])) { + execMulticast(array, count, ProtocolConstants.CL_CQ, 1, 1); + return; + } + if ("RN".equalsIgnoreCase(array[2].toUpperCase())) { + Client cl = Client.getClient(array[1]); + if (cl != null) { + String data = String.format("%s:%s:RN:%s:USER:%d", cl.getCallsign(), thisClient.getCallsign(), cl.getRealName(), cl.getRating()); + Fsd.clientInterface.sendPacket(thisClient, cl, null, ClientConstants.CLIENT_ALL, -1, ProtocolConstants.CL_CR, data); + return; + } + } + if ("fp".equalsIgnoreCase(array[2])) { + Client cl = Client.getClient(array[3]); + if (cl == null) { + showError(ProtocolConstants.ERR_NOSUCHCS, array[3]); + return; + } + if (cl.getPlan() == null) { + showError(ProtocolConstants.ERR_NOFP, ""); + return; + } + Fsd.clientInterface.sendPlan(thisClient, cl, -1); + } + } + + public void execKill(String[] array, int count) { + if (count < 3) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + Client cl = Client.getClient(array[1]); + if (cl != null) { + showError(ProtocolConstants.ERR_NOSUCHCS, array[1]); + return; + } + + String junk; + + if (thisClient.getRating() < 11) { + junk = "You are not allowed to kill users!"; + Fsd.clientInterface.sendGeneric(thisClient.getCallsign(), thisClient, null, + null, "server", junk, ProtocolConstants.CL_MESSAGE); + junk = String.format("%s attempted to remove %s, but was not allowed to", thisClient.getCallsign(), array[1]); + LOGGER.error(junk); + } else { + junk = String.format("Attempting to kill %s", array[1]); + Fsd.clientInterface.sendGeneric(thisClient.getCallsign(), thisClient, null, + null, "server", junk, ProtocolConstants.CL_MESSAGE); + junk = String.format("%s Killed %s", thisClient.getCallsign(), array[1]); + LOGGER.info(junk); + Fsd.serverInterface.sendKill(cl, array[2]); + } + return; + } + + void doParse(String s) { + String cmd = ""; + List list = new ArrayList<>(); + cmd = Support.snapPacket(s, cmd, 3); + int index = getComm(cmd), count; + if (index == -1) { + showError(ProtocolConstants.ERR_SYNTAX, ""); + return; + } + if (thisClient == null && index != ProtocolConstants.CL_ADDATC && index != ProtocolConstants.CL_ADDPILOT) + return; + + /* Just a hack to put the pointer on the first arg here */ + String str = s.substring(ClientConstants.CL_CMD_NAMES[index].length()); + + count = Support.breakPacket(str, list, 100); + + String[] array = list.toArray(new String[0]); + switch (index) { + case ProtocolConstants.CL_ADDATC: + execAa(array, count); + break; + case ProtocolConstants.CL_ADDPILOT: + execAp(array, count); + break; + case ProtocolConstants.CL_PLAN: + execFp(array, count); + break; + case ProtocolConstants.CL_RMATC: /* Handled like RMPILOT */ + case ProtocolConstants.CL_RMPILOT: + exeCd(array, count); + break; + case ProtocolConstants.CL_PILOTPOS: + execPilotPos(array, count); + break; + case ProtocolConstants.CL_ATCPOS: + execAtcPos(array, count); + break; + case ProtocolConstants.CL_PONG: + case ProtocolConstants.CL_PING: + execMulticast(array, count, index, 0, 1); + break; + case ProtocolConstants.CL_MESSAGE: + execMulticast(array, count, index, 1, 1); + break; + case ProtocolConstants.CL_REQHANDOFF: + case ProtocolConstants.CL_ACHANDOFF: + execMulticast(array, count, index, 1, 0); + break; + case ProtocolConstants.CL_SB: + case ProtocolConstants.CL_PC: + execMulticast(array, count, index, 0, 0); + break; + case ProtocolConstants.CL_WEATHER: + execWeather(array, count); + break; + case ProtocolConstants.CL_REQCOM: + execMulticast(array, count, index, 0, 0); + break; + case ProtocolConstants.CL_REPCOM: + execMulticast(array, count, index, 1, 0); + break; + case ProtocolConstants.CL_REQACARS: + execAcars(array, count); + break; + case ProtocolConstants.CL_CR: + execMulticast(array, count, index, 2, 0); + break; + case ProtocolConstants.CL_CQ: + execCq(array, count); + break; + case ProtocolConstants.CL_KILL: + execKill(array, count); + break; + default: + showError(ProtocolConstants.ERR_SYNTAX, ""); + break; + } + } + + public Client getThisClient() { + return thisClient; + } +} diff --git a/src/main/java/com/hans0924/fsd/user/ServerUser.java b/src/main/java/com/hans0924/fsd/user/ServerUser.java new file mode 100644 index 0000000..04caa3b --- /dev/null +++ b/src/main/java/com/hans0924/fsd/user/ServerUser.java @@ -0,0 +1,726 @@ +package com.hans0924.fsd.user; + +import com.hans0924.fsd.Fsd; +import com.hans0924.fsd.constants.*; +import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.model.Certificate; +import com.hans0924.fsd.model.Client; +import com.hans0924.fsd.model.Server; +import com.hans0924.fsd.process.config.ConfigEntry; +import com.hans0924.fsd.process.config.ConfigGroup; +import com.hans0924.fsd.process.metar.MetarManage; +import com.hans0924.fsd.process.network.ServerInterface; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.weather.WProfile; +import com.hans0924.fsd.weather.Weather; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +/** + * The server user class + *

+ * The USER class. There's not much interesting here, just handling + * the low level communication with the clients + * + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class ServerUser extends AbstractUser { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerUser.class); + public static final int MAX_SPACE = 1000000; + + private ServerInterface parent; + + private Server thisServer; + + private long startupTime; + + private int clientOk; + + public ServerUser(SocketChannel d, ServerInterface p, String pn, int portNum, int g) { + super(d, p, pn, portNum, g); + + parent = p; + startupTime = Support.mtime(); + clientOk = 0; + thisServer = null; + feed(); + } + + public void close() { + /* This server link is killed, send out a LINKDOWN message to the + others */ + StringBuilder buf = new StringBuilder(); + for (Server temp : Server.servers) { + if (temp.getPath() == this) { + temp.setPath(null, -1); + if (buf.length() > 0) { + buf.append(":"); + } + buf.append(temp.getIdent()); + } + } + if (buf.length() > 0) { + parent.sendLinkDown(buf.toString()); + LOGGER.info("link down"); + } + Fsd.serverInterface.sendSync(); + } + + public void parse(String s) { + if (s.charAt(0) == '#') return; + setActive(); + if (s.charAt(0) != '\\') { + doParse(s); + } else { + switch (s.charAt(1)) { + case 'q': + kill(NetworkConstants.KILL_COMMAND); + break; + case 'l': + list(s); + break; + default: + uprintf("Syntax error, type \\h for help\n"); + } + } + } + + private void list(String s) { + String str = s.substring(2).stripLeading(); + if (StringUtils.isEmpty(str)) { + str = null; + } + int nVars = Manage.manager.getNVars(); + for (int x = 0; x < nVars; x++) { + String buf = Manage.manager.sprintValue(x); + if (StringUtils.isNotBlank(buf)) { + String varName = Manage.manager.getVar(x).getName(); + if (str == null || varName.contains(s)) { + uprintf("%s=%s\r\n", varName, buf); + } + } + } + } + + /* Feed all data from this server to a direct neighbor */ + private void feed() { + for (Server tempServer : Server.servers) { + Fsd.serverInterface.sendServerNotify("*", tempServer, this); + } + for (Client tempClient : Client.clients) { + Fsd.serverInterface.sendAddClient("*", tempClient, this, null, 1); + if (tempClient.getPlan() != null) { + Fsd.serverInterface.sendPlan("*", tempClient, this); + } + } + for (Certificate tempCert : Certificate.certs) { + Fsd.serverInterface.sendCert("*", ProtocolConstants.CERT_ADD, tempCert, this); + } + for (WProfile tempWp : Weather.wProfiles) { + Fsd.serverInterface.sendAddWp(this, tempWp); + } + } + + /* + * When given the destination of a packet, needlocaldelivery() checks if this + * packet needs local delivery. + */ + private int needLocalDelivery(String dest) { + switch (dest.charAt(0)) { + /* wildcards are always delivered locally. Note : '*P' and '*A' are + also broadcast addresses */ + case '*': + /* We don't know yet, who is on what frequency, so deliver frequency + addressed packets */ + case '@': + return 1; + case '%': + /* This is a pilot packet, check if the client is connected to + this server */ + for (Client tempClient : Client.clients) { + if (tempClient.getCallsign().equalsIgnoreCase(dest.substring(1)) && tempClient.getLocation() == Server.myServer) { + return 1; + } + } + break; + default: + /* We have a server name here, check if the name equals our server + name */ + if (dest.equalsIgnoreCase(Server.myServer.getIdent())) { + return 1; + } + break; + } + return 0; + } + + /* + * Takes care of a packet, that comes in from a server connection. + */ + private void doParse(String s) { + int index = 0, count, packetCount = 0, hops = 0, bi; + List array = new ArrayList<>(); + String from, to; + Server origin; + + if (s.charAt(0) == '#') { + return; + } + + count = Support.breakArgs(s, array, 100); + + if (count < 6) { + /* This packet doesn't have the basic fields, show an error and + drop it */ + Manage.manager.incVar(parent.getVarShape()); + uprintf("# Missing basic packet fields, syntax error\r\n"); + return; + } + + /* Some statistics */ + boolean packetErr = false; + Scanner scanner = new Scanner(array.get(3).substring(1)); + if (!scanner.hasNextInt()) { + packetErr = true; + } else { + packetCount = scanner.nextInt(); + } + + scanner = new Scanner(array.get(4)); + if (!scanner.hasNextInt()) { + packetErr = true; + } else { + hops = scanner.nextInt(); + } + + from = array.get(2); + to = array.get(1); + bi = (array.get(3).charAt(0) == 'B') ? 1 : 0; + + if (packetErr) { + Manage.manager.incVar(parent.getVarIntErr()); + return; + } + + if (hops > GlobalConstants.MAX_HOPS) { + /* This packet seems to bounce, drop it */ + Manage.manager.incVar(parent.getVarBounce()); + return; + } + + /* Find the origin of the packet */ + origin = Server.getServer(from); + if (origin != null) { + /* This server seems to be alive */ + origin.setAlive(); + + /* Discard if it originated from our server */ + if (origin == Server.myServer) { + return; + } + + /* Check if this packet travelled a shorter route */ + if (hops < origin.getHops() || origin.getHops() == -1) { + origin.setPath(this, hops); + } + + /* Discard if the packet was broadcast and the packetcount doesn't + match the packetcount (we already handled this packet earlier) + The broadcast check (bi) is nessecary to prevent newer broadcast + packets from passing stalled unicast packets with a quicker route. + */ + if (packetCount <= origin.getpCount() || packetCount > (origin.getpCount() + MAX_SPACE)) { + if (packetCount == origin.getpCount() || (bi == 1 && origin.getPacketDrops() < 100) || + (bi == 1 && packetCount > (origin.getpCount() + MAX_SPACE))) { + Manage.manager.incVar(parent.getVarMcDrops()); + origin.setPacketDrops(origin.getPacketDrops() + 1); + return; + } + Manage.manager.incVar(parent.getVarUcOverRun()); + } + if (origin.getPacketDrops() >= 100) { + origin.setpCount(packetCount); + } + if (packetCount > origin.getpCount()) { + /* Update the packetcount for this server */ + origin.setpCount(packetCount); + } + if (bi == 1) { + /* Set the path to the server */ + origin.setPath(this, hops); + origin.setPacketDrops(0); + } + } + + /* Now find the command number for this packet */ + while (index < ProtocolConstants.CMD_NAMES.length) { + if (ProtocolConstants.CMD_NAMES[index].equalsIgnoreCase(array.get(0))) { + /* This user is correct */ + clientOk = 1; + + /* If we don't know the server, we only allow the NOTIFY packet from + it */ + if (origin == null && index != ProtocolConstants.CMD_NOTIFY) { + return; + } + + /* If the packet doesn't need local delivery, forward and return */ + if (needLocalDelivery(to) == 0) { + /* call sendpacket() to relay the packet to the other servers */ + if (!to.equalsIgnoreCase(Server.myServer.getIdent())) { + parent.sendPacket(this, null, index, to, from, packetCount, + hops, bi, Support.catCommand(array.subList(5, array.size() - 1), count - 5, new StringBuilder())); + } + return; + } + + /* Execute the command */ + int needForward = runCmd(index, array.subList(1, array.size() - 1).toArray(new String[0]), count - 1); + /* if the packet needs forwarding, call sendpacket() to relay the + packet to the other servers */ + if (!to.equalsIgnoreCase(Server.myServer.getIdent()) && needForward == 1) { + parent.sendPacket(this, null, index, to, from, packetCount, + hops, bi, Support.catCommand(array.subList(5, array.size() - 1), count - 5, new StringBuilder())); + } + Manage.manager.incVar(bi == 1 ? parent.getVarMcHandled() : parent.getVarUcHandled()); + return; + } + index++; + } + uprintf("# Unknown command, Syntax error\r\n"); + Manage.manager.incVar(parent.getVarFailed()); + } + + private int runCmd(int num, String[] data, int count) { + switch (num) { + case ProtocolConstants.CMD_NOTIFY: + execNotify(data, count); + break; + case ProtocolConstants.CMD_PING: + execPing(data, count); + break; + case ProtocolConstants.CMD_LINK_DOWN: + execLinkDown(data, count); + break; + case ProtocolConstants.CMD_PONG: + execPong(data, count); + break; + case ProtocolConstants.CMD_SYNC: + break; /* SYNC packets are for routing only */ + case ProtocolConstants.CMD_ADD_CLIENT: + return execAddClient(data, count); + case ProtocolConstants.CMD_RM_CLIENT: + execRmClient(data, count); + break; + case ProtocolConstants.CMD_PD: + execPd(data, count); + break; + case ProtocolConstants.CMD_AD: + execAd(data, count); + break; + case ProtocolConstants.CMD_CERT: + return execCert(data, count); + case ProtocolConstants.CMD_MULTICAST: + execMetar(data, count); + break; + case ProtocolConstants.CMD_PLAN: + return execPlan(data, count); + case ProtocolConstants.CMD_REQ_METAR: + execReqMetar(data, count); + break; + case ProtocolConstants.CMD_WEATHER: + execWeather(data, count); + break; + case ProtocolConstants.CMD_METAR: + execMetar(data, count); + break; + case ProtocolConstants.CMD_NO_WX: + execNoWx(data, count); + break; + case ProtocolConstants.CMD_ADD_WPROFILE: + execAddWp(data, count); + break; + case ProtocolConstants.CMD_DEL_WPROFILE: + execDelWp(data, count); + break; + case ProtocolConstants.CMD_KILL: + execKill(data, count); + break; + case ProtocolConstants.CMD_RESET: + execReset(data, count); + break; + } + + return 1; + } + + /* Handle a NOTIFY packet */ + private void execNotify(String[] array, int count) { + Server s; + int packetCount = 0, hops = 0, feedFlag = 0; + + if (count < 11) { + return; + } + + packetCount = NumberUtils.toInt(array[2]); + hops = NumberUtils.toInt(array[3]); + feedFlag = NumberUtils.toInt(array[4]); + s = Server.getServer(array[5]); + + if (s == Server.myServer) { + return; + } + + if (s == null) { + s = new Server(array[5], array[6], array[7], array[8], array[9], + NumberUtils.toInt(array[10]), (count == 12 ? array[11] : "")); + } else { + s.configure(array[6], array[7], array[8], array[9], + (count == 12 ? array[11] : "")); + } + /* If this is a notify from our peer, set 'thisserver' */ + if (feedFlag == 0 && hops == 1) thisServer = s; + + /* If this is a server feed, or we already have routing info, we don't + need to set the routing info */ + if (feedFlag == 1 || (s.getHops() > -1 && hops >= s.getHops())) return; + s.setPath(this, hops); + } + + /* Handle a PING packet */ + private void execPing(String[] array, int count) { + String[] subarray = ArrayUtils.subarray(array, 4, array.length); + parent.sendPong(array[1], Support.catCommand(Arrays.asList(subarray), count - 4, new StringBuilder())); + } + + /* Handle a PONG packet */ + private void execPong(String[] array, int count) { + if (count < 5) return; + Scanner scanner = new Scanner(array[4]); + int fd = scanner.nextInt(); + Server source = Server.getServer(array[1]); + if (source == null) { + return; + } + source.receivePong(array[4]); + if (fd != -1) { + Fsd.systemInterface.receivePong(array[1], array[4], array[2], array[3]); + } + } + + /* Handle a LINKDOWN packet */ + private void execLinkDown(String[] array, int count) { + for (int x = 4; x < count; x++) { + /* Get the next server ident from the packet and look it up */ + Server temp = Server.getServer(array[x]); + if (temp == null) { + continue; + } + + /* If the shortest path to the server is pointing at the direction + which the packet came from, we can no longer route packets + to the server in that direction: reset the path to NULL; packets for + the server will then be broadcast, until a new shortest path is + found */ + if (temp.getPath() == this) { + temp.setPath(null, -1); + } + } + Fsd.serverInterface.sendSync(); + } + + /* Handle an ADD CLIENT packet */ + private int execAddClient(String[] array, int count) { + if (count < 12) { + return 1; + } + + Server location = Server.getServer(array[5]); + /* If we can't find the server, or the indicated server is our server, + we don't need the new client. */ + if (location == null || location == Server.myServer) { + return 1; + } + + int type = NumberUtils.toInt(array[7]); + Client c = Client.getClient(array[6]); + if (c != null) { + return 0; + } + + c = new Client(array[4], location, array[6], type, NumberUtils.toInt(array[8]), array[9], + array[10], NumberUtils.toInt(array[11])); + if (type == ClientConstants.CLIENT_ATC) { + Fsd.clientInterface.sendAa(c, null); + } else if (type == ClientConstants.CLIENT_PILOT) { + Fsd.clientInterface.sendAp(c, null); + } + + return 1; + } + + /* Handle an RM CLIENT packet */ + private void execRmClient(String[] array, int count) { + if (count < 5) { + return; + } + Client c = Client.getClient(array[4]); + if (c != null) { + int type = c.getType(); + if (c.getLocation() == Server.myServer) { + return; + } + if (type == ClientConstants.CLIENT_ATC) { + Fsd.clientInterface.sendDa(c, null); + } else if (type == ClientConstants.CLIENT_PILOT) { + Fsd.clientInterface.sendDp(c, null); + } + c.close(); + } + } + + private void execAd(String[] array, int count) { + if (count < 12) return; + Client who = Client.getClient(array[4]); + if (who == null) return; + who.updateAtc(ArrayUtils.subarray(array, 5, array.length)); + Fsd.clientInterface.sendAtcPos(who, null); + } + + private void execPd(String[] array, int count) { + if (count < 14) return; + Client who = Client.getClient(array[5]); + if (who == null) return; + who.updatePilot(ArrayUtils.subarray(array, 4, array.length)); + Fsd.clientInterface.sendPilotPos(who, null); + } + + private int execCert(String[] array, int count) { + if (count < 10) return 0; + ConfigGroup group = Fsd.configManager.getGroup("hosts"); + ConfigEntry entry = null; + if (group != null) { + entry = group.getEntry("certificates"); + } + int ok = entry != null ? entry.inList(array[9]) : 1; + int level = NumberUtils.toInt(array[7]); + Certificate c = Certificate.getCert(array[5]); + long now = NumberUtils.toLong(array[8]) * 1000; + switch (NumberUtils.toInt(array[4])) { + case ProtocolConstants.CERT_ADD: + if (c == null) { + if (ok == 1) { + c = new Certificate(array[5], array[6], level, now, array[9]); + } + } else if (now > c.getCreation()) { + if (ok == 1) { + c.configure(null, NumberUtils.toInt(array[7]), now, array[9]); + } + } else { + return 0; + } + break; + case ProtocolConstants.CERT_DELETE: + if (c != null) { + if (ok == 1) { + c.close(); + } else { + return 0; + } + } + break; + case ProtocolConstants.CERT_MODIFY: + if (c != null && now > c.getCreation()) { + if (ok == 1) { + c.configure(array[6], NumberUtils.toInt(array[7]), now, array[9]); + } else { + return 0; + } + } + break; + } + return 1; + } + + private void execMulticast(String[] array, int count) { + if (count < 7) return; + Client source = Client.getClient(array[5]); + int cmd = NumberUtils.toInt(array[4]); + if (cmd > ProtocolConstants.CL_MAX) { + return; + } + String dest; + Client c = null; + switch (array[0].charAt(0)) { + case '*': + case '@': + dest = array[0]; + break; + default: + c = Client.getClient(array[0] + 1); + if (c == null) return; + dest = array[0] + 1; + break; + } + String[] subarray = ArrayUtils.subarray(array, 6, array.length); + Fsd.clientInterface.sendGeneric(dest, c, null, source, array[5], Support.catCommand(Arrays.asList(subarray), count - 6, new StringBuilder()), cmd); + } + + private int execPlan(String[] array, int count) { + if (count < 21) return 0; + int revision = NumberUtils.toInt(array[5]); + Client who = Client.getClient(array[4]); + if (who == null) return 1; + if (who.getPlan() != null && who.getPlan().getRevision() >= revision) return 0; + who.handleFp(ArrayUtils.subarray(array, 6, array.length)); + who.getPlan().setRevision(revision); + Fsd.clientInterface.sendPlan(null, who, 400); + return 1; + } + + private void execReqMetar(String[] array, int count) { + if (count < 8) return; + MetarManage.metarManager.requestMetar(array[4], array[5], NumberUtils.toInt(array[6]), NumberUtils.toInt(array[7])); + } + + private void execWeather(String[] array, int count) { + if (count < 56) return; + WProfile prof = new WProfile(array[4], 0, null); + int fd = NumberUtils.toInt(array[5]); + prof.loadArray(ArrayUtils.subarray(array, 6, array.length), count - 6); + if (fd == -1) { + Client c = Client.getClient(array[0].substring(1)); + if (c == null) { + return; + } + Fsd.clientInterface.sendWeather(c, prof); + } else { + Fsd.systemInterface.receiveWeather(fd, prof); + } + } + + private void execMetar(String[] array, int count) { + if (count<7) return; + int fd = NumberUtils.toInt(array[5]); + if (fd!=-1) { + Fsd.systemInterface.receiveMetar(fd, array[4], array[6]); + } else { + Client c=Client.getClient(array[0]+1); + if (c == null) return; + Fsd.clientInterface.sendMetar(c, array[6]); + } + } + + private void execNoWx(String[] array, int count) { + if (count < 6) return; + int fd = NumberUtils.toInt(array[5]); + if (fd == -1) { + Client c = Client.getClient(array[0].substring(1)); + if (c == null) return; + Fsd.clientInterface.sendNoWx(c, array[4]); + } else { + Fsd.systemInterface.receiveNoWx(fd, array[4]); + } + } + + private void execAddWp(String[] array, int count) { + /* Non METAR hosts, should not store weather profiles */ + if (count < 57 || MetarManage.metarManager.getSource() == MetarSource.SOURCE_NETWORK) return; + ConfigGroup group = Fsd.configManager.getGroup("hosts"); + ConfigEntry entry = null; + if (group != null) { + entry = group.getEntry("weather"); + } + int ok = entry != null ? entry.inList(array[6]) : 1; + if (ok == 0) { + return; + } + + Scanner scanner = new Scanner(array[5]); + long version = scanner.nextLong(); + WProfile prof = Weather.getWProfile(array[4]); + if (prof != null && prof.getVersion() >= version) { + return; + } + if (prof != null) { + prof.close(); + } + prof = new WProfile(array[4], version, array[6]); + prof.loadArray(ArrayUtils.subarray(array, 7, array.length), count - 7); + prof.genRawCode(); + } + + private void execDelWp(String[] array, int count) { + if (count < 5 || MetarManage.metarManager.getSource() == MetarSource.SOURCE_NETWORK) return; + ConfigGroup group = Fsd.configManager.getGroup("hosts"); + ConfigEntry entry = null; + if (group != null) { + entry = group.getEntry("weather"); + } + int ok = entry != null ? entry.inList(array[1]) : 1; + if (ok == 0) { + return; + } + WProfile prof = Weather.getWProfile(array[4]); + if (prof == null || prof.getActive() == 0) { + return; + } + prof.close(); + } + + private void execKill(String[] array, int count) { + if (count < 6) { + return; + } + + Client c = Client.getClient(array[4]); + if (c == null) { + return; + } + Fsd.clientInterface.handleKill(c, array[5]); + } + + private void execReset(String[] array, int count) { + Server s = Server.getServer(array[1]); + if (s == null) { + return; + } + s.setpCount(0); + } + + public Server getThisServer() { + return thisServer; + } + + public void setThisServer(Server thisServer) { + this.thisServer = thisServer; + } + + public long getStartupTime() { + return startupTime; + } + + public void setStartupTime(long startupTime) { + this.startupTime = startupTime; + } + + public int getClientOk() { + return clientOk; + } + + public void setClientOk(int clientOk) { + this.clientOk = clientOk; + } +} diff --git a/src/main/java/com/hans0924/fsd/user/SystemUser.java b/src/main/java/com/hans0924/fsd/user/SystemUser.java new file mode 100644 index 0000000..793e44e --- /dev/null +++ b/src/main/java/com/hans0924/fsd/user/SystemUser.java @@ -0,0 +1,1114 @@ +package com.hans0924.fsd.user; + +import com.hans0924.fsd.Fsd; +import com.hans0924.fsd.constants.*; +import com.hans0924.fsd.manager.Manage; +import com.hans0924.fsd.model.Certificate; +import com.hans0924.fsd.model.Client; +import com.hans0924.fsd.model.Server; +import com.hans0924.fsd.process.config.ConfigEntry; +import com.hans0924.fsd.process.config.ConfigGroup; +import com.hans0924.fsd.process.metar.MetarManage; +import com.hans0924.fsd.process.network.Guard; +import com.hans0924.fsd.process.network.SystemInterface; +import com.hans0924.fsd.support.Support; +import com.hans0924.fsd.weather.WProfile; +import com.hans0924.fsd.weather.Weather; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.SocketChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Hanshuo Zeng + * @since 2020-03-07 + */ +public class SystemUser extends AbstractUser { + + private int authorized; + + private SystemInterface parent; + + private double lat, lon; + + public SystemUser(SocketChannel fd, SystemInterface p, String pn, int portNum, int g) { + super(fd, p, pn, portNum, g); + // TODO logs[L_MAX] + uprintf(String.format("# %s system interface ready.\n", GlobalConstants.PRODUCT)); + // uprintf("# Logs: Emerg: %d, Alert: %d, Crit: %d, Err: %d, Warn: %d, Info:"\ + // " %d, Debug: %d\r\n", logs[0], logs[1], logs[2], logs[3], logs[4], + // logs[5], logs[6]); + uprintf("# Type 'help' for help.\r\n"); + parent = p; + + ConfigGroup group = Fsd.configManager.getGroup("system"); + ConfigEntry entry = null; + if (group != null) { + entry = group.getEntry("password"); + } + String pwd = entry != null ? entry.getData() : null; + authorized = StringUtils.isBlank(pwd) ? 1 : 0; + } + + public void parse(String s) { + setActive(); + doParse(s); + } + + private void information() { + uprintf("Number of servers in the network: %d\r\n", Server.servers.size()); + uprintf("Number of clients in the network: %d\r\n", Client.clients.size()); + uprintf("Number of certificates in the network: %d\r\n", Certificate.certs.size()); + } + + private void usage(int cmd, String string) { + uprintf("Usage: %s %s\r\n", SystemConstants.SYS_CMDS[cmd], string); + } + + private void list(String s) { + String str = s.stripLeading(); + if (StringUtils.isEmpty(str)) { + str = null; + } + int nVars = Manage.manager.getNVars(); + for (int x = 0; x < nVars; x++) { + String buf = Manage.manager.sprintValue(x); + if (buf != null) { + String varName = Manage.manager.getVar(x).getName(); + if (str == null || varName.contains(str)) { + uprintf("%s=%s\r\n", varName, buf); + } + } + } + } + + private void doParse(String s) { + List array = new ArrayList<>(); + int count = Support.breakArgs(s, array, 100); + if (count == 0) { + return; + } + for (int i = 0; i < SystemConstants.SYS_CMDS.length; i++) { + if (array.get(0).equalsIgnoreCase(SystemConstants.SYS_CMDS[i])) { + if (SystemConstants.NEED_AUTHORIZATION[i] == 1 && authorized == 0) { + uprintf("Not authorized, use 'pwd'.\r\n"); + } else { + runCmd(i, array.subList(1, array.size() - 1).toArray(new String[0]), count - 1); + } + return; + } + } + uprintf("Syntax error.\r\n"); + } + + private void runCmd(int num, String[] array, int count) { + switch (num) { + case SystemConstants.SYS_SERVERS: + execServers(array, count, 1); + break; + case SystemConstants.SYS_SERVERS2: + execServers(array, count, 2); + break; + case SystemConstants.SYS_INFORMATION: + information(); + break; + case SystemConstants.SYS_PING: + execPing(array, count); + break; + case SystemConstants.SYS_CONNECT: + execConnect(array, count); + break; + case SystemConstants.SYS_ROUTE: + execRoute(array, count); + break; + case SystemConstants.SYS_DISCONNECT: + execDisconnect(array, count); + break; + case SystemConstants.SYS_HELP: + execHelp(array, count); + break; + case SystemConstants.SYS_QUIT: + kill(NetworkConstants.KILL_COMMAND); + break; + case SystemConstants.SYS_STAT: + list((count == 0 ? "" : array[0])); + break; + case SystemConstants.SYS_SAY: + execSay(array, count); + break; + case SystemConstants.SYS_CLIENTS: + execClients(array, count); + break; + case SystemConstants.SYS_CERT: + execCert(array, count); + break; + case SystemConstants.SYS_DISTANCE: + execDistance(array, count); + break; + case SystemConstants.SYS_TIME: + execTime(array, count); + break; + case SystemConstants.SYS_WEATHER: + execWeather(array, count); + break; + case SystemConstants.SYS_RANGE: + execRange(array, count); + break; + case SystemConstants.SYS_LOG: + execLog(array, count); + break; + case SystemConstants.SYS_PWD: + execPwd(array, count); + break; + case SystemConstants.SYS_WALL: + execWall(array, count); + break; + case SystemConstants.SYS_DELGUARD: + execDelGuard(array, count); + break; + case SystemConstants.SYS_METAR: + execMetar(array, count); + break; + case SystemConstants.SYS_WP: + execWp(array, count); + break; + case SystemConstants.SYS_KILL: + execKill(array, count); + break; + case SystemConstants.SYS_DUMP: + execDump(array, count); + break; + case SystemConstants.SYS_POS: + execPos(array, count); + break; + case SystemConstants.SYS_REFMETAR: + execRefMetar(array, count); + break; + } + } + + private void execHelp(String[] array, int count) { + int copyMode = 0, topicMode = 0, globalMode = 0; + String s = count > 0 ? array[0] : null; + String topic; + if (s != null) { + topic = s; + } else { + topic = "global"; + globalMode = 1; + } + + List lines; + try { + lines = Files.readAllLines(Paths.get(FsdPath.PATH_FSD_HELP), StandardCharsets.UTF_8); + } catch (IOException e) { + uprintf("Could not open help file 'help.txt'\r\n"); + return; + } + + if ("topics".equals(topic)) { + topicMode = 1; + } + + for (String line : lines) { + if (line.startsWith("%topic", 6)) { + if (copyMode == 1) { + break; + } + + Pattern pattern = Pattern.compile("%topic (\\w+)"); + Matcher matcher = pattern.matcher(line); + if (!matcher.matches()) { + continue; + } + String top = matcher.group(1); + String p = ""; + if (top.contains(":")) { + String[] split = top.split(":"); + top = split[0]; + p = split[1]; + } + + if (topicMode == 1) { + uprintf("%s:%s", top, p); + continue; + } + + if (top.equalsIgnoreCase(topic)) { + String header = "FSD ONLINE DOCUMENTATION"; + uprintf("%s %55s", header, p); + StringBuilder tmp = new StringBuilder(); + while (tmp.length() < 79) { + tmp.append("="); + } + line = tmp.toString(); + uprintf("%s\r\n", line); + copyMode = 1; + } + } else if (copyMode == 1) { + uprintf("%s", line); + } + } + + if (globalMode == 1) { + String l = ""; + for (int i = 0; i < SystemConstants.SYS_CMDS.length; i++) { + if (StringUtils.isNotEmpty(l)) { + l += " "; + } + l += SystemConstants.SYS_CMDS[i]; + if (l.length() > 65) { + uprintf("%s\r\n", l.toUpperCase()); + l = ""; + } + } + if (StringUtils.isNotEmpty(l)) { + uprintf("%s\r\n", l.toUpperCase()); + } + } + } + + private void execConnect(String[] array, int count) { + if (count < 1) { + List users = Fsd.serverInterface.getUsers(); + if (users.size() == 0) + uprintf("No active server connections.\r\n"); + else { + uprintf("fd Limit Out-Q In-Q Feed Peer\r\n"); + uprintf("---------------------------------\r\n"); + for (AbstractUser user : users) { + ServerUser su = (ServerUser) user; + uprintf("%02d %5d %5d %5d %5d %s:%d (%s)\r\n", su.getSocketChannel(), su.getOutBufSoftLimit(), + su.outBuf.position(), su.inBuf.position(), su.feed, su.peer, su.port, + su.getThisServer() != null ? su.getThisServer().getIdent() : ""); + } + } + List guards = Fsd.serverInterface.getGuards(); + if (guards.size() == 0) + uprintf("No pending connections.\r\n"); + else { + uprintf("Pending connections:\r\n"); + for (Guard guard : guards) { + uprintf(" %s:%d\r\n", guard.getHost(), guard.getPort()); + } + } + return; + } + int port = 3011; + if (count > 1) port = NumberUtils.toInt(array[1]); + if (Fsd.serverInterface.addUser(array[0], port, this) == 1) { + Fsd.serverInterface.sendServerNotify("*", Server.myServer, null); + } + } + + private void execDisconnect(String[] array, int count) { + if (count == 0) { + usage(SystemConstants.SYS_DISCONNECT, ""); + return; + } + int fd = NumberUtils.toInt(array[0]); + List users = Fsd.serverInterface.getUsers(); + for (AbstractUser temp : users) { + if (temp.getFd() == fd) { + temp.kill(NetworkConstants.KILL_COMMAND); + uprintf("Disconnected\r\n"); + return; + } + } + uprintf("No such connection was found.\r\n"); + } + + private void execPing(String[] array, int count) { + if (count == 0) { + usage(SystemConstants.SYS_PING, ""); + return; + } + + long now = Support.mtime(); + String data = String.format("%d %d", fd, now); + Fsd.serverInterface.sendPing(array[0], data); + } + + private void execServers(String[] array, int count, int type) { + Server match = null, temp = null; + if (count == 1) { + for (Server server : Server.servers) { + temp = server; + if (temp.getName().contains(array[0]) || temp.getIdent().contains(array[0])) { + if (match != null) { + break; + } + match = temp; + } + } + if (temp == null && match != null) { + String flags = ""; + uprintf("ID : %s\r\n", match.getIdent()); + uprintf("Name : %s\r\n", match.getName()); + uprintf("Version : %s\r\n", match.getVersion()); + uprintf("Mail : %s\r\n", match.getEmail()); + uprintf("Host : %s\r\n", match.getHostName()); + uprintf("Location: %s\r\n", match.getLocation()); + uprintf("Hops : %d\r\n", match.getHops()); + uprintf("Lag : %d seconds\r\n", match.getLag()); + if ((match.getFlags() & ServerConstants.SERVER_SILENT) != 0) { + flags += "SILENT"; + } + if ((match.getFlags() & ServerConstants.SERVER_METAR) != 0) { + if (StringUtils.isNotEmpty(flags)) { + flags += ", "; + } + flags += "METAR"; + } + uprintf("Flags : %s\r\n", flags); + return; + } + } + if (type == 1) { + uprintf("ID Host Fl Hops Lag Name\r\n"); + uprintf("-------------------------------------------------------------\r\n"); + for (Server server : Server.servers) + temp = server; + if (count == 0 || temp.getName().contains(array[0]) || + temp.getIdent().contains(array[0])) + uprintf("%-10s %-25s %c%c %02d %02d %s\r\n", temp.getIdent(), + temp.getHostName(), (temp.getFlags() & ServerConstants.SERVER_METAR) != 0 ? 'M' : '-', + (temp.getFlags() & ServerConstants.SERVER_SILENT) != 0 ? 'S' : '-', + temp.getHops(), temp.getLag(), temp.getName()); + uprintf("\r\n"); + } else { + uprintf("ID Version Mail Location\r\n"); + uprintf("-------------------------------------------------------------\r\n"); + for (Server server : Server.servers) + if (count == 0 || temp.getName().contains(array[0]) || + temp.getIdent().contains(array[0])) + uprintf("%-10s %-11s %-20s %s\r\n", temp.getIdent(), + temp.getVersion(), temp.getEmail(), temp.getLocation()); + uprintf("\r\n"); + } + } + + private void execRoute(String[] array, int count) { + uprintf("ID Next hop\r\n"); + uprintf("-------------------\r\n"); + String dest; + for (Server temp : Server.servers) { + if (count == 0 || temp.getName().contains(array[0]) || + temp.getIdent().contains(array[0])) { + if (temp == Server.myServer) { + dest = "*local*"; + } else if (temp.getPath() == null) { + dest = "*broadcast*"; + } else { + dest = String.format("%s:%d", temp.getPath().getPeer(), temp.getPath().getPort()); + } + uprintf("%-10s %s\r\n", temp.getIdent(), dest); + } + } + uprintf("\r\n"); + } + + private void execSay(String[] array, int count) { + if (count < 2) { + usage(SystemConstants.SYS_SAY, " "); + return; + } + StringBuilder buf = new StringBuilder(); + String[] subarray = ArrayUtils.subarray(array, 1, array.length); + Support.catArgs(Arrays.asList(subarray), count - 1, buf); + if (Fsd.serverInterface.sendMulticast(null, array[0], buf.toString(), ProtocolConstants.CL_MESSAGE, 1, null) == 0) { + uprintf("client '%s' unknown.\r\n", array[0]); + } + } + + private void execClients(String[] array, int count) { + Client temp = null; + if (count > 0) { + Client match = null; + for (Client client : Client.clients) { + temp = client; + if (temp.getCallsign().contains(array[0]) || temp.getCid().contains(array[0])) { + if (match != null) { + break; + } + match = temp; + } + } + if (temp == null) { + if (match == null) { + uprintf("No match.\r\n"); + return; + } + uprintf("Callsign : %s\r\n", match.getCallsign()); + if (StringUtils.isNotEmpty(match.getRealName())) { + uprintf("Real name : %s\r\n", match.getRealName()); + } + uprintf("Type : %s\r\n", match.getType() == ClientConstants.CLIENT_ATC ? + "atc" : "pilot"); + uprintf("Online : %s\r\n", Support.sprintTime(Support.mtime() - match.getStartTime())); + uprintf("Idle : %s\r\n", Support.sprintTime(Support.mtime() - match.getAlive())); + if (match.getFrequency() != 0 && match.getFrequency() < 100000) { + uprintf("Frequency : 1%02d.%03d\r\n", match.getFrequency() / 1000, match.getFrequency() % 1000); + } + if (match.getPositionOk() == 1) { + uprintf("Location : %s\r\n", Support.printLoc(match.getLat(), match.getLon())); + uprintf("Altitude : %d\r\n", match.getAltitude()); + if (match.getType() == ClientConstants.CLIENT_PILOT) { + uprintf("Groundspeed : %d\r\n", match.getGroundSpeed()); + uprintf("Transponder : %d\r\n", match.getTransponder()); + } else { + uprintf("Facility : %d\r\n", match.getFacilityType()); + } + } + return; + } + } + uprintf("Call-sign CID Online Idle Type Server Frequency\r\n"); + uprintf("-----------------------------------------------------------------\r\n"); + for (Client client : Client.clients) { + temp = client; + if (count == 0 || temp.getCallsign().contains(array[0]) || temp.getCid().contains(array[0])) { + uprintf("%-10s %-7s %s %s %-5s %-10s", temp.getCallsign(), temp.getCid(), + Support.sprintTime(Support.mtime() - temp.getStartTime()), + Support.sprintTime(Support.mtime() - temp.getAlive()), + temp.getType() == ClientConstants.CLIENT_ATC ? "ATC" : "PILOT", temp.getLocation().getIdent(), + temp.getFrequency()); + if (temp.getFrequency() != 0 && temp.getFrequency() < 100000) + uprintf(" 1%02d.%03d", temp.getFrequency() / 1000, temp.getFrequency() % 1000); + uprintf("%s\r\n", ""); + } + } + } + + private void execCert(String[] array, int count) { + if (count < 2) { + uprintf("CID Max level Origin Creation (UTC) Last used (local)\r\n"); + uprintf("---------------------------------------------------------------------------\r\n"); + for (Certificate c : Certificate.certs) { + if (count == 0 || c.getCid().contains(array[0])) { + uprintf("%-10s %-13s %-10s %s %s\r\n", c.getCid(), CertificateConstants.CERT_LEVEL[c.getLevel()], + c.getOrigin(), Support.sprintDate(c.getCreation()), Support.sprintDate(c.getPrevVisit())); + return; + } + } + } + int mode; + if ("add".equalsIgnoreCase(array[0])) { + mode = ProtocolConstants.CERT_ADD; + } else if ("delete".equalsIgnoreCase(array[0])) { + mode = ProtocolConstants.CERT_DELETE; + } else if ("modify".equalsIgnoreCase(array[0])) { + mode = ProtocolConstants.CERT_MODIFY; + } else { + uprintf("Invalid mode, should be 'add', 'delete' or 'modify'.\r\n"); + return; + } + + int level, ok = 0; + Certificate c = Certificate.getCert(array[1]); + switch (mode) { + case ProtocolConstants.CERT_ADD: + if (count < 4) { + usage(SystemConstants.SYS_CERT, "add "); + return; + } + if (c != null) { + uprintf("Certificate already exists.\r\n"); + return; + } + for (level = 0; level <= GlobalConstants.LEV_MAX; level++) + if (array[3].equalsIgnoreCase(CertificateConstants.CERT_LEVEL[level])) { + ok = 1; + break; + } + if (ok == 0) { + uprintf("Unknown level, use 'help cert' for help.\r\n"); + return; + } + c = new Certificate(array[1], array[2], level, Support.mgmtime(), Server.myServer.getIdent()); + Fsd.serverInterface.sendCert("*", mode, c, null); + break; + case ProtocolConstants.CERT_DELETE: + if (c == null) { + uprintf("Certificate does not exist.\r\n"); + return; + } + Fsd.serverInterface.sendCert("*", mode, c, null); + c.close(); + break; + case ProtocolConstants.CERT_MODIFY: + if (c == null) { + uprintf("Certificate does not exist.\r\n"); + return; + } + if (count < 3) { + usage(SystemConstants.SYS_CERT, "modify "); + return; + } + for (level = 0; level <= GlobalConstants.LEV_MAX; level++) + if (array[2].equalsIgnoreCase(CertificateConstants.CERT_LEVEL[level])) { + ok = 1; + break; + } + if (ok == 0) { + uprintf("Unknown level, use 'help cert' for help.\r\n"); + return; + } + c.configure(null, level, Support.mgmtime(), Server.myServer.getIdent()); + Fsd.serverInterface.sendCert("*", mode, c, null); + break; + } + uprintf("Your change has been executed, but the certificate file\r\n"); + uprintf("has not been updated. Please update this file if your change\r\n"); + uprintf("should be permanent.\r\n"); + } + + private void execDistance(String[] array, int count) { + if (count < 2) { + usage(SystemConstants.SYS_DISTANCE, " "); + return; + } + Client c1 = Client.getClient(array[0]); + if (c1 == null) { + uprintf("client '%s' not found.\r\n", array[0]); + return; + } + Client c2 = Client.getClient(array[1]); + if (c2 == null) { + uprintf("client '%s' not found.\r\n", array[1]); + return; + } + if (c1.getPositionOk() == 0) { + uprintf("client '%s' did not send a position report.\r\n", array[0]); + return; + } + if (c2.getPositionOk() == 0) { + uprintf("client '%s' did not send a position report.\r\n", array[1]); + return; + } + uprintf("%f NM.\r\n", c1.distance(c2)); + } + + private void execTime(String[] array, int count) { + long now = Support.mtime(); + uprintf("%s local", DateTimeFormatter.ofPattern("HH:mm:ss").format(Instant.ofEpochMilli(now))); + long gmtNow = Support.mgmtime(); + uprintf("%s UTC", DateTimeFormatter.ofPattern("HH:mm:ss").format(Instant.ofEpochMilli(gmtNow).atZone(ZoneId.of("UTC")))); + } + + private void execWeather(String[] array, int count) { + if (count < 1) { + usage(SystemConstants.SYS_WEATHER, ""); + return; + } + Server s = MetarManage.metarManager.requestMetar(Server.myServer.getIdent(), array[0], 1, fd); + if (s != null) { + uprintf("Weather request sent to METAR host %s.\n\r", s.getIdent()); + } else { + uprintf("No METAR host in network.\r\n"); + } + } + + void execRange(String[] array, int count) { + if (count < 1) { + usage(SystemConstants.SYS_RANGE, ""); + return; + } + Client temp = Client.getClient(array[0]); + if (temp == null) { + uprintf("No such callsign.\n\r"); + return; + } + uprintf("Range : %d NM.\r\n", temp.getRange()); + } + + void execLog(String[] array, int count) { + // TODO +// if (count<2) +// { +// usage(SystemConstants.SYS_LOG,"(show|delete) [amount]\r\n Where level " +// "is 0=Emergency, 1=Alert, 2=Critical, 3=Error, 4=Warning, 5=Info\r\n" +// " 6=Debug"); +// return; +// } +// int mode, maxlevel=NumberUtils.toInt(array[1]), amount; +// if (count>2) amount=NumberUtils.toInt(array[2]); else amount=MAXLOG; +// if (amount>MAXLOG) amount=MAXLOG; +// if (maxlevel>=L_MAX) maxlevel=L_MAX-1; +// if (!STRCASECMP(array[0],"show")) mode=1; else +// if (!STRCASECMP(array[0],"delete")) mode=2; else +// { +// uprintf("Invalid operation mode, use 'show' or 'delete'.\r\n"); +// return; +// } +// int msgp=logp; +// while (amount) +// { +// if (--msgp<0) msgp+=MAXLOG; +// if (msgp==logp) break; +// if (loghistory[msgp].msg==NULL) break; +// if (loghistory[msgp].level>maxlevel) continue; +// if (mode==1) uprintf("%s\r\n", loghistory[msgp].msg); else +// { +// free(loghistory[msgp].msg); +// loghistory[msgp].msg=NULL; +// } +// amount--; +// } + } + + void execPwd(String[] array, int count) { + if (count < 1) { + usage(SystemConstants.SYS_PWD, ""); + return; + } + ConfigGroup sgroup = Fsd.configManager.getGroup("system"); + ConfigEntry sentry = null; + if (sgroup != null) { + sentry = sgroup.getEntry("password"); + } + String buf = sentry != null ? sentry.getData() : null; + if (buf != null && buf.equals(array[0])) + authorized = 1; + if (authorized == 0) + uprintf("Password incorrect.\r\n"); + else uprintf("Password correct.\r\n"); + } + + void execWall(String[] array, int count) { + if (count < 1) { + usage(SystemConstants.SYS_PWD, ""); + return; + } + String buf = Support.catArgs(Arrays.asList(array), count, new StringBuilder()); + for (AbstractUser temp : Fsd.clientInterface.getUsers()) { + ClientUser ctemp = (ClientUser) temp; + Client c = ctemp.getThisClient(); + if (c == null) { + continue; + } + Fsd.clientInterface.sendGeneric(c.getCallsign(), c, null, null, "server", buf, ProtocolConstants.CL_MESSAGE); + } + } + + void execDelGuard(String[] array, int count) { + Fsd.serverInterface.delGuard(); + uprintf("Guard connections deleted.\r\n"); + } + + void execMetar(String[] array, int count) { + if (count < 1) { + usage(SystemConstants.SYS_METAR, ""); + return; + } + Server s = MetarManage.metarManager.requestMetar(Server.myServer.getIdent(), array[0], 0, fd); + if (s != null) { + uprintf("Metar request sent to METAR host %s.\n\r", s.getIdent()); + } else { + uprintf("No METAR host in network.\r\n"); + } + } + + void execWp(String[] array, int count) { + String mode = array[0]; + String help = " ..."; + if (count == 0) { + usage(SystemConstants.SYS_WP, help); + return; + } + if (mode.equals("show")) { + if (count == 1) { + uprintf("Active Name Origin Creation\r\n"); + uprintf("---------------------------------\r\n"); + for (WProfile wp : Weather.wProfiles) + uprintf("%-6s %-4s %-10s %s\r\n", wp.getActive() == 1 ? "Yes" : "No", wp.getName(), + wp.getOrigin() != null ? wp.getOrigin() : "", Support.sprintDate(wp.getCreation())); + } else { + WProfile wp = Weather.getWProfile(array[1]); + if (wp == null) { + uprintf("Could not find weather profile '%s'\r\n", array[1]); + return; + } else { + printWeather(wp); + } + } + return; + } + if (count < 2) { + usage(SystemConstants.SYS_WP, help); + return; + } + array[1] = array[1].toUpperCase(); + WProfile wp = Weather.getWProfile(array[1]); + if (mode.equalsIgnoreCase("create")) { + if (wp != null) { + uprintf("Weather profile already exists!\r\n"); + return; + } + Server s = MetarManage.metarManager.requestMetar(Server.myServer.getIdent(), array[1], 1, -2); + if (s != null) { + uprintf("A METAR request has been sent out to server %s to get the initial data.\r\n", s.getIdent()); + uprintf("The weather profile will created when the data arrives.\r\n"); + } else { + new WProfile(array[1], Support.mgmtime(), Server.myServer.getIdent()); + uprintf("No METAR host in network.\r\n"); + uprintf("The weather profile has been created without default data.\r\n"); + } + return; + } + if (wp == null) { + uprintf("Could not find weather profile '%s'\r\n", array[1]); + return; + } + if (mode.equalsIgnoreCase("delete")) { + if (wp.getActive() == 1) + Fsd.serverInterface.sendDelWp(wp); + wp.close(); + uprintf("Weather profile deleted.\r\n"); + } else if (mode.equalsIgnoreCase("activate")) { + wp.activate(); + wp.genRawCode(); + Fsd.serverInterface.sendAddWp(null, wp); + uprintf("Weather profile now in effect!\r\n"); + } else if (mode.equalsIgnoreCase("set")) { + String p = array[2], v = array[3]; + Scanner scanner = new Scanner(v); + if (!scanner.hasNextInt()) { + uprintf("Invalid value '%s'\r\n", v); + } else { + int val = scanner.nextInt(); + doSet(wp, p, val); + } + } else { + uprintf("Invalid wp mode.\r\n"); + } + } + + void execKill(String[] array, int count) { + if (count < 2) { + usage(SystemConstants.SYS_KILL, " "); + return; + } + Client c = Client.getClient(array[0]); + if (c == null) { + uprintf("Could not find client '%s'\r\n", array[0]); + return; + } + String[] subarray = ArrayUtils.subarray(array, 1, array.length); + String buf = Support.catArgs(Arrays.asList(subarray), count - 1, new StringBuilder()); + Fsd.serverInterface.sendKill(c, buf); + uprintf("Killed\r\n"); + } + + void execPos(String[] array, int count) { + if (count < 2) { + usage(SystemConstants.SYS_POS, " "); + return; + } + int num = NumberUtils.toInt(array[0]); + for (AbstractUser temp : Fsd.serverInterface.getUsers()) + if (temp.fd == num) { + try (FileOutputStream output = new FileOutputStream(array[1])) { + output.write(temp.outBuf.array()); + } catch (IOException e) { + uprintf("%s: %s", array[1], e.getMessage()); + return; + } + uprintf("Done\r\n"); + return; + } + uprintf("Can't find fd\r\n"); + } + + void execRefMetar(String[] array, int count) { + if (MetarManage.metarManager.getSource() == MetarSource.SOURCE_NETWORK) { + uprintf("Servers doesn't use a local METAR file.\r\n"); + return; + } + if (MetarManage.metarManager.isDownloading()) { + uprintf("Already downloading latest METAR information.\r\n"); + return; + } + uprintf("Starting download of metar data.\r\n"); + MetarManage.metarManager.startDownload(); + } + + public void printWeather(WProfile wp) { + uprintf("\r\nWeather profile %s:\r\n\r\n", wp.getName()); + int x; + StringBuilder line; + String buf; + + line = new StringBuilder(String.format("%-20s%-10s%-10s", "Cloud layer", "1", "2")); + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Floor")); + for (x = 0; x < 2; x++) { + buf = String.format("%-10d", wp.getClouds().get(x).getFloor()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Ceiling")); + for (x = 0; x < 2; x++) { + buf = String.format("%-10d", wp.getClouds().get(x).getCeiling()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Coverage")); + for (x = 0; x < 2; x++) { + buf = String.format("%-10d", wp.getClouds().get(x).getCoverage()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Turbulence")); + for (x = 0; x < 2; x++) { + buf = String.format("%-10d", wp.getClouds().get(x).getTurbulence()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Icing")); + for (x = 0; x < 2; x++) { + buf = String.format("%-10s", wp.getClouds().get(x).getIcing() == 1 ? "Yes" : "No"); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + + + line = new StringBuilder(String.format("%-20s%-10s%-10s%-10s%-10s", "Wind layer", "1", "2", "3", "4")); + uprintf("\r\n%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Floor")); + for (x = 0; x < 4; x++) { + buf = String.format("%-10d", wp.getWinds().get(x).getFloor()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Ceiling")); + for (x = 0; x < 4; x++) { + buf = String.format("%-10d", wp.getWinds().get(x).getCeiling()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Direction")); + for (x = 0; x < 4; x++) { + buf = String.format("%-10d", wp.getWinds().get(x).getDirection()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Speed")); + for (x = 0; x < 4; x++) { + buf = String.format("%-10d", wp.getWinds().get(x).getSpeed()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Gusting")); + for (x = 0; x < 4; x++) { + buf = String.format("%-10s", wp.getWinds().get(x).getGusting() > 0 ? "Yes" : "No"); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + + line = new StringBuilder(String.format("%-20s%-10s%-10s%-10s%-10s", "Temp layer", "1", "2", "3", "4")); + uprintf("\r\n%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Ceiling")); + for (x = 0; x < 4; x++) { + buf = String.format("%-10d", wp.getTemps().get(x).getCeiling()); + line.append(buf); + } + uprintf("%s\r\n", line.toString()); + line = new StringBuilder(String.format("%-20s", " Temperature")); + for (x = 0; x < 4; x++) { + buf = String.format("%-10d", wp.getTemps().get(x).getTemp()); + line.append(buf); + } + uprintf("%s\r\n\r\n", line.toString()); + + uprintf("Visibility : %.2f SM\r\n", wp.getVisibility()); + uprintf("Barometer : %d Hg\r\n", wp.getBarometer()); + printPrompt(); + } + + public void printMetar(String wp, String data) { + uprintf("\r\nWeather profile %s:\r\n\r\n", wp); + uprintf("%s\r\n", data); + printPrompt(); + } + + void doSet(WProfile wp, String p, int val) { + int x, ok; + String[] cat1 = { + "winds", "clouds", "temperature", "pressure", "visibility" + }; + String where = null, more = null; + int find = p.indexOf('.'); + if (find != -1) { + where = p.substring(find + 1); + } + ok = 0; + for (x = 0; x < 5; x++) + if (p.equalsIgnoreCase(cat1[x])) { + ok = 1; + break; + } + if (ok == 0) { + uprintf("Invalid category '%s'.\r\n", p); + return; + } + switch (x) { + case 3: + wp.setBarometer(val); + return; + case 4: + wp.setVisibility(val); + return; + } + if (StringUtils.isEmpty(where)) { + uprintf("Invalid parameter\r\n"); + return; + } + String[] split = where.split("."); + if (split.length > 1) { + more = split[1]; + } + Scanner scanner = new Scanner(where); + if (!scanner.hasNextInt()) { + uprintf("Invalid index '%s'\r\n", where); + return; + } + int index = scanner.nextInt(); + index--; + if (index < 0 || (x == 1 && index > 2) || index > 4) { + uprintf("Invalid index %d\r\n", index); + return; + } + if (StringUtils.isEmpty(more)) { + uprintf("Invalid parameter\r\n"); + return; + } + where = more; + String[] strings = new String[10]; + switch (x) { + case 0: + strings[0] = "floor"; + strings[1] = "ceiling"; + strings[2] = "direction"; + strings[3] = "speed"; + strings[4] = "gusting"; + strings[5] = null; + break; + case 1: + strings[0] = "floor"; + strings[1] = "ceiling"; + strings[2] = "coverage"; + strings[3] = "turbulence"; + strings[4] = "icing"; + strings[5] = null; + break; + case 2: + strings[0] = "ceiling"; + strings[1] = "temperature"; + strings[2] = null; + break; + } + int y; + ok = 0; + for (y = 0; strings[y] != null; y++) + if (where.equalsIgnoreCase(strings[y])) { + ok = 1; + break; + } + if (ok == 0) { + uprintf("Invalid specifier '%s'\r\n", where); + return; + } + switch (x) { + case 0: + switch (y) { + case 0: + wp.getWinds().get(index).setFloor(val); + break; + case 1: + wp.getWinds().get(index).setCeiling(val); + break; + case 2: + wp.getWinds().get(index).setDirection(val); + break; + case 3: + wp.getWinds().get(index).setSpeed(val); + break; + case 4: + wp.getWinds().get(index).setGusting(val > 0 ? 1 : 0); + break; + } + break; + case 1: + switch (y) { + case 0: + wp.getClouds().get(index).setFloor(val); + break; + case 1: + wp.getClouds().get(index).setCeiling(val); + break; + case 2: + wp.getClouds().get(index).setCoverage(val); + break; + case 3: + wp.getClouds().get(index).setTurbulence(val); + break; + case 4: + wp.getClouds().get(index).setIcing(val > 0 ? 1 : 0); + break; + } + break; + case 2: + switch (y) { + case 0: + wp.getTemps().get(index).setCeiling(val); + break; + case 1: + wp.getTemps().get(index).setTemp(val); + break; + } + break; + } + } + + public double getLat() { + return lat; + } + + public void setLat(double lat) { + this.lat = lat; + } + + public double getLon() { + return lon; + } + + public void setLon(double lon) { + this.lon = lon; + } +} diff --git a/src/main/java/com/hans0924/fsd/weather/CloudLayer.java b/src/main/java/com/hans0924/fsd/weather/CloudLayer.java new file mode 100644 index 0000000..e886530 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/weather/CloudLayer.java @@ -0,0 +1,61 @@ +package com.hans0924.fsd.weather; + +/** + * @author Hanshuo Zeng + * @since 2020-03-04 + */ +public class CloudLayer { + private int ceiling; + private int floor; + private int coverage; + private int icing; + private int turbulence; + + public CloudLayer() { + } + + public CloudLayer(int ceiling, int floor) { + this.ceiling = ceiling; + this.floor = floor; + } + + public int getCeiling() { + return ceiling; + } + + public void setCeiling(int ceiling) { + this.ceiling = ceiling; + } + + public int getFloor() { + return floor; + } + + public void setFloor(int floor) { + this.floor = floor; + } + + public int getCoverage() { + return coverage; + } + + public void setCoverage(int coverage) { + this.coverage = coverage; + } + + public int getIcing() { + return icing; + } + + public void setIcing(int icing) { + this.icing = icing; + } + + public int getTurbulence() { + return turbulence; + } + + public void setTurbulence(int turbulence) { + this.turbulence = turbulence; + } +} diff --git a/src/main/java/com/hans0924/fsd/weather/TempLayer.java b/src/main/java/com/hans0924/fsd/weather/TempLayer.java new file mode 100644 index 0000000..e4a817e --- /dev/null +++ b/src/main/java/com/hans0924/fsd/weather/TempLayer.java @@ -0,0 +1,33 @@ +package com.hans0924.fsd.weather; + +/** + * @author Hanshuo Zeng + * @since 2020-03-04 + */ +public class TempLayer { + private int ceiling; + private int temp; + + public TempLayer() { + } + + public TempLayer(int ceiling) { + this.ceiling = ceiling; + } + + public int getCeiling() { + return ceiling; + } + + public void setCeiling(int ceiling) { + this.ceiling = ceiling; + } + + public int getTemp() { + return temp; + } + + public void setTemp(int temp) { + this.temp = temp; + } +} diff --git a/src/main/java/com/hans0924/fsd/weather/WProfile.java b/src/main/java/com/hans0924/fsd/weather/WProfile.java new file mode 100644 index 0000000..b68623d --- /dev/null +++ b/src/main/java/com/hans0924/fsd/weather/WProfile.java @@ -0,0 +1,702 @@ + +package com.hans0924.fsd.weather; + +import com.hans0924.fsd.constants.WeatherConstants; +import com.hans0924.fsd.process.metar.MetarManage; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.math.NumberUtils; + +import java.util.*; + +/** + * @author Hanshuo Zeng + * @since 2020-03-04 + */ +public class WProfile { + + private String name; + + private String origin; + + private String rawCode; + + private List winds; + + private List clouds; + + private List temps; + + private CloudLayer tstorm; + + private int barometer; + + private float visibility; + + private int dewPoint; + + private long creation; + + private long version; + + private int active; + + public WProfile(String name, long creation, String origin) { + this.creation = creation; + active = 0; + this.origin = origin; + winds = new ArrayList<>(4); + clouds = new ArrayList<>(2); + temps = new ArrayList<>(4); + + winds.add(new WindLayer(-1, -1)); + winds.add(new WindLayer(10400, 2500)); + winds.add(new WindLayer(22600, 10400)); + winds.add(new WindLayer(90000, 22700)); + + temps.add(new TempLayer(100)); + temps.add(new TempLayer(10000)); + temps.add(new TempLayer(18000)); + temps.add(new TempLayer(35000)); + + dewPoint = 0; + for (int i = 0; i < 2; i++) { + clouds.add(new CloudLayer(-1, -1)); + } + tstorm = new CloudLayer(-1, -1); + + visibility = 15.0f; + barometer = 2950; + + this.name = name; + } + + public void close() { + Weather.wProfiles.remove(this); + } + + public void activate() { + active = 1; + } + + public void parseMetar(String[] array, int count) { + int index = 0; + String station; + + /* First field could be 'METAR' */ + if (index >= array.length || StringUtils.isBlank(array[index])) { + return; + } + if (StringUtils.equalsIgnoreCase(array[index], "metar")) { + index++; + } + + /* The station field */ + if (index >= array.length || StringUtils.isBlank(array[index])) { + return; + } + station = array[index++]; + + /* date and time */ + if (index >= array.length || StringUtils.isBlank(array[index])) { + return; + } + if (array[index].endsWith("Z")) { + index++; + } + + /* Here there could be 'AUTO' or 'COR' */ + if (index >= array.length || StringUtils.isBlank(array[index])) { + return; + } + if (StringUtils.equalsIgnoreCase(array[index], "auto")) { + index++; + } else if (StringUtils.equalsIgnoreCase(array[index], "cor")) { + index++; + } + + /* Wind speed and direction */ + index += parseWind(ArrayUtils.subarray(array, index, array.length), count - index); + + /* Visibility */ + index += parseVis(ArrayUtils.subarray(array, index, array.length), count - index); + + /* Runway visual range */ + index += parseRvr(ArrayUtils.subarray(array, index, array.length), count - index); + + /* Weather phenomena */ + index += parseWx(ArrayUtils.subarray(array, index, array.length), count - index); + + /* Sky conditions */ + index += parseSky(ArrayUtils.subarray(array, index, array.length), count - index); + + /* Temperature */ + index += parseTemp(ArrayUtils.subarray(array, index, array.length), count - index); + + /* Barometer */ + index += parseAlt(ArrayUtils.subarray(array, index, array.length), count - index); + + /* Use dewpoint to fix visibility */ + fixVisibility(); + } + + public void loadArray(String[] array, int count) { + int x; + int index = 0; + barometer = NumberUtils.toInt(array[index++]); + visibility = NumberUtils.toInt(array[index++]); + + /* load cloud data */ + for (x = 0; x < 2; x++) { + CloudLayer layer = clouds.get(x); + layer.setCeiling(NumberUtils.toInt(array[index++])); + layer.setFloor(NumberUtils.toInt(array[index++])); + layer.setCoverage(NumberUtils.toInt(array[index++])); + layer.setIcing(NumberUtils.toInt(array[index++])); + layer.setTurbulence(NumberUtils.toInt(array[index++])); + } + + /* load thunderstorm data */ + tstorm.setCeiling(NumberUtils.toInt(array[index++])); + tstorm.setFloor(NumberUtils.toInt(array[index++])); + tstorm.setCoverage(NumberUtils.toInt(array[index++])); + tstorm.setIcing(NumberUtils.toInt(array[index++])); + tstorm.setTurbulence(NumberUtils.toInt(array[index++])); + + /* load wind data */ + for (x = 0; x < 4; x++) { + WindLayer layer = winds.get(x); + layer.setCeiling(NumberUtils.toInt(array[index++])); + layer.setFloor(NumberUtils.toInt(array[index++])); + layer.setDirection(NumberUtils.toInt(array[index++])); + layer.setSpeed(NumberUtils.toInt(array[index++])); + layer.setGusting(NumberUtils.toInt(array[index++])); + layer.setTurbulence(NumberUtils.toInt(array[index++])); + } + + /* load temp data */ + for (x = 0; x < 4; x++) { + TempLayer layer = temps.get(x); + layer.setCeiling(NumberUtils.toInt(array[index++])); + layer.setTemp(NumberUtils.toInt(array[index++])); + } + } + + public String print() { + int x; + StringBuilder data; + String piece; + data = new StringBuilder(String.format("%d:%.2f:", barometer, visibility)); + /* Add cloud data */ + for (x = 0; x < 2; x++) { + CloudLayer c = clouds.get(x); + piece = String.format("%d:%d:%d:%d:%d:", c.getCeiling(), c.getFloor(), c.getCoverage(), c.getIcing(), + c.getTurbulence()); + data.append(piece); + } + /* Add thunderstorm data */ + CloudLayer c = tstorm; + piece = String.format("%d:%d:%d:%d:%d:", c.getCeiling(), c.getFloor(), c.getCoverage(), c.getIcing(), + c.getTurbulence()); + data.append(piece); + /* Add wind data */ + for (x = 0; x < 4; x++) { + WindLayer w = winds.get(x); + piece = String.format("%d:%d:%d:%d:%d:%d:", w.getCeiling(), w.getFloor(), w.getDirection(), w.getSpeed(), + w.getGusting(), w.getTurbulence()); + data.append(piece); + } + /* Add temp data */ + for (x = 0; x < 4; x++) { + TempLayer t = temps.get(x); + piece = String.format("%d:%d:", t.getCeiling(), t.getTemp()); + data.append(piece); + } + + return data.toString(); + } + + private int parseWind(String[] array, int count) { + if (count == 0) { + return 0; + } + if (array[0].length() < 3) { + return 0; + } + if (!array[0].endsWith("kt") && !array[0].endsWith("mps")) { + return 0; + } + String wind = array[0]; + String gusting = null; + if (array[0].contains("G")) { + winds.get(0).setGusting(1); + String[] split = array[0].split("G"); + wind = split[0]; + gusting = split[1].replace("kt", "").replace("mps", ""); + } + winds.get(0).setSpeed(NumberUtils.toInt(wind.substring(3))); + winds.get(0).setCeiling(2500); + winds.get(0).setFloor(0); + winds.get(0).setDirection(NumberUtils.toInt(wind.substring(0, 3))); + + if (count == 1) { + return 1; + } + + if (array[1].contains("V")) { + // TODO unused? + return 2; + } + return 1; + } + + private int parseVis(String[] array, int count) { + if (count == 0) { + return 0; + } + + visibility = 10.00f; + + if (StringUtils.equalsIgnoreCase(array[0], "M1/4SM")) { + visibility = 0.15f; + return 1; + } + + if (StringUtils.equalsIgnoreCase(array[0], "1/4SM")) { + visibility = 0.25f; + return 1; + } + + if (StringUtils.equalsIgnoreCase(array[0], "1/2SM")) { + visibility = 0.50f; + return 1; + } + + if (StringUtils.equalsIgnoreCase(array[0], "CAVOK") || StringUtils.equals(array[0], "////") + || StringUtils.equals(array[0], "CLR")) { + visibility = 15.00f; + CloudLayer cloudLayer = clouds.get(1); + cloudLayer.setCeiling(26000); + cloudLayer.setFloor(24000); + cloudLayer.setIcing(0); + cloudLayer.setTurbulence(0); + cloudLayer.setCoverage(1); + return 1; + } + + Scanner scanner = new Scanner(array[0]); + visibility = scanner.nextFloat(); + if (array[0].contains("SM")) { + return 1; + } + if (count > 1 && array[1].contains("SM")) { + return 2; + } + if (array[0].contains("KM")) { + visibility /= 1.609; + return 1; + } + if (count > 1 && array[1].contains("KM")) { + visibility /= 1.609; + return 2; + } + if (visibility == 9999) { + visibility = 15.00f; + } else { + visibility /= 1609.0f; + } + + return 1; + } + + private int parseWx(String[] array, int count) { + int amount = 0, i; + String[] patterns = { "+", "-", "VC", "MI", "BL", "PR", "SH", "BC", "TS", "DR", "FZ", "DZ", "OC", "UP", "RA", + "PE", "SN", "GR", "SG", "GS", "BR", "DU", "FG", "SA", "FU", "HZ", "VA", "PY", "PO", "DS", "SQ", "FC", + "SS", "PLUS" }; + while (amount != count) { + for (i = 0; i < patterns.length; i++) { + if (array[amount].startsWith(patterns[i])) { + break; + } + } + amount++; + } + + return amount; + } + + int parseSky(String[] array, int count) { + int amount = 0, i; + String[] patterns = { "SKC", "CLR", "VV", "FEW", "SCT", "BKN", "OVC" }; + int[] coverage = { 0, 0, 8, 1, 3, 5, 8, 0 }; + while (amount != count) { + for (i = 0; i < patterns.length; i++) { + if (array[amount].startsWith(patterns[i])) { + break; + } + } + if (amount < 2) { + int base; + String baseString = array[amount].substring(patterns[i].length()); + Scanner scanner = new Scanner(baseString); + if (scanner.hasNextInt()) { + base = scanner.nextInt(); + } else { + base = 10; + } + base *= 100; + clouds.get(amount).setCoverage(coverage[i]); + clouds.get(amount).setFloor(base); + } + amount++; + } + + if (amount == 1) { + clouds.get(0).setCeiling(clouds.get(0).getFloor() + 3000); + clouds.get(0).setTurbulence(17); + } else if (amount > 1) { + if (clouds.get(1).getFloor() > clouds.get(0).getFloor()) { + clouds.get(0).setCeiling( + clouds.get(0).getFloor() + (clouds.get(1).getFloor() - clouds.get(0).getFloor()) / 2); + clouds.get(1).setCeiling(clouds.get(1).getFloor() + 3000); + } else { + clouds.get(1).setCeiling( + clouds.get(1).getFloor() + (clouds.get(0).getFloor() - clouds.get(1).getFloor()) / 2); + clouds.get(0).setCeiling(clouds.get(0).getFloor() + 3000); + } + clouds.get(0).setTurbulence((clouds.get(0).getCeiling() - clouds.get(0).getFloor()) / 175); + clouds.get(1).setTurbulence((clouds.get(1).getCeiling() - clouds.get(1).getFloor()) / 175); + } + + return amount; + } + + private int parseTemp(String[] array, int count) { + if (count == 0) { + return 0; + } + + int p = array[0].indexOf('/'); + if (p == -1) { + return 0; + } + String[] split = array[0].split("/"); + if (split[0].startsWith("M")) { + temps.get(0).setTemp(-(NumberUtils.toInt(split[0].substring(1)))); + } else { + temps.get(0).setTemp(NumberUtils.toInt(split[0])); + } + temps.get(0).setCeiling(100); + if (split[1].startsWith("M")) { + dewPoint = -(NumberUtils.toInt(split[0].substring(1))); + } else { + dewPoint = NumberUtils.toInt(split[0]); + } + + if (temps.get(0).getTemp() > -10 && temps.get(0).getTemp() < 10) { + if (clouds.get(0).getCeiling() < 12000) { + clouds.get(0).setIcing(1); + } + if (clouds.get(1).getCeiling() < 12000) { + clouds.get(1).setIcing(1); + } + } + + return 1; + } + + private int parseRvr(String[] array, int count) { + int amount = 0; + while (amount < count && array[amount].contains("/")) { + amount++; + } + return amount; + } + + private int parseAlt(String[] array, int count) { + if (count == 0) { + return 0; + } + barometer = 2992; + if (array[0].charAt(0) == 'A') { + barometer = NumberUtils.toInt(array[0].substring(1)); + } else if (array[0].charAt(0) == 'Q') { + barometer = NumberUtils.toInt(array[0].substring(1)); + barometer = (int) Math.round((barometer * 100.0 * 100.0) / (1333.0 * 2.54)); + } else { + return 0; + } + return 1; + } + + private void fixVisibility() { + // disabled + // if (visibility >= 10.00f) { + // visibility += temps.get(0).getTemp() - dewPoint - 10; + // if (visibility <= 30.00f) { + // if ((int) visibility % 5 > 2) { + // visibility += 5; + // } + // visibility -= (int) visibility % 5; + // } else { + // if ((int) visibility % 10 > 5) { + // visibility += 10; + // } + // visibility -= (int) visibility % 10; + // } + // } + } + + public WProfile getWProfile(String name) { + return Weather.getWProfile(name); + } + + public int getSeason(double lat) { + Calendar calendar = Calendar.getInstance(); + int month = calendar.get(Calendar.MONTH); + int season; + switch (month) { + case 11: + case 0: + case 1: + season = 0; + break; + case 5: + case 6: + case 7: + season = 2; + break; + case 2: + case 3: + case 4: + season = 1; + break; + case 8: + case 9: + case 10: + season = 3; + break; // fixed a bug + default: + season = 1; + } + if (lat < 0) { + if (season == 0) { + season = 2; + } else if (season == 2) { + season = 0; + } + } + return season; + } + + public void fix(double lat, double lon) { + double a1 = lat; + double a2 = Math.abs(lon / 18.0); + int season = getSeason(a1); + int coriolisVar; + int latVar = MetarManage.metarManager.getVariation(WeatherConstants.VAR_UPDIRECTION, -25, 25); + if (a1 > 0) { + winds.get(3).setDirection((int) Math.round(6 * a1 + latVar + a2)); + } else { + winds.get(3).setDirection((int) Math.round(-6 * a1 + latVar + a2)); + } + winds.get(3).setDirection((winds.get(3).getDirection() + 360) % 360); + + int maxVelocity = 1; + switch (getSeason(a1)) { + case 0: + maxVelocity = 120; + break; + case 1: + case 3: + maxVelocity = 80; + break; + case 2: + maxVelocity = 50; + break; + } + winds.get(3).setSpeed((int) Math.round(Math.abs(Math.sin(a1 * Math.PI / 180.0f)) * maxVelocity)); + + /**************************************/ + latVar = MetarManage.metarManager.getVariation(WeatherConstants.VAR_MIDDIRECTION, 10, 45); + coriolisVar = MetarManage.metarManager.getVariation(WeatherConstants.VAR_MIDCOR, 10, 30); + if (a1 > 0) { + winds.get(2).setDirection((int) Math.round(6 * a1 + latVar + a2 - coriolisVar)); + } else { + winds.get(2).setDirection((int) Math.round(-6 * a1 + latVar + a2 - coriolisVar)); + } + winds.get(2).setDirection((winds.get(2).getDirection() + 360) % 360); + winds.get(2).setSpeed((int) (winds.get(3).getSpeed() + * (MetarManage.metarManager.getVariation(WeatherConstants.VAR_MIDSPEED, 500, 800) / 1000.0f))); + + /**************************************/ + int coriolisVarLow = coriolisVar + MetarManage.metarManager.getVariation(WeatherConstants.VAR_LOWCOR, 10, 30); + latVar = MetarManage.metarManager.getVariation(WeatherConstants.VAR_LOWDIRECTION, 10, 45); + if (a1 > 0) { + winds.get(1).setDirection((int) Math.round(6 * a1 + latVar + a2 - coriolisVarLow)); + } else { + winds.get(1).setDirection((int) Math.round(-6 * a1 + latVar + a2 - coriolisVar)); + } + winds.get(1).setDirection((winds.get(1).getDirection() + 360) % 360); + + winds.get(1).setSpeed((winds.get(0).getSpeed() + winds.get(1).getSpeed()) / 2); + + /**************************************/ + temps.get(3).setTemp(-57 + MetarManage.metarManager.getVariation(WeatherConstants.VAR_UPTEMP, -4, 4)); + temps.get(2).setTemp(-21 + MetarManage.metarManager.getVariation(WeatherConstants.VAR_MIDTEMP, -7, 7)); + temps.get(1).setTemp(-5 + MetarManage.metarManager.getVariation(WeatherConstants.VAR_LOWTEMP, -12, 12)); + } + + public void genRawCode() { + StringBuilder data; + String piece; + + /* Station */ + data = new StringBuilder(name); + + /* Zulu time */ + Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); + piece = String.format("%02d%02d%02dZ ", calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), + calendar.get(Calendar.MINUTE)); + data.append(piece); + + /* Winds */ + piece = String.format("%03d%02dKT ", winds.get(0).getDirection(), winds.get(0).getSpeed()); + data.append(piece); + + /* Visibility */ + piece = String.format("%02dSM ", (int) visibility); + data.append(piece); + + /* Clouds */ + int x; + for (x = 0; x < 2; x++) { + if (clouds.get(x).getCeiling() != -1) { + int c = clouds.get(0).getCoverage(); + piece = String.format("%s%03d ", + c == 0 ? "CLR" + : (c == 1 || c == 2) ? "FEW" + : (c == 3 || c == 4) ? "SCT" : (c == 5 || c == 6) ? "BKN" : "OVC", + clouds.get(0).getFloor() / 100); + data.append(piece); + } + } + + /* Temp */ + int temperature = temps.get(0).getTemp(); + int dew = visibility < 5 ? temperature - 1 : temperature + 1; + piece = String.format("%s%02d/%s%02d ", temperature < 0 ? "M" : "", Math.abs(temperature), dew < 0 ? "M" : "", + Math.abs(dew)); + data.append(piece); + + /* QNH */ + piece = String.format("A%04d", barometer); + data.append(piece); + rawCode = data.toString(); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getOrigin() { + return origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getRawCode() { + return rawCode; + } + + public void setRawCode(String rawCode) { + this.rawCode = rawCode; + } + + public List getWinds() { + return winds; + } + + public void setWinds(List winds) { + this.winds = winds; + } + + public List getClouds() { + return clouds; + } + + public void setClouds(List clouds) { + this.clouds = clouds; + } + + public List getTemps() { + return temps; + } + + public void setTemps(List temps) { + this.temps = temps; + } + + public CloudLayer getTstorm() { + return tstorm; + } + + public void setTstorm(CloudLayer tstorm) { + this.tstorm = tstorm; + } + + public int getBarometer() { + return barometer; + } + + public void setBarometer(int barometer) { + this.barometer = barometer; + } + + public float getVisibility() { + return visibility; + } + + public void setVisibility(float visibility) { + this.visibility = visibility; + } + + public int getDewPoint() { + return dewPoint; + } + + public void setDewPoint(int dewPoint) { + this.dewPoint = dewPoint; + } + + public long getCreation() { + return creation; + } + + public void setCreation(long creation) { + this.creation = creation; + } + + public long getVersion() { + return version; + } + + public void setVersion(long version) { + this.version = version; + } + + public int getActive() { + return active; + } + + public void setActive(int active) { + this.active = active; + } +} diff --git a/src/main/java/com/hans0924/fsd/weather/Weather.java b/src/main/java/com/hans0924/fsd/weather/Weather.java new file mode 100644 index 0000000..5388e38 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/weather/Weather.java @@ -0,0 +1,22 @@ +package com.hans0924.fsd.weather; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Hanshuo Zeng + * @since 2020-03-04 + */ +public class Weather { + public static List wProfiles = new ArrayList<>(); + + public static WProfile getWProfile(String name) { + for (WProfile wProfile : wProfiles) { + if (wProfile.getName().equals(name)) { + return wProfile; + } + } + + return null; + } +} diff --git a/src/main/java/com/hans0924/fsd/weather/WindLayer.java b/src/main/java/com/hans0924/fsd/weather/WindLayer.java new file mode 100644 index 0000000..31ba201 --- /dev/null +++ b/src/main/java/com/hans0924/fsd/weather/WindLayer.java @@ -0,0 +1,70 @@ +package com.hans0924.fsd.weather; + +/** + * @author Hanshuo Zeng + * @since 2020-03-04 + */ +public class WindLayer { + private int ceiling; + private int floor; + private int direction; + private int speed; + private int gusting; + private int turbulence; + + public WindLayer() { + } + + public WindLayer(int ceiling, int floor) { + this.ceiling = ceiling; + this.floor = floor; + } + + public int getCeiling() { + return ceiling; + } + + public void setCeiling(int ceiling) { + this.ceiling = ceiling; + } + + public int getFloor() { + return floor; + } + + public void setFloor(int floor) { + this.floor = floor; + } + + public int getDirection() { + return direction; + } + + public void setDirection(int direction) { + this.direction = direction; + } + + public int getSpeed() { + return speed; + } + + public void setSpeed(int speed) { + this.speed = speed; + } + + public int getGusting() { + return gusting; + } + + public void setGusting(int gusting) { + this.gusting = gusting; + } + + public int getTurbulence() { + return turbulence; + } + + public void setTurbulence(int turbulence) { + this.turbulence = turbulence; + } +} diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties index d5a370e..993074d 100644 --- a/src/main/resources/log4j.properties +++ b/src/main/resources/log4j.properties @@ -1,39 +1,4 @@ -#日志级别大小: DEBUG < INFO < WARN < ERROR < FATAL -#log4j.rootLogger 配置的是大于等于当前级别的日志信息的输出 -#log4j.rootLogger 用法:(注意appenderName可以是一个或多个) -#log4j.rootLogger = 日志级别,appenderName1,appenderName2,.... -#log4j.appender.appenderName1定义的是日志的输出方式,有两种:一种是命令行输出或者叫控制台输出,另一种是文件方式保存 -# 1)控制台输出则应该配置为org.apache.log4j.PatternLayout -# 2)文本方式保存应该配置为org.apache.log4j.DailyRollingFileAppender -# 3)也可以自定义 Appender类 -#log4j.appender.appenderName1.layout.ConversionPattern 定义的是日志内容格式 -#log4j.appender.appenderName1.file 定义了该日志文件的文件名称 -#log4j.appender.appenderName1.DatePattern 定义了日志文件重新生成的时间间隔,如果设置到天,则每天重新生成一个新的日志文件。 -# 旧的日志文件则以新的文件名保存,文件名称 = log4j.appender.appenderName1.file + log4j.appender.appenderName1.DatePattern -#log4j.rootLogger = info,stdout,file log4j.rootLogger = info,stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout -log4j.appender.stdout.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%C{1}:%L] - %m%n -#log4j.appender.file = org.apache.log4j.DailyRollingFileAppender -#log4j.appender.file.file=d\:\\log\\info(+).log -#log4j.appender.file.DatePattern= '.'yyyy-MM-dd -#log4j.appender.file.layout=org.apache.log4j.PatternLayout -#log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%C{1}:%L] - %m%n - - -# log4j.logger 用法如下 -# 1)log4j.logger.包名 = 日志级别 , appenderName1,appenderName2,.... -# 定义该包名下的所有类的日志输出 -# 2)log4j.logger.类全名含包名 = 日志级别 , appenderName1,appenderName2,.... -# 定义指定类的日志输出 -# 3) log4j.logger.日志对象Logger命名名称 = 日志级别 , appenderName1,appenderName2,.... -# 定义了某命名名称的日志的 输出,如: -# log4j.logger.Log1 就是指定义通过 Logger.getLogger("Log1") 获取的日志对象的日志输出 - -log4j.logger.edu.mvcdemo.controller = debug,controller_logfile -log4j.appender.controller_logfile = org.apache.log4j.DailyRollingFileAppender -log4j.appender.controller_logfile.file= fsd.log -log4j.appender.controller_logfile.DatePattern= '.'yyyy-MM-dd -log4j.appender.controller_logfile.layout=org.apache.log4j.PatternLayout -log4j.appender.controller_logfile.layout.ConversionPattern=[%p][%d{yyyy-MM-dd HH:mm:ss}][%C{1}:%L] - %m%n \ No newline at end of file +log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss.SSS} [%p] %m%n \ No newline at end of file