" + CR);
25 | return true;
26 | }
27 | try {
28 | int messageId = Integer.parseInt(data[1]);
29 | Message message = KISSet.INSTANCE.getStorage().getMessage(messageId);
30 | if (message == null) {
31 | writeToTerminal("*** Message not found" + CR);
32 | return true;
33 | } else {
34 | KISSet.INSTANCE.getStorage().deleteMessage(messageId);
35 | writeToTerminal("*** Message deleted" + CR);
36 | return true;
37 | }
38 | } catch (NumberFormatException e) {
39 | writeToTerminal("*** Invalid message number" + CR);
40 | return true;
41 | }
42 |
43 |
44 | }
45 |
46 |
47 | @Override
48 | public String[] getCommandNames() {
49 | return new String[]{"km", "kill"};
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/netrom/user/parser/Mode.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.netrom.user.parser;
2 |
3 |
4 | import java.util.Objects;
5 |
6 | /**
7 | * This represents the mode of the parser. The parser can be in different modes depending on the command that was
8 | * executed, for example message read pagination mode, message list pagination mode, to provide 'continue' prompts to the
9 | * user
10 | *
11 | * Other modes like can be made for example a 'File BBS' mode that uses different sets of plugin provided commands.
12 | *
13 | * Plugins implementing the @Commandable annotation check the mode to behaving accordingly. Commands can overload and use the
14 | * same command name as other commands, but this should be only done when they use their 'own' mode so as not to
15 | * intefere with app modes.
16 | */
17 | public class Mode {
18 |
19 | // Command mode
20 | public static final Mode CMD = new Mode("NODE_CMD");
21 | public static final Mode CONNECTED_TO_STATION = new Mode("NODE_CONNECTED_TO_STATION");
22 |
23 |
24 | private final String mode;
25 |
26 | /**
27 | * Creates a mode object which plugins can also create
28 | *
29 | * @param mode
30 | */
31 | public Mode(String mode) {
32 | this.mode = mode;
33 | }
34 |
35 | public String toString() {
36 | return mode;
37 | }
38 |
39 | @Override
40 | public boolean equals(Object o) {
41 | if (this == o) return true;
42 | if (o == null || getClass() != o.getClass()) return false;
43 | Mode mode1 = (Mode) o;
44 | return Objects.equals(mode, mode1.mode);
45 | }
46 |
47 | @Override
48 | public int hashCode() {
49 | return Objects.hash(mode);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/ConnStateChangeListener.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 |
3 | /*
4 | * Copyright (C) 2011-2022 Andrew Pavlin, KA2DDO
5 | * This file is part of YAAC.
6 | *
7 | * YAAC is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU Lesser General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * YAAC is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * and GNU Lesser General Public License along with YAAC. If not,
19 | * see .
20 | */
21 |
22 | /**
23 | * This listener interface allows classes outside the org.ka2ddo.ax25 hierarchy
24 | * to be informed when connected sessions are updated.
25 | */
26 | public interface ConnStateChangeListener {
27 | /**
28 | * Report that the row containing the specified pair of callsigns has been updated.
29 | * This is expected to be called from a thread other than the AWT dispatch thread.
30 | *
31 | * @param sender AX25Callsign of originator of session
32 | * @param dest AX25Callsign of recipient of session
33 | */
34 | void updateConnStateRow(AX25Callsign sender, AX25Callsign dest);
35 |
36 | /**
37 | * Report that a ConnState session has been added or removed from the
38 | * {@link AX25Stack}, but we don't know which row number it is.
39 | * This is expected to be called from a thread other than the AWT dispatch thread.
40 | */
41 | void updateWholeConStateTable();
42 | }
43 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/util/compression/deflatehuffman/huffman/Node.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Reference Huffman coding
3 | * Copyright (c) Project Nayuki
4 | *
5 | * https://www.nayuki.io/page/reference-huffman-coding
6 | * https://github.com/nayuki/Reference-Huffman-coding
7 | *
8 | * License
9 | * Copyright © 2018 Project Nayuki. (MIT License)
10 | *
11 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
12 | * associated documentation files (the "Software"), to deal in the Software without restriction,
13 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
15 | * subject to the following conditions:
16 | *
17 | * The above copyright notice and this permission notice shall be included in all copies or substantial
18 | * portions of the Software.
19 | *
20 | * The Software is provided "as is", without warranty of any kind, express or implied, including but not
21 | * limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
22 | * no event shall the authors or copyright holders be liable for any claim, damages or other liability,
23 | * whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
24 | * Software or the use or other dealings in the Software.
25 | */
26 | package org.prowl.kisset.util.compression.deflatehuffman.huffman;
27 |
28 |
29 | /**
30 | * A node in a code tree. This class has exactly two subclasses: InternalNode, Leaf.
31 | *
32 | * @see CodeTree
33 | */
34 | public abstract class Node {
35 |
36 | // This constructor is package-private to prevent accidental subclassing outside of this package.
37 | Node() {
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/AX25ParserWithDistributor.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 | /*
3 | * Copyright (C) 2011-2021 Andrew Pavlin, KA2DDO
4 | * This file is part of YAAC.
5 | *
6 | * YAAC is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Lesser General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * YAAC is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * and GNU Lesser General Public License along with YAAC. If not,
18 | * see .
19 | */
20 |
21 | /**
22 | * This interface extends the {@link AX25Parser} interface to indicate it has a means
23 | * to distribute protocol-specific packets to interested listeners. Because the
24 | * listeners are protocol-specific, their registration method signatures are not defined
25 | * here; this is just the hand-off for the {@link AX25Stack} to send the
26 | * decoded packets to the protocol-specific distributor, which then handles any
27 | * casting to subclasses and distribution to registered listeners.
28 | *
29 | * @author Andrew Pavlin, KA2DDO
30 | */
31 | public interface AX25ParserWithDistributor extends AX25Parser {
32 | /**
33 | * Send this message (and its associated frame) to the APRS consumers.
34 | *
35 | * @param frame AX25Frame containing the APRS message
36 | * @param parsedMsg AX25Message that was decoded from the frame
37 | */
38 | void processParsedAX25Packet(AX25Frame frame, AX25Message parsedMsg);
39 | }
40 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/ParsedAX25MessageListener.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 | /*
3 | * Copyright (C) 2011-2023 Andrew Pavlin, KA2DDO
4 | * This file is part of YAAC (Yet Another APRS Client).
5 | *
6 | * YAAC is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Lesser General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * YAAC is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * and GNU Lesser General Public License along with YAAC. If not,
18 | * see .
19 | */
20 |
21 | /**
22 | * This interface defines how a code segment waiting for a response message is
23 | * informed when the response is received. Such implementors should be registered
24 | * with the {@link AX25Stack} method {@link AX25Stack#addParsedAX25MessageListener(ParsedAX25MessageListener)}.
25 | *
26 | * @author Andrew Pavlin, KA2DDO
27 | */
28 | @FunctionalInterface
29 | public interface ParsedAX25MessageListener {
30 | /**
31 | * Delivers the next message received by YAAC that is some sort of parsed AX.25 higher-level message.
32 | *
33 | * @param pid AX.25 protocol ID
34 | * @param msg some subclass of AX25Message containing the message contents; the message should have
35 | * an AX25Frame connected to it
36 | * @see AX25Frame
37 | * @see AX25Message#ax25Frame
38 | * @see AX25Message#getAx25Frame()
39 | */
40 | void parsedAX25MessageReceived(byte pid, AX25Message msg);
41 | }
42 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/protocols/dxcluster/DXListener.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.protocols.dxcluster;
2 |
3 | import com.google.common.eventbus.Subscribe;
4 | import org.apache.commons.logging.Log;
5 | import org.apache.commons.logging.LogFactory;
6 | import org.prowl.ax25.AX25Frame;
7 | import org.prowl.kisset.eventbus.SingleThreadBus;
8 | import org.prowl.kisset.eventbus.events.DXSpotEvent;
9 | import org.prowl.kisset.eventbus.events.HeardNodeEvent;
10 | import org.prowl.kisset.objects.dxcluster.DXSpot;
11 |
12 | public enum DXListener {
13 |
14 | INSTANCE;
15 |
16 | private static final Log LOG = LogFactory.getLog("DXListener");
17 |
18 | DXListener() {
19 | // Load recent spots from disk in case of an app restart
20 | //KISSet.INSTANCE.getStorage().loadRecentDXClusterSpots();
21 |
22 | // Start listening for new DX spots
23 | SingleThreadBus.INSTANCE.register(this);
24 | }
25 |
26 | @Subscribe
27 | public void onHeardNode(HeardNodeEvent event) {
28 | try {
29 | if (event.getNode().getFrame().getPid() != AX25Frame.PID_NOLVL3) {
30 | return;
31 | }
32 |
33 | if (!event.getNode().getDestination().equals("DX")) {
34 | return;
35 | }
36 |
37 | byte[] body = event.getNode().getFrame().getBody();
38 | String data = new String(body);
39 | LOG.debug("DATA:" + data);
40 | if (data.startsWith("DX de")) {
41 | // It's a DX spot
42 | DXSpot dxSpot = new DXSpot(event.getNode());
43 | //KISSet.INSTANCE.getStorage().addRecentDXClusterSpot(dxSpot);
44 | SingleThreadBus.INSTANCE.post(new DXSpotEvent(dxSpot));
45 | }
46 |
47 |
48 | } catch (Throwable e) {
49 | LOG.error(e.getMessage(), e);
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/KISSetGUI/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module org.prowl.kissetgui {
2 | requires javafx.controls;
3 | requires javafx.fxml;
4 | requires org.controlsfx.controls;
5 | requires net.synedra.validatorfx;
6 | requires org.kordamp.ikonli.javafx;
7 | // If you're not using bellsoft-liberica-jdk, and get a module not found error for javafx.swing, then you will need
8 | // to add the dependency in the pom.xml file (which is in there, just commented out for you). I would recommend
9 | // using bellsoft-liberica-jdk, as it is the easiest way to get JavaFX working with Maven.
10 | requires javafx.swing;
11 |
12 |
13 | requires commons.logging;
14 | requires commons.configuration;
15 | requires java.prefs;
16 | requires org.reflections;
17 | requires com.fazecast.jSerialComm;
18 | requires commons.lang;
19 | requires atlantafx.base;
20 | requires java.sql;
21 | requires nsmenufx;
22 | requires com.jthemedetector;
23 | requires com.github.oshi;
24 | requires versioncompare;
25 | requires com.google.common;
26 | requires aprslib;
27 | requires javafx.web;
28 | requires org.eclipse.paho.client.mqttv3;
29 | requires com.google.errorprone.annotations;
30 | requires org.bouncycastle.provider;
31 | requires jdk.unsupported;
32 | requires com.googlecode.lanterna;
33 | requires org.prowl.kisset;
34 |
35 |
36 | exports org.prowl.kissetgui.userinterface.desktop.terminals;
37 | exports org.prowl.maps;
38 | exports org.prowl.kissetgui.userinterface.desktop.fx;
39 | opens org.prowl.kissetgui.userinterface.desktop.fx to javafx.fxml;
40 | exports org.prowl.kissetgui.userinterface.desktop;
41 | opens org.prowl.kissetgui.userinterface.desktop to javafx.fxml;
42 | exports org.prowl.kissetgui.userinterface.desktop.utils;
43 | opens org.prowl.kissetgui.userinterface.desktop.utils to javafx.fxml;
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/netrom/opcodebeans/DisconnectAcknowledge.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.netrom.opcodebeans;
2 |
3 | import org.prowl.ax25.AX25Callsign;
4 | import org.prowl.kisset.protocols.netrom.NetROMPacket;
5 |
6 | public class DisconnectAcknowledge {
7 |
8 | private NetROMPacket netROMPacket;
9 |
10 | public DisconnectAcknowledge(NetROMPacket packet) {
11 | this.netROMPacket = packet;
12 | }
13 |
14 | public DisconnectAcknowledge() {
15 | netROMPacket = new NetROMPacket();
16 | netROMPacket.setBody(new byte[0]);
17 | netROMPacket.setOpCode(NetROMPacket.OPCODE_DISCONNECT_ACK);
18 | }
19 |
20 | public int getYourCircuitIndex() {
21 | return netROMPacket.getCircuitIndex();
22 | }
23 |
24 | public int getYourCircuitID() {
25 | return netROMPacket.getCircuitId();
26 | }
27 |
28 | public int getOpcode() {
29 | return netROMPacket.getOpCode();
30 | }
31 |
32 | public void setYourCircuitIndex(int circuitIndex) {
33 | netROMPacket.setCircuitIndex(circuitIndex);
34 | }
35 |
36 | public void setYourCircuitID(int circuitID) {
37 | netROMPacket.setCircuitId(circuitID);
38 | }
39 |
40 | public AX25Callsign getSourceCallsign() {
41 | return new AX25Callsign(netROMPacket.getOriginCallsign());
42 | }
43 |
44 | public AX25Callsign getDestinationCallsign() {
45 | return new AX25Callsign(netROMPacket.getDestinationCallsign());
46 | }
47 |
48 | public void setSourceCallsign(AX25Callsign callsign) {
49 | netROMPacket.setOriginCallsign(callsign.toString());
50 | }
51 |
52 | public void setDestinationCallsign(AX25Callsign callsign) {
53 | netROMPacket.setDestinationCallsign(callsign.toString());
54 | }
55 |
56 | public NetROMPacket getNetROMPacket() {
57 | return netROMPacket;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/netrom/opcodebeans/DisconnectRequest.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.netrom.opcodebeans;
2 |
3 | import org.prowl.ax25.AX25Callsign;
4 | import org.prowl.kisset.protocols.netrom.NetROMPacket;
5 |
6 | public class DisconnectRequest {
7 |
8 | private NetROMPacket netROMPacket;
9 |
10 | public DisconnectRequest(NetROMPacket packet) {
11 | this.netROMPacket = packet;
12 | }
13 |
14 | public DisconnectRequest() {
15 | netROMPacket = new NetROMPacket();
16 | netROMPacket.setBody(new byte[0]);
17 | netROMPacket.setOpCode(NetROMPacket.OPCODE_DISCONNECT_REQUEST);
18 | }
19 |
20 | public int getYourCircuitIndex() {
21 | return netROMPacket.getCircuitIndex();
22 | }
23 |
24 | public int getYourCircuitID() {
25 | return netROMPacket.getCircuitId();
26 | }
27 |
28 | public int getOpcode() {
29 | return netROMPacket.getOpCode();
30 | }
31 |
32 |
33 | public void setYourCircuitIndex(int circuitIndex) {
34 | netROMPacket.setCircuitIndex(circuitIndex);
35 | }
36 |
37 | public void setYourCircuitID(int circuitID) {
38 | netROMPacket.setCircuitId(circuitID);
39 | }
40 |
41 |
42 | public AX25Callsign getSourceCallsign() {
43 | return new AX25Callsign(netROMPacket.getOriginCallsign());
44 | }
45 |
46 | public AX25Callsign getDestinationCallsign() {
47 | return new AX25Callsign(netROMPacket.getDestinationCallsign());
48 | }
49 |
50 |
51 | public void setSourceCallsign(AX25Callsign callsign) {
52 | netROMPacket.setOriginCallsign(callsign.toString());
53 | }
54 |
55 | public void setDestinationCallsign(AX25Callsign callsign) {
56 | netROMPacket.setDestinationCallsign(callsign.toString());
57 | }
58 |
59 | public NetROMPacket getNetROMPacket() {
60 | return netROMPacket;
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/KISSetGUI/src/main/java/org/prowl/kissetgui/userinterface/desktop/terminals/DebugTerminal.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kissetgui.userinterface.desktop.terminals;
2 |
3 | import org.prowl.kisset.util.ANSI;
4 |
5 | public class DebugTerminal extends ANSITerminal {
6 |
7 | public DebugTerminal() {
8 | super();
9 | }
10 |
11 | /**
12 | * Appends the text to the terminal buffer for later drawing.
13 | */
14 | public synchronized void append(int b) {
15 | //b = b & 0xFF;
16 | // LOG.debug("Append:" + Integer.toString(b,16));
17 | // Store the byte in the buffer.
18 |
19 |
20 | if (b == 10) {
21 | lastByteWasCR = false;
22 | // Write the hex and update the line
23 | writeHex(b);
24 | updateCurrentLine();
25 | } else if (b == 13) {
26 | lastByteWasCR = true;
27 | // Write the hex and update the line
28 | writeHex(b);
29 | updateCurrentLine();
30 | // Newline time
31 | currentLine = new StringBuilder(); // don't use .delete as the backing byte[] would never get trimmed.
32 | makeNewLine();
33 | clearSelection();
34 | } else {
35 | // Just write the byte and hexify it if it's not a printable char
36 | lastByteWasCR = false;
37 | if (b < 32 || b > 128) {
38 | writeHex(b);
39 | } else {
40 | currentLine.append((char) b);
41 | }
42 | updateCurrentLine();
43 |
44 | }
45 |
46 | queueRedraw();
47 |
48 | }
49 | public void writeHex(int b) {
50 | currentLine.append(ANSI.YELLOW);
51 | currentLine.append('<');
52 | currentLine.append(String.format("%02X", b));
53 | currentLine.append('>');
54 | currentLine.append(ANSI.NORMAL);
55 | }
56 |
57 | public String toString() {
58 | return "DebugTerminal";
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/util/compression/deflatehuffman/huffman/Leaf.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Reference Huffman coding
3 | * Copyright (c) Project Nayuki
4 | *
5 | * https://www.nayuki.io/page/reference-huffman-coding
6 | * https://github.com/nayuki/Reference-Huffman-coding
7 | *
8 | * License
9 | * Copyright © 2018 Project Nayuki. (MIT License)
10 | *
11 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
12 | * associated documentation files (the "Software"), to deal in the Software without restriction,
13 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
15 | * subject to the following conditions:
16 | *
17 | * The above copyright notice and this permission notice shall be included in all copies or substantial
18 | * portions of the Software.
19 | *
20 | * The Software is provided "as is", without warranty of any kind, express or implied, including but not
21 | * limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
22 | * no event shall the authors or copyright holders be liable for any claim, damages or other liability,
23 | * whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
24 | * Software or the use or other dealings in the Software.
25 | */
26 | package org.prowl.kisset.util.compression.deflatehuffman.huffman;
27 |
28 |
29 | /**
30 | * A leaf node in a code tree. It has a symbol value. Immutable.
31 | *
32 | * @see CodeTree
33 | */
34 | public final class Leaf extends Node {
35 |
36 | public final int symbol; // Always non-negative
37 |
38 |
39 | public Leaf(int sym) {
40 | if (sym < 0)
41 | throw new IllegalArgumentException("Symbol value must be non-negative");
42 | symbol = sym;
43 | }
44 |
45 | }
46 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/ProtocolFamily.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 |
3 | /*
4 | * Copyright (C) 2011-2022 Andrew Pavlin, KA2DDO
5 | * This file is part of YAAC.
6 | *
7 | * YAAC is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU Lesser General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * YAAC is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * and GNU Lesser General Public License along with YAAC. If not,
19 | * see .
20 | */
21 |
22 | /**
23 | * This enumeration categorizes AX.25 messages by what protocol they are carrying,
24 | * to make it easier for YAAC I/O ports to determine if a particular AX25Frame should
25 | * be sent out a particular port.
26 | *
27 | * @author Andrew Pavlin, KA2DDO
28 | */
29 | public enum ProtocolFamily {
30 | /**
31 | * APRS packets over AX.25 UI frames, PID=0xF0 (NOLVL3).
32 | */
33 | APRS(AX25Frame.PID_NOLVL3),
34 | /**
35 | * OpenTRAC packets over AX.25 UI frames, PID=0x77 (OPENTRAC).
36 | */
37 | OPENTRAC(AX25Frame.PID_OPENTRAC),
38 | /**
39 | * Other level 2 AX.25 frames, including connected-mode frames, PID=0xF0 (NOLVL3).
40 | */
41 | RAW_AX25(AX25Frame.PID_NOLVL3);
42 |
43 | private final byte pid;
44 |
45 | ProtocolFamily(byte pid) {
46 | this.pid = pid;
47 | }
48 |
49 | /**
50 | * Get the AX.25 protocol ID value associated with this ProtocolFamily.
51 | *
52 | * @return PID value
53 | */
54 | public byte getPid() {
55 | return pid;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/protocols/aprs/APRSListener.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.protocols.aprs;
2 |
3 | import com.google.common.eventbus.Subscribe;
4 | import org.apache.commons.logging.Log;
5 | import org.apache.commons.logging.LogFactory;
6 | import org.prowl.aprslib.parser.APRSPacket;
7 | import org.prowl.aprslib.parser.Parser;
8 | import org.prowl.ax25.AX25Frame;
9 | import org.prowl.kisset.KISSet;
10 | import org.prowl.kisset.config.Conf;
11 | import org.prowl.kisset.config.Config;
12 | import org.prowl.kisset.eventbus.SingleThreadBus;
13 | import org.prowl.kisset.eventbus.events.APRSPacketEvent;
14 | import org.prowl.kisset.eventbus.events.HeardNodeEvent;
15 |
16 | public enum APRSListener {
17 |
18 | INSTANCE;
19 |
20 | private static final Log LOG = LogFactory.getLog("APRSListener");
21 |
22 | private Config config;
23 |
24 | APRSListener() {
25 | config = KISSet.INSTANCE.getConfig();
26 | // Load recent APRS packets from disk in case of an app restart
27 | //KISSet.INSTANCE.getStorage().loadRecentAPRSPackets();
28 |
29 | // Start listening for new DX spots
30 | SingleThreadBus.INSTANCE.register(this);
31 | }
32 |
33 |
34 | @Subscribe
35 | public void onHeardNode(HeardNodeEvent event) {
36 | if (config.getConfig(Conf.aprsDecoingOverKISSEnabled, Conf.aprsDecoingOverKISSEnabled.boolDefault())) {
37 | AX25Frame frame = event.getNode().getFrame();
38 | boolean isAprs = false;
39 | try {
40 | APRSPacket packet = Parser.parseAX25(frame.getRawPacket());
41 | // APRS packet reserialize the body
42 | //KISSet.INSTANCE.getStorage().addRecentAPRSPacket(aprsPacket);
43 | SingleThreadBus.INSTANCE.post(new APRSPacketEvent(packet));
44 | // }
45 | } catch (Throwable e) {
46 | // Ignore - probably not aprs. or unable to parse MICe
47 | }
48 | }
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/pms/parser/commands/Command.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.pms.parser.commands;
2 |
3 | import org.prowl.kisset.services.remote.pms.PMSClientHandler;
4 | import org.prowl.kisset.services.remote.pms.parser.CommandParser;
5 | import org.prowl.kisset.services.remote.pms.parser.Mode;
6 |
7 | import java.io.IOException;
8 |
9 | public abstract class Command {
10 |
11 | public static final String CR = CommandParser.CR;
12 |
13 | protected PMSClientHandler client;
14 | protected CommandParser commandParser;
15 |
16 | public Command() {
17 |
18 | }
19 |
20 | public void setClient(PMSClientHandler client, CommandParser commandParser) {
21 | this.client = client;
22 | this.commandParser = commandParser;
23 | }
24 |
25 | /**
26 | * Execute the command
27 | *
28 | * @param data
29 | * @return true if the command was consumed by this class, false if otherwise.
30 | * @throws IOException
31 | */
32 | public abstract boolean doCommand(String[] data) throws IOException;
33 |
34 | /**
35 | * This is the command and it's aliases.
36 | *
37 | * @return The command and it's aliases.
38 | */
39 | public abstract String[] getCommandNames();
40 |
41 | /**
42 | * Convenience method to write to the client (no detokenisation of strings)
43 | *
44 | * @param s
45 | * @throws IOException
46 | */
47 | public void write(String s) throws IOException {
48 | client.send(s);
49 | }
50 |
51 | public void flush() throws IOException {
52 | client.flush();
53 | }
54 |
55 | public Mode getMode() {
56 | return commandParser.getMode();
57 | }
58 |
59 | public void setMode(Mode mode) {
60 | commandParser.setMode(mode);
61 | }
62 |
63 | public void popModeFromStack() {
64 | commandParser.popModeFromStack();
65 | }
66 |
67 | public void pushModeToStack(Mode mode) {
68 | commandParser.pushModeToStack(mode);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/util/compression/deflatehuffman/huffman/InternalNode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Reference Huffman coding
3 | * Copyright (c) Project Nayuki
4 | *
5 | * https://www.nayuki.io/page/reference-huffman-coding
6 | * https://github.com/nayuki/Reference-Huffman-coding
7 | *
8 | * License
9 | * Copyright © 2018 Project Nayuki. (MIT License)
10 | *
11 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
12 | * associated documentation files (the "Software"), to deal in the Software without restriction,
13 | * including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 | * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
15 | * subject to the following conditions:
16 | *
17 | * The above copyright notice and this permission notice shall be included in all copies or substantial
18 | * portions of the Software.
19 | *
20 | * The Software is provided "as is", without warranty of any kind, express or implied, including but not
21 | * limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In
22 | * no event shall the authors or copyright holders be liable for any claim, damages or other liability,
23 | * whether in an action of contract, tort or otherwise, arising from, out of or in connection with the
24 | * Software or the use or other dealings in the Software.
25 | */
26 | package org.prowl.kisset.util.compression.deflatehuffman.huffman;
27 |
28 | import java.util.Objects;
29 |
30 |
31 | /**
32 | * An internal node in a code tree. It has two nodes as children. Immutable.
33 | *
34 | * @see CodeTree
35 | */
36 | public final class InternalNode extends Node {
37 |
38 | public final Node leftChild; // Not null
39 |
40 | public final Node rightChild; // Not null
41 |
42 |
43 | public InternalNode(Node left, Node right) {
44 | leftChild = Objects.requireNonNull(left);
45 | rightChild = Objects.requireNonNull(right);
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/Service.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services;
2 |
3 | import org.prowl.ax25.AX25Frame;
4 | import org.prowl.ax25.AX25InputStream;
5 | import org.prowl.ax25.AX25OutputStream;
6 | import org.prowl.kisset.io.Interface;
7 | import org.prowl.kisset.objects.user.User;
8 |
9 | import java.io.InputStream;
10 | import java.io.OutputStream;
11 |
12 | public abstract class Service {
13 |
14 | private final String name;
15 |
16 | public Service(String name) {
17 | this.name = name;
18 | if (name == null) {
19 | throw new AssertionError("Service name cannot be null");
20 | }
21 | }
22 |
23 | public abstract void start();
24 |
25 | public abstract void stop();
26 |
27 | public String getName() {
28 | return name;
29 | }
30 |
31 | /**
32 | * The callsign we are using for this service (used in the AX25 package so it knows what to respond to)
33 | *
34 | * @return A calsign - might have an SSID, might not - who knows what the user decides!
35 | */
36 | public abstract String getCallsign();
37 |
38 | /**
39 | * Called when a connection is accepted.
40 | *
41 | * @param anInterface The interface the connection was accepted on - note not to be used to control connection states. Connectionless only as the
42 | * interface may only reflect the net/rom connection and not actually have a physical connection to the remote node as it is
43 | * net/rom tunneled.
44 | * @param user The user that is connecting
45 | * @param in The input stream for the connection
46 | * @param out The output stream for the connection - close this to end the connection.
47 | */
48 | public abstract void acceptedConnection(Interface anInterface, User user, InputStream in, OutputStream out);
49 |
50 |
51 | /**
52 | * The frame type used for I frames
53 | * @return
54 | */
55 | public byte getServicePid(User user) {
56 | return AX25Frame.PID_NOLVL3;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/Command.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.prowl.kisset.services.host.TNCHost;
4 | import org.prowl.kisset.services.host.parser.CommandParser;
5 | import org.prowl.kisset.services.host.parser.Mode;
6 |
7 | import java.io.IOException;
8 |
9 | public abstract class Command {
10 |
11 | public static final String CR = CommandParser.CR;
12 |
13 | protected TNCHost tncHost;
14 | protected CommandParser commandParser;
15 |
16 | public Command() {
17 |
18 | }
19 |
20 | public void setClient(TNCHost tncHost, CommandParser commandParser) {
21 | this.tncHost = tncHost;
22 | this.commandParser = commandParser;
23 | }
24 |
25 | /**
26 | * Execute the command
27 | *
28 | * @param data
29 | * @return true if the command was consumed by this class, false if otherwise.
30 | * @throws IOException
31 | */
32 | public abstract boolean doCommand(String[] data) throws IOException;
33 |
34 | /**
35 | * This is the command and it's aliases.
36 | *
37 | * @return The command and it's aliases.
38 | */
39 | public abstract String[] getCommandNames();
40 |
41 | /**
42 | * Convenience method to write to the client (no detokenisation of strings)
43 | *
44 | * @param s
45 | * @throws IOException
46 | */
47 | public void writeToTerminal(String s) throws IOException {
48 | tncHost.send(s);
49 | }
50 |
51 | public Mode getMode() {
52 | return commandParser.getMode();
53 | }
54 |
55 |
56 | public void setMode(Mode mode) {
57 | commandParser.setMode(mode);
58 | }
59 |
60 | public void setMode(Mode mode, boolean sendPrompt) throws IOException {
61 | commandParser.setMode(mode, sendPrompt);
62 | }
63 |
64 |
65 | public void popModeFromStack() {
66 | commandParser.popModeFromStack();
67 | }
68 |
69 | public void pushModeToStack(Mode mode) {
70 | commandParser.pushModeToStack(mode);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/FrameState.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 | /*
3 | * Copyright (C) 2011-2016 Andrew Pavlin, KA2DDO
4 | * This file is part of YAAC (Yet Another APRS Client).
5 | *
6 | * YAAC is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU Lesser General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * YAAC is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * and GNU Lesser General Public License along with YAAC. If not,
18 | * see .
19 | */
20 |
21 | /**
22 | * The class describes the digipeat status of an associated AX25Frame.
23 | */
24 | public class FrameState {
25 | /**
26 | * Indicate whether the associated frame has already been digipeated.
27 | */
28 | public boolean alreadyDigipeated;
29 | /**
30 | * Record when the frame was digipeated.
31 | */
32 | public long when;
33 |
34 | /**
35 | * Create a FrameState for the current time, already digipeated.
36 | */
37 | public FrameState() {
38 | this.when = System.currentTimeMillis();
39 | this.alreadyDigipeated = true;
40 | }
41 |
42 | /**
43 | * Create a FrameState for the specified time, not yet digipeated.
44 | *
45 | * @param now time of frame in milliseconds since Jan 1 1970 UTC
46 | */
47 | public FrameState(long now) {
48 | this.when = now;
49 | }
50 |
51 | /**
52 | * Report a description of this FrameState object.
53 | *
54 | * @return descriptive String
55 | */
56 | @Override
57 | public String toString() {
58 | return (alreadyDigipeated ? "already-digi'd" : "") + (when > 0 ? "@" + when : "");
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/Mycall.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.prowl.kisset.KISSet;
4 | import org.prowl.kisset.annotations.TNCCommand;
5 | import org.prowl.kisset.config.Conf;
6 | import org.prowl.kisset.eventbus.SingleThreadBus;
7 | import org.prowl.kisset.eventbus.events.ConfigurationChangeCompleteEvent;
8 | import org.prowl.kisset.eventbus.events.ConfigurationChangedEvent;
9 | import org.prowl.kisset.services.host.parser.Mode;
10 |
11 | import java.io.IOException;
12 |
13 | /**
14 | * Display or set the station callsign
15 | */
16 | @TNCCommand
17 | public class Mycall extends Command {
18 |
19 | @Override
20 | public boolean doCommand(String[] data) throws IOException {
21 |
22 | if (!getMode().equals(Mode.CMD)) {
23 | return false;
24 | }
25 |
26 | if (data.length == 1) {
27 | writeToTerminal("*** MYcall is " + KISSet.INSTANCE.getMyCall() + CR);
28 | } else {
29 |
30 | // Cant set mycall to mypcall check if PMS service is enabled
31 | String mypcall = KISSet.INSTANCE.getMyCallNoSSID() + KISSet.INSTANCE.getConfig().getConfig(Conf.pmsSSID, Conf.pmsSSID.stringDefault());
32 | if (mypcall.equalsIgnoreCase(data[1])) {
33 | writeToTerminal("*** MYcall cannot be set to PMS mycall " + mypcall + CR);
34 | return true;
35 | }
36 |
37 | String call = data[1].toUpperCase();
38 | KISSet.INSTANCE.setMyCall(call);
39 | writeToTerminal("*** MYcall set to " + KISSet.INSTANCE.getMyCall() + CR);
40 | KISSet.INSTANCE.getConfig().setProperty(Conf.callsign, call).saveConfig();
41 |
42 | SingleThreadBus.INSTANCE.post(new ConfigurationChangedEvent());
43 | SingleThreadBus.INSTANCE.post(new ConfigurationChangeCompleteEvent(false));
44 |
45 | }
46 |
47 | return true;
48 | }
49 |
50 |
51 | @Override
52 | public String[] getCommandNames() {
53 | return new String[]{"my", "mycall"};
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/util/LoopingCircularBuffer.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.util;
2 |
3 | import org.apache.commons.logging.Log;
4 | import org.apache.commons.logging.LogFactory;
5 |
6 | /**
7 | * This buffer retains the last X bytes of a stream, and overwrites (and pushes the tail forwards) when the end of the
8 | * stream is reached.
9 | *
10 | * It is a quick way to provide what happened in the last X bytes of a stream so you can replay that to popupate a terminal
11 | * when you swap terminals (crude, but useful)
12 | */
13 | public final class LoopingCircularBuffer {
14 |
15 | private static final Log LOG = LogFactory.getLog("LoopingCircularBuffer");
16 |
17 | private byte[] buffer;
18 | private int head;
19 | private int tail;
20 | private int filled;
21 | private final int size;
22 |
23 | public LoopingCircularBuffer(int size) {
24 | this.size = size;
25 | clear();
26 | }
27 |
28 | public void clear() {
29 | buffer = new byte[size];
30 | head = 1;
31 | tail = 0;
32 | filled = 0;
33 | }
34 |
35 | /**
36 | * Get the current contents of our byte stream
37 | *
38 | * @return
39 | */
40 | public byte[] getBytes() {
41 | byte[] b = new byte[filled];
42 | int count = 0;
43 | while (count < filled) {
44 | b[count] = buffer[(tail + count) % buffer.length];
45 | count++;
46 | }
47 | return b;
48 | }
49 |
50 |
51 | /**
52 | * Put a byte into the stream
53 | *
54 | * @param b
55 | */
56 | public void put(byte b) {
57 | buffer[head % buffer.length] = b;
58 | // avoid int wraparound.
59 |
60 |
61 | // Grow until we are the same size.
62 | if (head % buffer.length == tail % buffer.length) {
63 | tail = (tail + 1) % buffer.length;
64 | }
65 | if (filled < buffer.length) {
66 | filled++;
67 | }
68 | head++;
69 | if (head % buffer.length == 0) {
70 | head = 0;
71 | }
72 | }
73 |
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/Routes.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.prowl.kisset.annotations.TNCCommand;
4 | import org.prowl.kisset.objects.routing.NetROMRoute;
5 | import org.prowl.kisset.protocols.netrom.NetROMRoutingTable;
6 | import org.prowl.kisset.services.host.parser.Mode;
7 | import org.prowl.kisset.util.ANSI;
8 |
9 | import java.io.IOException;
10 | import java.util.List;
11 |
12 | /**
13 | * List the known routes heard from various other nodes.
14 | */
15 | @TNCCommand
16 | public class Routes extends Command {
17 |
18 | private static final long HOUR = 1000 * 60 * 60;
19 | private static final long TWO_HOUR = 1000 * 60 * 60 * 2;
20 |
21 |
22 | @Override
23 | public boolean doCommand(String[] data) throws IOException {
24 |
25 | if (!getMode().equals(Mode.CMD)) {
26 | return false;
27 | }
28 | writeToTerminal(CR);
29 |
30 | // Get the routes and interate
31 | List nodes = NetROMRoutingTable.INSTANCE.getNodes();
32 | if (nodes.size() > 0) {
33 | writeToTerminal(ANSI.BOLD + ANSI.UNDERLINE + "List of Net/ROM routes seen from local nodes:" + ANSI.NORMAL + CR);
34 |
35 | long now = System.currentTimeMillis();
36 | for (NetROMRoute node : nodes) {
37 |
38 | String color;
39 | if (now - node.getLastHeard() < HOUR) {
40 | color = ANSI.GREEN;
41 | } else if (now - node.getLastHeard() < TWO_HOUR) {
42 | color = ANSI.YELLOW;
43 | } else {
44 | color = ANSI.RED;
45 | }
46 | writeToTerminal(color);
47 | writeToTerminal(node + ANSI.NORMAL + CR);
48 | }
49 | } else {
50 | writeToTerminal("*** No routes seen yet" + CR);
51 | }
52 |
53 | return true;
54 | }
55 |
56 |
57 | @Override
58 | public String[] getCommandNames() {
59 | return new String[]{"routes", "route", "ro", "rou"};
60 | }
61 |
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/netrom/user/parser/commands/Interfaces.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.netrom.user.parser.commands;
2 |
3 | import org.apache.commons.lang.StringUtils;
4 | import org.prowl.kisset.KISSet;
5 | import org.prowl.kisset.annotations.NodeCommand;
6 | import org.prowl.kisset.io.Interface;
7 | import org.prowl.kisset.io.InterfaceStatus;
8 | import org.prowl.kisset.services.remote.netrom.user.parser.Mode;
9 | import org.prowl.kisset.util.ANSI;
10 |
11 | import java.io.IOException;
12 | import java.text.NumberFormat;
13 |
14 | @NodeCommand
15 | public class Interfaces extends Command {
16 |
17 | @Override
18 | public boolean doCommand(String[] data) throws IOException {
19 |
20 | // We're only interesteed in comamnd moed.
21 | if (!getMode().equals(Mode.CMD)) {
22 | return false;
23 | }
24 |
25 | write(CR);
26 |
27 | NumberFormat nf = NumberFormat.getInstance();
28 | nf.setMaximumFractionDigits(4);
29 | nf.setMinimumFractionDigits(3);
30 |
31 | NumberFormat nfb = NumberFormat.getInstance();
32 | nfb.setMaximumFractionDigits(1);
33 | nfb.setMinimumFractionDigits(1);
34 |
35 | // No parameter? Just list the interfaces then
36 | if (data.length == 1) {
37 | write(CR + ANSI.BOLD + ANSI.UNDERLINE + "No. Interface " + ANSI.NORMAL + CR);
38 | int i = 0;
39 | for (Interface anInterface : KISSet.INSTANCE.getInterfaceHandler().getInterfaces()) {
40 | InterfaceStatus status = anInterface.getInterfaceStatus();
41 |
42 |
43 | write(StringUtils.rightPad(Integer.toString(i) + ": ", 4) + StringUtils.rightPad(anInterface.toString(), 25) + StringUtils.rightPad(status.getState().name(), 30) + CR);
44 | i++;
45 | }
46 | write(CR);
47 | return true;
48 | }
49 | return true;
50 | }
51 |
52 |
53 | @Override
54 | public String[] getCommandNames() {
55 | return new String[]{"int", "ports", "i", "interfaces"};
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/netrom/user/parser/commands/Command.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.netrom.user.parser.commands;
2 |
3 | import org.prowl.kisset.services.remote.netrom.user.NetROMUserClientHandler;
4 | import org.prowl.kisset.services.remote.netrom.user.parser.CommandParser;
5 | import org.prowl.kisset.services.remote.netrom.user.parser.Mode;
6 |
7 | import java.io.IOException;
8 |
9 | public abstract class Command {
10 |
11 | public static final String CR = CommandParser.CR;
12 |
13 | protected NetROMUserClientHandler client;
14 | protected CommandParser commandParser;
15 |
16 | public Command() {
17 |
18 | }
19 |
20 | public void setClient(NetROMUserClientHandler client, CommandParser commandParser) {
21 | this.client = client;
22 | this.commandParser = commandParser;
23 | }
24 |
25 | /**
26 | * Execute the command
27 | *
28 | * @param data
29 | * @return true if the command was consumed by this class, false if otherwise.
30 | * @throws IOException
31 | */
32 | public abstract boolean doCommand(String[] data) throws IOException;
33 |
34 | /**
35 | * This is the command and it's aliases.
36 | *
37 | * @return The command and it's aliases.
38 | */
39 | public abstract String[] getCommandNames();
40 |
41 | /**
42 | * Convenience method to write to the client (no detokenisation of strings)
43 | *
44 | * @param s
45 | * @throws IOException
46 | */
47 | public void write(String s) throws IOException {
48 | client.send(s);
49 | client.flush();
50 | }
51 |
52 | public void flush() throws IOException {
53 | client.flush();
54 | }
55 |
56 | public Mode getMode() {
57 | return commandParser.getMode();
58 | }
59 |
60 | public void setMode(Mode mode) {
61 | commandParser.setMode(mode);
62 | }
63 |
64 | public void popModeFromStack() {
65 | commandParser.popModeFromStack();
66 | }
67 |
68 | public void pushModeToStack(Mode mode) {
69 | commandParser.pushModeToStack(mode);
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/AX25Parser.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 |
3 | /*
4 | * Copyright (C) 2011-2023 Andrew Pavlin, KA2DDO
5 | * This file is part of YAAC (Yet Another APRS Client).
6 | *
7 | * YAAC is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU Lesser General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * YAAC is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * and GNU Lesser General Public License along with YAAC. If not,
19 | * see .
20 | */
21 |
22 | /**
23 | * Interface declaring an API for parsing an AX25Frame into a particular protocol (PID)'s
24 | * decoded message.
25 | *
26 | * @author Andrew Pavlin, KA2DDO
27 | */
28 | @FunctionalInterface
29 | public interface AX25Parser {
30 | /**
31 | * Parse a message to the appropriate object class.
32 | *
33 | * @param body byte array containing the message to be parsed
34 | * @param src AX25Callsign of the sending station
35 | * @param dest AX25Callsign of the destination (probably an APRS alias)
36 | * @param digipeaters array of AX25Callsigns for RF digipeaters, or null if none
37 | * @param rcvTimestamp the time in Java/Unix milliseconds since midnight Jan 1, 1970 UTC when this
38 | * message was actually received (as opposed to any timestamp that might be
39 | * embedded in the message body)
40 | * @param connector Connector over which the message was received (null if from a file)
41 | * @return the decoded Message (if not decipherable, a DefaultMessage is returned)
42 | */
43 | AX25Message parse(byte[] body, AX25Callsign src, AX25Callsign dest, AX25Callsign[] digipeaters, long rcvTimestamp, Connector connector);
44 | }
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/objects/dxcluster/DXSpot.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.objects.dxcluster;
2 |
3 | import org.apache.commons.logging.Log;
4 | import org.apache.commons.logging.LogFactory;
5 | import org.prowl.kisset.eventbus.SingleThreadBus;
6 | import org.prowl.kisset.eventbus.events.DXSpotEvent;
7 | import org.prowl.kisset.protocols.core.Node;
8 |
9 | import java.text.ParseException;
10 | import java.text.SimpleDateFormat;
11 |
12 | /**
13 | * A DX spot
14 | *
15 | * A DX spot looks like:
16 | * DX de G4ABC: 14074.0 EA1AHA FT8 0 dB 1236 Hz
17 | */
18 | public class DXSpot {
19 | private static final Log LOG = LogFactory.getLog("DXSpot");
20 |
21 | String dxLine;
22 | String sourceCallsign;
23 |
24 | String dxFrom; // The reporting callsign
25 | String dxSpotted; // The spooted callsign
26 | String comment; // Any comments;
27 | double frequency; // The frequency in MHz
28 | String mode; // The mode(if it can be parsed from the comemnt)
29 | long spotTime; // The time the spot was made
30 |
31 | public DXSpot(Node node) {
32 |
33 | SimpleDateFormat sdf = new SimpleDateFormat("HHmm");
34 |
35 | sourceCallsign = node.getCallsign();
36 |
37 | dxLine = new String(node.getFrame().getBody());
38 |
39 | dxFrom = dxLine.substring(dxLine.indexOf("DX de ") + 6, dxLine.indexOf(":"));
40 | frequency = Double.parseDouble(dxLine.substring(dxLine.indexOf(":") + 1, 24).trim());
41 | dxSpotted = dxLine.substring(25, dxLine.indexOf(" ", 26)).trim();
42 | comment = dxLine.substring(dxLine.indexOf(" ", 26) + 1).trim();
43 | comment = comment.substring(0, comment.length() - 5).trim();
44 | try {
45 | spotTime = sdf.parse(dxLine.substring(dxLine.length() - 5, dxLine.length() - 1)).getTime();
46 | } catch (ParseException e) {
47 | LOG.error(e.getMessage(), e);
48 | spotTime = node.getLastHeard();
49 | }
50 |
51 | DXSpotEvent event = new DXSpotEvent(this);
52 | SingleThreadBus.INSTANCE.post(event);
53 | }
54 |
55 | public String getLine() {
56 | return dxLine;
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/pms/parser/commands/Interfaces.java:
--------------------------------------------------------------------------------
1 | //package org.prowl.kisset.comms.remote.pms.parser.commands;
2 | //
3 | //import org.apache.commons.lang.StringUtils;
4 | //import org.prowl.kisset.KISSet;
5 | //import org.prowl.kisset.annotations.PMSCommand;
6 | //import org.prowl.kisset.comms.remote.pms.parser.Mode;
7 | //import org.prowl.kisset.io.Interface;
8 | //import org.prowl.kisset.util.ANSI;
9 | //
10 | //import java.io.IOException;
11 | //import java.text.NumberFormat;
12 | //
13 | //@PMSCommand
14 | //public class Interfaces extends Command {
15 | //
16 | // @Override
17 | // public boolean doCommand(String[] data) throws IOException {
18 | //
19 | // // We're only interesteed in comamnd moed.
20 | // if (!getMode().equals(Mode.CMD)) {
21 | // return false;
22 | // }
23 | //
24 | // write(CR);
25 | //
26 | // NumberFormat nf = NumberFormat.getInstance();
27 | // nf.setMaximumFractionDigits(4);
28 | // nf.setMinimumFractionDigits(3);
29 | //
30 | // NumberFormat nfb = NumberFormat.getInstance();
31 | // nfb.setMaximumFractionDigits(1);
32 | // nfb.setMinimumFractionDigits(1);
33 | //
34 | // // No parameter? Just list the interfaces then
35 | // if (data.length == 1) {
36 | // write(CR + ANSI.BOLD + ANSI.UNDERLINE + "No. Interface " + ANSI.NORMAL + CR);
37 | // int i = 0;
38 | // for (Interface anInterface : KISSet.INSTANCE.getInterfaceHandler().getInterfaces()) {
39 | // String status = anInterface.getInterfaceStatus()
40 | // if (status == null) {
41 | // status = "OK";
42 | // }
43 | // write(StringUtils.rightPad(Integer.toString(i) + ": ", 4) + StringUtils.rightPad(anInterface.toString(), 25) + StringUtils.rightPad(status, 30) + CR);
44 | // i++;
45 | // }
46 | // write(CR);
47 | // return true;
48 | // }
49 | // return true;
50 | // }
51 | //
52 | //
53 | // @Override
54 | // public String[] getCommandNames() {
55 | // return new String[]{"int", "ports", "i", "interfaces"};
56 | // }
57 | //}
58 |
--------------------------------------------------------------------------------
/KISSetGUI/src/main/java/org/prowl/maps/MapPoint.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2016, Gluon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 | package org.prowl.maps;
29 |
30 | import javafx.beans.NamedArg;
31 |
32 | /**
33 | *
34 | */
35 | public class MapPoint {
36 |
37 | private double latitude, longitude;
38 |
39 | public MapPoint(@NamedArg("latitude") double lat, @NamedArg("longitude") double lon) {
40 | this.latitude = lat;
41 | this.longitude = lon;
42 | }
43 |
44 | public double getLatitude() {
45 | return this.latitude;
46 | }
47 |
48 | public double getLongitude() {
49 | return this.longitude;
50 | }
51 |
52 | public void update(double lat, double lon) {
53 | this.latitude = lat;
54 | this.longitude = lon;
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/KISSetGUI/src/main/java/org/prowl/maps/tile/TileRetriever.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2018, 2020, Gluon
3 | *
4 | * This program is free software: you can redistribute it and/or modify
5 | * it under the terms of the GNU General Public License as published by
6 | * the Free Software Foundation, either version 3 of the License, or
7 | * (at your option) any later version.
8 | *
9 | * This program is distributed in the hope that it will be useful,
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | * GNU General Public License for more details.
13 | *
14 | * You should have received a copy of the GNU General Public License
15 | * along with this program. If not, see .
16 | *
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
18 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 | * DISCLAIMED. IN NO EVENT SHALL GLUON BE LIABLE FOR ANY
21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 | */
28 | package org.prowl.maps.tile;
29 |
30 | import javafx.scene.image.Image;
31 |
32 | import java.util.concurrent.CompletableFuture;
33 |
34 | public interface TileRetriever {
35 |
36 | /**
37 | * Loads a tile at the specified zoom level and coordinates and returns it
38 | * as an {@link Image}.
39 | *
40 | * @param zoom the desired zoom level for the tile to load
41 | * @param i the horizontal position of the tile to load
42 | * @param j the vertical position of the tile to load
43 | * @return a completableFuture with the image representing the tile
44 | */
45 | CompletableFuture loadTile(int zoom, long i, long j);
46 |
47 | }
48 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/module-info.java:
--------------------------------------------------------------------------------
1 | module org.prowl.kisset {
2 |
3 | requires java.logging;
4 | requires commons.logging;
5 | requires commons.configuration;
6 | requires java.prefs;
7 | requires org.reflections;
8 | requires com.fazecast.jSerialComm;
9 | requires commons.lang;
10 | requires com.github.oshi;
11 | requires com.google.common;
12 | requires aprslib;
13 | requires org.eclipse.paho.client.mqttv3;
14 | requires com.google.errorprone.annotations;
15 | requires org.bouncycastle.provider;
16 | requires jdk.unsupported;
17 | requires com.googlecode.lanterna;
18 | requires java.xml;
19 |
20 |
21 | exports org.prowl.ax25;
22 | exports org.prowl.kisset.eventbus.events;
23 | exports org.prowl.kisset.protocols.mqtt;
24 | exports org.prowl.kisset;
25 | exports org.prowl.kisset.objects;
26 | exports org.prowl.kisset.protocols.aprs;
27 | exports org.prowl.kisset.statistics.types;
28 | exports org.prowl.kisset.protocols;
29 | exports org.prowl.kisset.protocols.dxcluster;
30 | exports org.prowl.kisset.protocols.fbb;
31 | exports org.prowl.kisset.services.host;
32 | exports org.prowl.kisset.services.remote.pms;
33 | exports org.prowl.kisset.objects.routing;
34 | exports org.prowl.kisset.protocols.netrom;
35 | exports org.prowl.kisset.util;
36 | exports org.prowl.kisset.config;
37 | exports org.prowl.kisset.eventbus;
38 | exports org.prowl.kisset.services.host.parser;
39 | exports org.prowl.kisset.userinterface;
40 | exports org.prowl.kisset.annotations;
41 | exports org.prowl.kisset.services.host.parser.commands;
42 | exports org.prowl.kisset.io;
43 | exports org.prowl.ax25.util;
44 | exports org.prowl.kisset.services;
45 | exports org.prowl.kisset.objects.dxcluster;
46 | exports org.prowl.kisset.protocols.core;
47 | exports org.prowl.kisset.util.compression.deflate;
48 | exports org.prowl.kisset.util.compression.deflatehuffman;
49 | exports org.prowl.kisset.services.remote.netrom.user;
50 | exports org.prowl.kisset.services.remote.netrom.user.parser.commands;
51 | exports org.prowl.kisset.services.remote.netrom.circuit;
52 | exports org.prowl.kisset.services.remote.netrom.server;
53 |
54 | }
55 |
56 |
--------------------------------------------------------------------------------
/KISSetGUI/src/main/java/org/prowl/kissetgui/userinterface/desktop/fx/ConnectionPreferenceInterface.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kissetgui.userinterface.desktop.fx;
2 |
3 | import javafx.collections.FXCollections;
4 | import javafx.fxml.FXML;
5 | import javafx.scene.control.ChoiceBox;
6 | import javafx.scene.control.TextField;
7 | import org.apache.commons.configuration.HierarchicalConfiguration;
8 | import org.prowl.kisset.config.BeaconType;
9 | import org.prowl.kisset.config.Conf;
10 |
11 | public abstract class ConnectionPreferenceInterface {
12 |
13 | @FXML
14 | private ChoiceBox beaconChoice;
15 | @FXML
16 | private TextField beaconText;
17 |
18 | private ConnectionPreferenceHost connectionPreferenceHost;
19 |
20 | @FXML
21 | private void beaconTextChanged() {
22 | connectionPreferenceHost.setValidation(validate());
23 | }
24 |
25 | @FXML
26 | private void beaconIntervalChanged() {
27 | connectionPreferenceHost.setValidation(validate());
28 | }
29 |
30 |
31 | protected void init(HierarchicalConfiguration configInterfaceNode, PreferencesController controller, ConnectionPreferenceHost host) {
32 | this.connectionPreferenceHost = host;
33 | }
34 |
35 | public boolean validate() {
36 | return true;
37 | }
38 |
39 | @FXML
40 | public void validateAll() {
41 | connectionPreferenceHost.setValidation(validate());
42 | }
43 |
44 | protected void applyToConfig(HierarchicalConfiguration configuration) {
45 | configuration.setProperty(Conf.beaconText.name(), beaconText.getText());
46 | configuration.setProperty(Conf.beaconEvery.name(), beaconChoice.getSelectionModel().getSelectedItem().getInterval());
47 | }
48 |
49 | protected void applyFromConfig(HierarchicalConfiguration configInterfaceNode) {
50 | // Beacons
51 | beaconText.setText(configInterfaceNode.getString(Conf.beaconText.name(), Conf.beaconText.stringDefault()));
52 | beaconChoice.setItems(FXCollections.observableArrayList(BeaconType.values()));
53 | int beaconInterval = configInterfaceNode.getInteger(Conf.beaconEvery.name(), Conf.beaconEvery.intDefault());
54 | beaconChoice.getSelectionModel().select(BeaconType.getBeaconType(beaconInterval));
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/pms/parser/Mode.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.pms.parser;
2 |
3 |
4 | import java.util.Objects;
5 |
6 | /**
7 | * This represents the mode of the parser. The parser can be in different modes depending on the command that was
8 | * executed, for example message read pagination mode, message list pagination mode, to provide 'continue' prompts to the
9 | * user
10 | *
11 | * Other modes like can be made for example a 'File BBS' mode that uses different sets of plugin provided commands.
12 | *
13 | * Plugins implementing the @Commandable annotation check the mode to behaving accordingly. Commands can overload and use the
14 | * same command name as other commands, but this should be only done when they use their 'own' mode so as not to
15 | * intefere with app modes.
16 | */
17 | public class Mode {
18 |
19 | // Command mode
20 | public static final Mode CMD = new Mode("PMS_CMD");
21 |
22 | // Reading a message mode (for pagination handling)
23 | public static final Mode MESSAGE_READ_PAGINATION = new Mode("MESSAGE_READ_PAGINATION");
24 |
25 | // Listing messages mode (for pagination handling)
26 | public static final Mode MESSAGE_LIST_PAGINATION = new Mode("MESSAGE_LIST_PAGINATION");
27 |
28 | // Sending a private message mode
29 | public static final Mode SENDING_PRIVATE_MESSAGE = new Mode("SENDING_PRIVATE_MESSAGE");
30 |
31 | // Sending a public/bulletin message mode
32 | public static final Mode SENDING_PUBLIC_MESSAGE = new Mode("SENDING_PUBLIC_MESSAGE");
33 |
34 | private final String mode;
35 |
36 | /**
37 | * Creates a mode object which plugins can also create
38 | *
39 | * @param mode
40 | */
41 | public Mode(String mode) {
42 | this.mode = mode;
43 | }
44 |
45 | public String toString() {
46 | return mode;
47 | }
48 |
49 | @Override
50 | public boolean equals(Object o) {
51 | if (this == o) return true;
52 | if (o == null || getClass() != o.getClass()) return false;
53 | Mode mode1 = (Mode) o;
54 | return Objects.equals(mode, mode1.mode);
55 | }
56 |
57 | @Override
58 | public int hashCode() {
59 | return Objects.hash(mode);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/userinterface/stdinout/StdTerminal.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.userinterface.stdinout;
2 |
3 | import org.apache.commons.logging.Log;
4 | import org.apache.commons.logging.LogFactory;
5 | import org.prowl.kisset.util.PipedIOStream;
6 |
7 | import java.io.InputStream;
8 | import java.io.OutputStream;
9 | import java.nio.channels.Pipe;
10 |
11 | /**
12 | * This interface so we can support different types of text based terminals.
13 | *
14 | * It will be assumed that the hosting terminal will be at least ANSI compliant as that seems to be the standard
15 | * for a lot of terminals nowdays on Linux, Mac and Windows.
16 | */
17 | public abstract class StdTerminal {
18 |
19 | private static Log LOG = LogFactory.getLog("StdTerminal");
20 |
21 | // STDIN from the console
22 | protected InputStream stdIn;
23 |
24 | // STDOUT to the console
25 | protected OutputStream stdOut;
26 |
27 | // The TNC host inpurt stream
28 | protected PipedIOStream tncIn;
29 |
30 | // The TNC host output stream
31 | protected PipedIOStream tncOut;
32 |
33 |
34 | public StdTerminal(InputStream stdIn, OutputStream stdOut) {
35 | this.stdIn = stdIn;
36 | this.stdOut = stdOut;
37 |
38 | this.tncIn = new PipedIOStream();
39 | this.tncOut = new PipedIOStream();
40 |
41 | }
42 |
43 | public StdTerminal() {
44 |
45 | this.tncIn = new PipedIOStream();
46 | this.tncOut = new PipedIOStream();
47 |
48 | }
49 |
50 | public void setIOStreams(InputStream stdIn, OutputStream stdOut) {
51 | this.stdIn = stdIn;
52 | this.stdOut = stdOut;
53 |
54 | }
55 |
56 |
57 | /**
58 | * Start the terminal this should deal with filtering any data
59 | */
60 | public abstract void start();
61 |
62 | /**
63 | * Stop the terminal
64 | */
65 | public abstract void stop();
66 |
67 | /**
68 | * The input stream that talks to the TNC Host
69 | *
70 | * @return
71 | */
72 | public InputStream getInputStream() {
73 | return tncIn;
74 | }
75 |
76 |
77 | /**
78 | * The output stream that talks to the TNC Host
79 | *
80 | * @return
81 | */
82 | public OutputStream getOutputStream() {
83 | return tncOut.getOutputStream();
84 | }
85 |
86 |
87 | }
88 |
--------------------------------------------------------------------------------
/KISSetGUI/src/packaging/add-launch-to-msi.js:
--------------------------------------------------------------------------------
1 | // run with command
2 | // cscript add-change.js
3 | var installer = WScript.CreateObject("WindowsInstaller.Installer");
4 | var database = installer.OpenDatabase("${app.name}-${app.version}.msi", 1);
5 | var sql
6 | var view
7 |
8 | var file = FindFileIdentifier(database, "${app.name}.exe");
9 |
10 | try {
11 | sql = "INSERT INTO `CustomAction` (`Action`,`Type`,`Source`) VALUES ('ExecuteAfterFinalize','2258','" + file + "')"
12 | WScript.StdErr.WriteLine(sql);
13 | view = database.OpenView(sql);
14 | view.Execute();
15 | view.Close();
16 |
17 | sql = "INSERT INTO `InstallExecuteSequence` (`Action`,`Condition`,`Sequence`) VALUES ('ExecuteAfterFinalize','NOT Installed','6700')"
18 | WScript.StdErr.WriteLine(sql);
19 | view = database.OpenView(sql);
20 | view.Execute();
21 | view.Close();
22 | WScript.StdErr.WriteLine("Committing changes");
23 | database.Commit();
24 | } catch (e) {
25 | WScript.StdErr.WriteLine(e);
26 | WScript.Quit(1);
27 | }
28 |
29 | // Finds file id and component id of file
30 | function FindFileIdentifier(database, fileName) {
31 | var sql
32 | var view
33 | var record
34 |
35 | // First, try to find the exact file name
36 | sql = "SELECT `File`, `Component_` FROM `File` WHERE `FileName`='" + fileName + "'";
37 | view = database.OpenView(sql);
38 | view.Execute();
39 | record = view.Fetch();
40 | if (record) {
41 | var value = record.StringData(1);
42 | componentId = record.StringData(2)
43 | view.Close();
44 | return value;
45 | }
46 | view.Close();
47 |
48 | // The file may be in SFN|LFN format. Look for a filename in this case next
49 | sql = "SELECT `File`, `Component_`, `FileName` FROM `File`";
50 | view = database.OpenView(sql);
51 | view.Execute();
52 | record = view.Fetch();
53 | while (record) {
54 | if (StringEndsWith(record.StringData(3), "|" + fileName)) {
55 | componentId = record.StringData(2);
56 | var value = record.StringData(1);
57 | view.Close();
58 | return value;
59 | }
60 |
61 | record = view.Fetch();
62 | }
63 | view.Close();
64 |
65 | }
66 |
67 | function StringEndsWith(str, value) {
68 | if (str.length < value.length)
69 | return false;
70 |
71 | return (str.indexOf(value, str.length - value.length) != -1);
72 | }
--------------------------------------------------------------------------------
/KISSetMain/src/packaging/add-launch-to-msi.js:
--------------------------------------------------------------------------------
1 | // run with command
2 | // cscript add-change.js
3 | var installer = WScript.CreateObject("WindowsInstaller.Installer");
4 | var database = installer.OpenDatabase("${app.name}-${app.version}.msi", 1);
5 | var sql
6 | var view
7 |
8 | var file = FindFileIdentifier(database, "${app.name}.exe");
9 |
10 | try {
11 | sql = "INSERT INTO `CustomAction` (`Action`,`Type`,`Source`) VALUES ('ExecuteAfterFinalize','2258','" + file + "')"
12 | WScript.StdErr.WriteLine(sql);
13 | view = database.OpenView(sql);
14 | view.Execute();
15 | view.Close();
16 |
17 | sql = "INSERT INTO `InstallExecuteSequence` (`Action`,`Condition`,`Sequence`) VALUES ('ExecuteAfterFinalize','NOT Installed','6700')"
18 | WScript.StdErr.WriteLine(sql);
19 | view = database.OpenView(sql);
20 | view.Execute();
21 | view.Close();
22 | WScript.StdErr.WriteLine("Committing changes");
23 | database.Commit();
24 | } catch (e) {
25 | WScript.StdErr.WriteLine(e);
26 | WScript.Quit(1);
27 | }
28 |
29 | // Finds file id and component id of file
30 | function FindFileIdentifier(database, fileName) {
31 | var sql
32 | var view
33 | var record
34 |
35 | // First, try to find the exact file name
36 | sql = "SELECT `File`, `Component_` FROM `File` WHERE `FileName`='" + fileName + "'";
37 | view = database.OpenView(sql);
38 | view.Execute();
39 | record = view.Fetch();
40 | if (record) {
41 | var value = record.StringData(1);
42 | componentId = record.StringData(2)
43 | view.Close();
44 | return value;
45 | }
46 | view.Close();
47 |
48 | // The file may be in SFN|LFN format. Look for a filename in this case next
49 | sql = "SELECT `File`, `Component_`, `FileName` FROM `File`";
50 | view = database.OpenView(sql);
51 | view.Execute();
52 | record = view.Fetch();
53 | while (record) {
54 | if (StringEndsWith(record.StringData(3), "|" + fileName)) {
55 | componentId = record.StringData(2);
56 | var value = record.StringData(1);
57 | view.Close();
58 | return value;
59 | }
60 |
61 | record = view.Fetch();
62 | }
63 | view.Close();
64 |
65 | }
66 |
67 | function StringEndsWith(str, value) {
68 | if (str.length < value.length)
69 | return false;
70 |
71 | return (str.indexOf(value, str.length - value.length) != -1);
72 | }
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/Transmitting.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 |
3 | /*
4 | * Copyright (C) 2011-2018 Andrew Pavlin, KA2DDO
5 | * This file is part of YAAC (Yet Another APRS Client).
6 | *
7 | * YAAC is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU Lesser General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * YAAC is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * and GNU Lesser General Public License along with YAAC. If not,
19 | * see .
20 | */
21 |
22 | /**
23 | * This interface specifies an object that can queue AX.25 frames for transmission through
24 | * Connectors.
25 | *
26 | * @author Andrew Pavlin, KA2DDO
27 | * @see Connector
28 | * @see AX25Frame
29 | */
30 | public interface Transmitting {
31 |
32 | /**
33 | * Queue the specified frame source for transmission over the specified (or all, if not
34 | * specified) transmit-enabled Connectors.
35 | *
36 | * @param entry AX25FrameSource of the frame to be transmitted
37 | */
38 | void queue(AX25FrameSource entry);
39 |
40 | /**
41 | * Queue the specified frame source for transmission over the specified (or all, if not
42 | * specified) transmit-enabled Connectors.
43 | *
44 | * @param entry AX25FrameSource of the frame to be transmitted
45 | * @param timeToSend long time in milliseconds since Unix epoch when packet is to be dequeued and transmitted
46 | */
47 | void delayedQueue(AX25FrameSource entry, long timeToSend);
48 |
49 | /**
50 | * Test if this callsign is addressed to the local station.
51 | *
52 | * @param destCallsign String of AX.25 callsign-SSID to test as a destination
53 | * @return boolean true if this callsign is for the local station
54 | */
55 | boolean isLocalDest(String destCallsign);
56 |
57 | /**
58 | * Get the locally-originated message retransmit count.
59 | *
60 | * @return default retransmit count
61 | */
62 | int getRetransmitCount();
63 | }
64 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/protocols/RoutingListener.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.protocols;
2 |
3 | import com.google.common.eventbus.Subscribe;
4 | import org.apache.commons.logging.Log;
5 | import org.apache.commons.logging.LogFactory;
6 | import org.prowl.ax25.AX25Frame;
7 | import org.prowl.kisset.KISSet;
8 | import org.prowl.kisset.eventbus.SingleThreadBus;
9 | import org.prowl.kisset.eventbus.events.HeardNodeEvent;
10 | import org.prowl.kisset.protocols.netrom.NetROMRoutingPacket;
11 | import org.prowl.kisset.protocols.netrom.NetROMRoutingTable;
12 | import org.prowl.kisset.protocols.xrouter.INP3RoutingPacket;
13 | import org.prowl.kisset.protocols.xrouter.INP3RoutingTable;
14 |
15 | /**
16 | * Listen to node packets and use it to build a list of routes
17 | */
18 | public enum RoutingListener {
19 |
20 | INSTANCE;
21 |
22 | private static final Log LOG = LogFactory.getLog("RoutingListener");
23 |
24 |
25 | RoutingListener() {
26 | // Load the routing table from disk (expiring old entries)
27 | KISSet.INSTANCE.getStorage().loadNetROMRoutingTable();
28 | // Start listening for new routes
29 | SingleThreadBus.INSTANCE.register(this);
30 | }
31 |
32 | @Subscribe
33 | public void onHeardNode(HeardNodeEvent event) {
34 | try {
35 | if (event.getNode().getFrame().getPid() == AX25Frame.PID_NETROM) {
36 | byte[] body = event.getNode().getFrame().getBody();
37 | if ((body[0] & 0xFF) == 0xFF && ((body[body.length - 1] & 0xFF) != 0)) {
38 | // It's a netrom packet
39 | NetROMRoutingPacket netROMRoutingPacket = new NetROMRoutingPacket(event.getNode());
40 | NetROMRoutingTable.INSTANCE.addRoutes(netROMRoutingPacket.getRoutesInThisPacket());
41 | KISSet.INSTANCE.getStorage().saveNetROMRoutingTable();
42 | } else if ((body[0] & 0xFF) == 0xFF && ((body[body.length - 1] & 0xFF) == 0)) {
43 | // It's an inp3 routing packet
44 | INP3RoutingPacket inp3RoutingPacket = new INP3RoutingPacket(event.getNode());
45 | INP3RoutingTable.INSTANCE.addRoutes(inp3RoutingPacket.getRoutes());
46 | KISSet.INSTANCE.getStorage().saveNetROMRoutingTable();
47 | }
48 |
49 | }
50 | } catch (Throwable e) {
51 | LOG.error(e.getMessage(), e);
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/ConnectionRequestListener.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 |
3 | /*
4 | * Copyright (C) 2011-2023 Andrew Pavlin, KA2DDO
5 | * This file is part of YAAC (Yet Another APRS Client).
6 | *
7 | * YAAC is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU Lesser General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * YAAC is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * and GNU Lesser General Public License along with YAAC. If not,
19 | * see .
20 | */
21 |
22 | /**
23 | * This interface defines a means by which an arbitrary handler can choose to accept
24 | * an inbound AX.25 connected-mode session request.
25 | *
26 | * @author Andrew Pavlin, KA2DDO
27 | */
28 | public interface ConnectionRequestListener {
29 | /**
30 | * Decide whether to accept the specified inbound AX.25 connected-mode session request. Note
31 | * that the state is not fully connected at the point of this call (so the called code can choose
32 | * to reject it), so the called code should register a {@link ConnectionEstablishmentListener} on the
33 | * ConnState to be informed when the connection is fully established if the called code chooses to
34 | * accept the connection request.
35 | *
36 | * @param state ConnState object describing the session being built
37 | * @param originator AX25Callsign of the originating station
38 | * @param port Connector through which the request was received
39 | * @return boolean true if request should be accepted, false if not
40 | * @see ConnState#listener
41 | */
42 | boolean acceptInbound(ConnState state, AX25Callsign originator, Connector port);
43 |
44 | /**
45 | * The connector needs to know the callsigns that are running services (we are listening for connections to)
46 | * and this method can check for this.
47 | *
48 | * @param callsign The callsign to check
49 | * @return true if the callsign is a local callsign
50 | */
51 | boolean isLocal(String callsign);
52 | }
53 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/Disconnect.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.prowl.kisset.annotations.TNCCommand;
4 | import org.prowl.kisset.io.Stream;
5 | import org.prowl.kisset.io.StreamState;
6 | import org.prowl.kisset.services.host.parser.Mode;
7 |
8 | import java.io.IOException;
9 |
10 | @TNCCommand
11 | public class Disconnect extends Command {
12 |
13 | @Override
14 | public boolean doCommand(String[] data) throws IOException {
15 |
16 | if (!getMode().equals(Mode.CMD)) {
17 | return false;
18 | }
19 |
20 | if (data.length > 1 && data[1].equals("?")) {
21 | writeToTerminal("*** Usage: disconnect" + CR);
22 | return true;
23 | }
24 |
25 | if (commandParser.getCurrentInterface() == null) {
26 | writeToTerminal("*** No interfaces configured" + CR);
27 | return true;
28 | }
29 |
30 | // Get the current stream we are using
31 | Stream currentStream = commandParser.getCurrentInterface().getCurrentStream();
32 |
33 | // If we're in command mode, then
34 | if (currentStream.getStreamState().equals(StreamState.DISCONNECTED)) {
35 | writeToTerminal("*** Not connected to a station");
36 | return true;
37 | }
38 |
39 | // Cancel the current connection attempt on the current interface
40 | if (currentStream.getStreamState().equals(StreamState.CONNECTING)) {
41 | commandParser.getCurrentInterface().cancelConnection(currentStream);
42 | writeToTerminal("*** Connection attempt cancelled");
43 | return true;
44 | }
45 |
46 | // Disconnect the current stream
47 | if (currentStream.getStreamState().equals(StreamState.CONNECTED)) {
48 | writeToTerminal("*** Disconnecting");
49 | currentStream.disconnect();
50 | return true;
51 | }
52 |
53 | if (currentStream.getStreamState().equals(StreamState.DISCONNECTING)) {
54 | currentStream.disconnectNow();
55 | writeToTerminal("*** Disconnected");
56 | return true;
57 | }
58 |
59 | commandParser.closeDivertStream();
60 | commandParser.setMode(Mode.CMD);
61 | writeToTerminal(CR);
62 |
63 | return true;
64 | }
65 |
66 |
67 | @Override
68 | public String[] getCommandNames() {
69 | return new String[]{"disc", "d", "disconnect"};
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/TXTail.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.apache.commons.configuration.HierarchicalConfiguration;
4 | import org.prowl.ax25.KissParameterType;
5 | import org.prowl.kisset.KISSet;
6 | import org.prowl.kisset.annotations.TNCCommand;
7 | import org.prowl.kisset.config.Conf;
8 | import org.prowl.kisset.config.Config;
9 | import org.prowl.kisset.services.host.parser.Mode;
10 | import org.prowl.kisset.util.ANSI;
11 | import org.prowl.kisset.util.Tools;
12 |
13 | import java.io.IOException;
14 |
15 | /**
16 | * Set the TXTail parameter
17 | */
18 | @TNCCommand
19 | public class TXTail extends Command {
20 |
21 | @Override
22 | public boolean doCommand(String[] data) throws IOException {
23 |
24 | // This command requires CMD mode to be active
25 | if (!getMode().equals(Mode.CMD)) {
26 | return false;
27 | }
28 |
29 | // Check the user has configured and selected an interface
30 | if (commandParser.getCurrentInterface() == null) {
31 | writeToTerminal(ANSI.RED + "*** No interface selected" + ANSI.NORMAL + CR);
32 | return true;
33 | }
34 |
35 | // Querying the value or setting it?
36 | if (data.length == 1) {
37 | Disp.showValue(this, "TXTail", Disp.getKissParameterValue(commandParser, KissParameterType.TX_TAIL));
38 | } else {
39 |
40 | // Get the value and check it
41 | int value = Tools.getInteger(data[1], -1);
42 | if (value == -1) {
43 | writeToTerminal(ANSI.RED + "*** Invalid value" + ANSI.NORMAL + CR);
44 | }
45 |
46 | // Set it on the interface
47 | commandParser.getCurrentInterface().setKissParameter(KissParameterType.TX_TAIL, value);
48 |
49 | // Now update the config.
50 | Config config = KISSet.INSTANCE.getConfig();
51 | HierarchicalConfiguration iConfig = config.getInterfaceConfig(commandParser.getCurrentInterface().getUUID());
52 | iConfig.setProperty(Conf.txTail.name(), value);
53 | config.saveConfig();
54 |
55 | // Show the new value to the user
56 | Disp.showValue(this, "TXTail", Disp.getKissParameterValue(commandParser, KissParameterType.TX_TAIL));
57 | }
58 |
59 | return true;
60 | }
61 |
62 |
63 | @Override
64 | public String[] getCommandNames() {
65 | return new String[]{"txt", "txtail"};
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/KISSetGUI/src/main/java/org/prowl/kissetgui/userinterface/desktop/utils/GUITools.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kissetgui.userinterface.desktop.utils;
2 |
3 | import javafx.scene.text.Font;
4 | import javafx.scene.text.FontPosture;
5 | import javafx.scene.text.FontWeight;
6 | import javafx.scene.text.Text;
7 |
8 | import java.awt.geom.Point2D;
9 | import java.awt.geom.Rectangle2D;
10 | import java.util.ArrayList;
11 | import java.util.List;
12 |
13 | public class GUITools {
14 |
15 | /**
16 | * Get a list of all the monospaced fonts available on the system.
17 | */
18 | public static List getMonospacedFonts() {
19 | List found = new ArrayList<>();
20 | final Text text1 = new Text("i i l i l i l i l");
21 | final Text text2 = new Text("A B C D E F G H I");
22 | for (String fontFamilyName : Font.getFamilies()) {
23 | // Get the font
24 | Font font = Font.font(fontFamilyName, FontWeight.NORMAL, FontPosture.REGULAR, 12d);
25 | text1.setFont(font);
26 | text2.setFont(font);
27 | if (text1.getLayoutBounds().getWidth() == text2.getLayoutBounds().getWidth()) {
28 | found.add(fontFamilyName);
29 | }
30 | }
31 | return found;
32 | }
33 |
34 |
35 | /**
36 | * Convert from a locator to a lat lon centered in the bounding box
37 | *
38 | * @return
39 | */
40 | public static Point2D locatorToLatLonCentered(String locator) {
41 | Point2D latlng = new Point2D.Double();
42 | Rectangle2D box = locatorToBoundingBox(locator);
43 | latlng.setLocation(box.getCenterX(), box.getCenterY());
44 | return latlng;
45 | }
46 |
47 | /**
48 | * Convert from a locator to a lat/lon bounding box
49 | */
50 | public static Rectangle2D locatorToBoundingBox(String locator) {
51 | Rectangle2D boundingBox = new Rectangle2D.Double();
52 | locator = locator.toUpperCase();
53 | double[] bbox = new double[4];
54 | bbox[0] = (locator.charAt(0) - 'A') * 20 - 180;
55 | bbox[1] = (locator.charAt(1) - 'A') * 10 - 90;
56 | bbox[2] = (locator.charAt(2) - '0') * 2;
57 | bbox[3] = (locator.charAt(3) - '0');
58 |
59 | bbox[0] += (locator.charAt(4) - 'A') * 5.0 / 60;
60 | bbox[1] += (locator.charAt(5) - 'A') * 2.5 / 60;
61 | bbox[2] += 2.5 / 60;
62 | bbox[3] += 5.0 / 60;
63 |
64 | boundingBox.setRect(bbox[0], bbox[1], bbox[2], bbox[3]);
65 |
66 | return boundingBox;
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/TXDelay.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.apache.commons.configuration.HierarchicalConfiguration;
4 | import org.prowl.ax25.KissParameterType;
5 | import org.prowl.kisset.KISSet;
6 | import org.prowl.kisset.annotations.TNCCommand;
7 | import org.prowl.kisset.config.Conf;
8 | import org.prowl.kisset.config.Config;
9 | import org.prowl.kisset.services.host.parser.Mode;
10 | import org.prowl.kisset.util.ANSI;
11 | import org.prowl.kisset.util.Tools;
12 |
13 | import java.io.IOException;
14 |
15 | /**
16 | * Set the TXDelay parameter
17 | */
18 | @TNCCommand
19 | public class TXDelay extends Command {
20 |
21 | @Override
22 | public boolean doCommand(String[] data) throws IOException {
23 |
24 | // This command requires CMD mode to be active
25 | if (!getMode().equals(Mode.CMD)) {
26 | return false;
27 | }
28 |
29 | // Check the user has configured and selected an interface
30 | if (commandParser.getCurrentInterface() == null) {
31 | writeToTerminal(ANSI.RED + "*** No interface selected" + ANSI.NORMAL + CR);
32 | return true;
33 | }
34 |
35 | // Querying the value or setting it?
36 | if (data.length == 1) {
37 | Disp.showValue(this, "TXDelay", Disp.getKissParameterValue(commandParser, KissParameterType.TXDELAY));
38 | } else {
39 |
40 | // Get the value and check it
41 | int value = Tools.getInteger(data[1], -1);
42 | if (value == -1) {
43 | writeToTerminal(ANSI.RED + "*** Invalid value" + ANSI.NORMAL + CR);
44 | }
45 |
46 | // Set it on the interface
47 | commandParser.getCurrentInterface().setKissParameter(KissParameterType.TXDELAY, value);
48 |
49 | // Now update the config.
50 | Config config = KISSet.INSTANCE.getConfig();
51 | HierarchicalConfiguration iConfig = config.getInterfaceConfig(commandParser.getCurrentInterface().getUUID());
52 | iConfig.setProperty(Conf.txDelay.name(), value);
53 | config.saveConfig();
54 |
55 | // Show the new value to the user
56 | Disp.showValue(this, "TXDelay", Disp.getKissParameterValue(commandParser, KissParameterType.TXDELAY));
57 | }
58 |
59 | return true;
60 | }
61 |
62 |
63 | @Override
64 | public String[] getCommandNames() {
65 | return new String[]{"txd", "txdelay"};
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/SlotTime.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.apache.commons.configuration.HierarchicalConfiguration;
4 | import org.prowl.ax25.KissParameterType;
5 | import org.prowl.kisset.KISSet;
6 | import org.prowl.kisset.annotations.TNCCommand;
7 | import org.prowl.kisset.config.Conf;
8 | import org.prowl.kisset.config.Config;
9 | import org.prowl.kisset.services.host.parser.Mode;
10 | import org.prowl.kisset.util.ANSI;
11 | import org.prowl.kisset.util.Tools;
12 |
13 | import java.io.IOException;
14 |
15 | /**
16 | * Set the SlotTime KISS parameter
17 | */
18 | @TNCCommand
19 | public class SlotTime extends Command {
20 |
21 | @Override
22 | public boolean doCommand(String[] data) throws IOException {
23 |
24 | // This command requires CMD mode to be active
25 | if (!getMode().equals(Mode.CMD)) {
26 | return false;
27 | }
28 |
29 | // Check the user has configured and selected an interface
30 | if (commandParser.getCurrentInterface() == null) {
31 | writeToTerminal(ANSI.RED + "*** No interface selected" + ANSI.NORMAL + CR);
32 | return true;
33 | }
34 |
35 | // Querying the value or setting it?
36 | if (data.length == 1) {
37 | Disp.showValue(this, "SLOTtime", Disp.getKissParameterValue(commandParser, KissParameterType.SLOT_TIME));
38 | } else {
39 |
40 | // Get the value and check it
41 | int value = Tools.getInteger(data[1], -1);
42 | if (value == -1) {
43 | writeToTerminal(ANSI.RED + "*** Invalid value" + ANSI.NORMAL + CR);
44 | }
45 |
46 | // Set it on the interface
47 | commandParser.getCurrentInterface().setKissParameter(KissParameterType.SLOT_TIME, value);
48 |
49 | // Now update the config.
50 | Config config = KISSet.INSTANCE.getConfig();
51 | HierarchicalConfiguration iConfig = config.getInterfaceConfig(commandParser.getCurrentInterface().getUUID());
52 | iConfig.setProperty(Conf.slotTime.name(), value);
53 | config.saveConfig();
54 |
55 | // Show the new value to the user
56 | Disp.showValue(this, "SLOTtime", Disp.getKissParameterValue(commandParser, KissParameterType.SLOT_TIME));
57 | }
58 |
59 | return true;
60 | }
61 |
62 |
63 | @Override
64 | public String[] getCommandNames() {
65 | return new String[]{"slot", "slottime"};
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/pms/parser/commands/MHeard.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.pms.parser.commands;
2 |
3 | import org.apache.commons.lang.StringUtils;
4 | import org.prowl.kisset.KISSet;
5 | import org.prowl.kisset.annotations.PMSCommand;
6 | import org.prowl.kisset.io.Interface;
7 | import org.prowl.kisset.protocols.core.Capability;
8 | import org.prowl.kisset.protocols.core.Node;
9 | import org.prowl.kisset.services.remote.pms.parser.Mode;
10 | import org.prowl.kisset.util.ANSI;
11 |
12 | import java.io.IOException;
13 | import java.text.SimpleDateFormat;
14 | import java.util.List;
15 |
16 | @PMSCommand
17 | public class MHeard extends Command {
18 |
19 | @Override
20 | public boolean doCommand(String[] data) throws IOException {
21 |
22 | if (!getMode().equals(Mode.CMD)) {
23 | return false;
24 | }
25 | write(CR);
26 | SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy hh:mm:ss");
27 | org.prowl.kisset.statistics.types.MHeard heard = KISSet.INSTANCE.getStatistics().getHeard();
28 | List connectors = KISSet.INSTANCE.getInterfaceHandler().getInterfaces();
29 | List nodes = heard.listHeard();
30 | if (nodes.size() == 0) {
31 | write("*** No nodes heard" + CR);
32 | } else {
33 | write(ANSI.UNDERLINE + ANSI.BOLD + "Int Callsign Last Heard Capabilities" + ANSI.NORMAL + CR);
34 | for (Node node : nodes) {
35 | write(StringUtils.rightPad(Integer.toString(connectors.indexOf(node.getInterface())), 5) + StringUtils.rightPad(node.getCallsign(), 10) + StringUtils.rightPad(sdf.format(node.getLastHeard()), 18) + " " + StringUtils.rightPad(listCapabilities(node), 14) + CR);
36 | }
37 | }
38 |
39 |
40 | return true;
41 | }
42 |
43 |
44 | /**
45 | * Returns a string list of capability names this node has been seen to perform.
46 | *
47 | * @param node
48 | * @return
49 | */
50 | public String listCapabilities(Node node) {
51 | StringBuilder sb = new StringBuilder();
52 | for (Capability c : node.getCapabilities()) {
53 | sb.append(c.getService().getName());
54 | sb.append(",");
55 | }
56 | if (sb.length() > 0) {
57 | sb.deleteCharAt(sb.length() - 1);
58 | }
59 | return sb.toString();
60 | }
61 |
62 |
63 | @Override
64 | public String[] getCommandNames() {
65 | return new String[]{"mheard", "heard", "mh"};
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/ServiceHandler.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services;
2 |
3 | import org.apache.commons.configuration.HierarchicalConfiguration;
4 | import org.apache.commons.configuration.SubnodeConfiguration;
5 | import org.apache.commons.logging.Log;
6 | import org.apache.commons.logging.LogFactory;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class ServiceHandler {
12 |
13 | private static final Log LOG = LogFactory.getLog("ServiceHandler");
14 |
15 | private final SubnodeConfiguration configuration;
16 |
17 | private final List services = new ArrayList<>();
18 |
19 | public ServiceHandler(SubnodeConfiguration configuration) {
20 | this.configuration = configuration;
21 | parseConfiguration();
22 | }
23 |
24 | public Service getServiceForName(String name) {
25 | for (Service service : services) {
26 | if (service.getName().equals(name)) {
27 | return service;
28 | }
29 | }
30 | return null;
31 | }
32 |
33 | /**
34 | * Return a copy array of the current running services.
35 | *
36 | * @return
37 | */
38 | public List getServices() {
39 | return new ArrayList<>(services);
40 | }
41 |
42 | public void parseConfiguration() {
43 |
44 | // Get a list of user interfaces from the config file
45 | List services = configuration.configurationsAt("service");
46 |
47 | // Go create and configure each one.
48 | for (HierarchicalConfiguration service : services) {
49 | String className = service.getString("type");
50 | try {
51 | Service con = (Service) Class.forName(className).getConstructor(HierarchicalConfiguration.class).newInstance(service);
52 | this.services.add(con);
53 | LOG.info("Added service: " + className);
54 | } catch (Throwable e) {
55 | // Something blew up. Log it and carry on.
56 | LOG.error("Unable to add service: " + className, e);
57 | }
58 | }
59 | }
60 |
61 | public void start() {
62 | LOG.info("Starting services...");
63 | for (Service service : services) {
64 | try {
65 | LOG.info("Starting service: " + service.getName());
66 | service.start();
67 |
68 | } catch (Throwable e) {
69 | LOG.error("Unable to start service: " + service.getName(), e);
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/Persistence.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.apache.commons.configuration.HierarchicalConfiguration;
4 | import org.prowl.ax25.KissParameterType;
5 | import org.prowl.kisset.KISSet;
6 | import org.prowl.kisset.annotations.TNCCommand;
7 | import org.prowl.kisset.config.Conf;
8 | import org.prowl.kisset.config.Config;
9 | import org.prowl.kisset.services.host.parser.Mode;
10 | import org.prowl.kisset.util.ANSI;
11 | import org.prowl.kisset.util.Tools;
12 |
13 | import java.io.IOException;
14 |
15 | /**
16 | * Set the persistence KISS parameter
17 | */
18 | @TNCCommand
19 | public class Persistence extends Command {
20 |
21 | @Override
22 | public boolean doCommand(String[] data) throws IOException {
23 |
24 | // This command requires CMD mode to be active
25 | if (!getMode().equals(Mode.CMD)) {
26 | return false;
27 | }
28 |
29 | // Check the user has configured and selected an interface
30 | if (commandParser.getCurrentInterface() == null) {
31 | writeToTerminal(ANSI.RED + "*** No interface selected" + ANSI.NORMAL + CR);
32 | return true;
33 | }
34 |
35 | // Querying the value or setting it?
36 | if (data.length == 1) {
37 | Disp.showValue(this, "PERsistence", Disp.getKissParameterValue(commandParser, KissParameterType.PERSISTENCE));
38 | } else {
39 |
40 | // Get the value and check it
41 | int value = Tools.getInteger(data[1], -1);
42 | if (value == -1) {
43 | writeToTerminal(ANSI.RED + "*** Invalid value" + ANSI.NORMAL + CR);
44 | }
45 |
46 | // Set it on the interface
47 | commandParser.getCurrentInterface().setKissParameter(KissParameterType.PERSISTENCE, value);
48 |
49 | // Now update the config.
50 | Config config = KISSet.INSTANCE.getConfig();
51 | HierarchicalConfiguration iConfig = config.getInterfaceConfig(commandParser.getCurrentInterface().getUUID());
52 | iConfig.setProperty(Conf.persistence.name(), value);
53 | config.saveConfig();
54 |
55 | // Show the new value to the user
56 | Disp.showValue(this, "PERsistence", Disp.getKissParameterValue(commandParser, KissParameterType.PERSISTENCE));
57 | }
58 |
59 | return true;
60 | }
61 |
62 |
63 | @Override
64 | public String[] getCommandNames() {
65 | return new String[]{"per","persist", "persistence"};
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/Mode.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser;
2 |
3 |
4 | import java.util.Objects;
5 |
6 | /**
7 | * This represents the mode of the parser. The parser can be in different modes depending on the command that was
8 | * executed, for example message read pagination mode, message list pagination mode, to provide 'continue' prompts to the
9 | * user
10 | *
11 | * Other modes like can be made for example a 'File BBS' mode that uses different sets of plugin provided commands.
12 | *
13 | * Plugins implementing the @Commandable annotation check the mode to behaving accordingly. Commands can overload and use the
14 | * same command name as other commands, but this should be only done when they use their 'own' mode so as not to
15 | * intefere with app modes.
16 | */
17 | public class Mode {
18 |
19 | // Command mode
20 | public static final Mode CMD = new Mode("CMD");
21 |
22 | // Reading a message mode (for pagination handling)
23 | public static final Mode MESSAGE_READ_PAGINATION = new Mode("MESSAGE_READ_PAGINATION");
24 |
25 | // Listing messages mode (for pagination handling)
26 | public static final Mode MESSAGE_LIST_PAGINATION = new Mode("MESSAGE_LIST_PAGINATION");
27 |
28 | // Connected to a station mode
29 | public static final Mode CONNECTED_TO_STATION = new Mode("CONNECTED_TO_STATION");
30 |
31 | // Connected to a internet station (telnet)
32 | public static final Mode CONNECTED_TO_INTERNET = new Mode("CONNECTED_TO_INTERNET");
33 |
34 | // Sending a private message mode
35 | public static final Mode SENDING_PRIVATE_MESSAGE = new Mode("SENDING_PRIVATE_MESSAGE");
36 |
37 | // Sending a public/bulletin message mode
38 | public static final Mode SENDING_PUBLIC_MESSAGE = new Mode("SENDING_PUBLIC_MESSAGE");
39 |
40 | public static final Mode CONFIGURE_INTERFACE = new Mode("CONFIGURE_INTERFACE");
41 |
42 |
43 | private final String mode;
44 |
45 | /**
46 | * Creates a mode object which plugins can also create
47 | *
48 | * @param mode
49 | */
50 | public Mode(String mode) {
51 | this.mode = mode;
52 | }
53 |
54 | public String toString() {
55 | return mode;
56 | }
57 |
58 | @Override
59 | public boolean equals(Object o) {
60 | if (this == o) return true;
61 | if (o == null || getClass() != o.getClass()) return false;
62 | Mode mode1 = (Mode) o;
63 | return Objects.equals(mode, mode1.mode);
64 | }
65 |
66 | @Override
67 | public int hashCode() {
68 | return Objects.hash(mode);
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/host/parser/commands/FullDuplex.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.host.parser.commands;
2 |
3 | import org.apache.commons.configuration.HierarchicalConfiguration;
4 | import org.prowl.ax25.KissParameterType;
5 | import org.prowl.kisset.KISSet;
6 | import org.prowl.kisset.annotations.TNCCommand;
7 | import org.prowl.kisset.config.Conf;
8 | import org.prowl.kisset.config.Config;
9 | import org.prowl.kisset.services.host.parser.Mode;
10 | import org.prowl.kisset.util.ANSI;
11 | import org.prowl.kisset.util.Tools;
12 |
13 | import java.io.IOException;
14 |
15 | /**
16 | * Set the Full duplex KISS parameter
17 | */
18 | @TNCCommand
19 | public class FullDuplex extends Command {
20 |
21 | @Override
22 | public boolean doCommand(String[] data) throws IOException {
23 |
24 | // This command requires CMD mode to be active
25 | if (!getMode().equals(Mode.CMD)) {
26 | return false;
27 | }
28 |
29 | // Check the user has configured and selected an interface
30 | if (commandParser.getCurrentInterface() == null) {
31 | writeToTerminal(ANSI.RED + "*** No interface selected" + ANSI.NORMAL + CR);
32 | return true;
33 | }
34 |
35 | // Querying the value or setting it?
36 | if (data.length == 1) {
37 | Disp.showValue(this, "FULLDuplex", Disp.getKissParameterValue(commandParser, KissParameterType.FULL_DUPLEX));
38 | } else {
39 |
40 | // Get the value and check it
41 | boolean value =false;
42 | try {
43 | value = Boolean.parseBoolean(data[1]);
44 | } catch (Exception e) {
45 | writeToTerminal(ANSI.RED + "*** Invalid value" + ANSI.NORMAL + CR);
46 | }
47 |
48 | // Set it on the interface
49 | commandParser.getCurrentInterface().setKissParameter(KissParameterType.FULL_DUPLEX, value ? 1 : 0);
50 |
51 | // Now update the config.
52 | Config config = KISSet.INSTANCE.getConfig();
53 | HierarchicalConfiguration iConfig = config.getInterfaceConfig(commandParser.getCurrentInterface().getUUID());
54 | iConfig.setProperty(Conf.fullDuplex.name(), value);
55 | config.saveConfig();
56 |
57 | // Show the new value to the user
58 | Disp.showValue(this, "FULLDuplex", Disp.getKissParameterValue(commandParser, KissParameterType.FULL_DUPLEX));
59 | }
60 |
61 | return true;
62 | }
63 |
64 |
65 | @Override
66 | public String[] getCommandNames() {
67 | return new String[]{"fulld", "fullduplex"};
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/ax25/ConnectionEstablishmentListener.java:
--------------------------------------------------------------------------------
1 | package org.prowl.ax25;
2 |
3 | /*
4 | * Copyright (C) 2011-2013 Andrew Pavlin, KA2DDO
5 | * This file is part of YAAC (Yet Another APRS Client).
6 | *
7 | * YAAC is free software: you can redistribute it and/or modify
8 | * it under the terms of the GNU Lesser General Public License as published by
9 | * the Free Software Foundation, either version 3 of the License, or
10 | * (at your option) any later version.
11 | *
12 | * YAAC is distributed in the hope that it will be useful,
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 | * GNU General Public License for more details.
16 | *
17 | * You should have received a copy of the GNU General Public License
18 | * and GNU Lesser General Public License along with YAAC. If not,
19 | * see .
20 | */
21 |
22 | /**
23 | * This interface provides an asynchronous callback for requests to
24 | * open a AX.25 I-frame connection to another station. Each of these methods can
25 | * only be called once for any given session.
26 | */
27 | public interface ConnectionEstablishmentListener {
28 | /**
29 | * Report that the requested connection has been successfully established.
30 | *
31 | * @param sessionIdentifier identifier of the particular connection
32 | * @param conn the ConnState object from which communications streams can be obtained
33 | */
34 | void connectionEstablished(Object sessionIdentifier, ConnState conn);
35 |
36 | /**
37 | * Report that the requested connection could not be established.
38 | *
39 | * @param sessionIdentifier identifier of the particular connection
40 | * @param reason object explaining why the connection could not be established
41 | */
42 | void connectionNotEstablished(Object sessionIdentifier, Object reason);
43 |
44 | /**
45 | * Report that the established connection was shut down normally.
46 | *
47 | * @param sessionIdentifier identifier of the particular connection
48 | * @param fromOtherEnd boolean true if other end initiated the close
49 | */
50 | void connectionClosed(Object sessionIdentifier, boolean fromOtherEnd);
51 |
52 | /**
53 | * Report that the established connection was closed abnormally.
54 | *
55 | * @param sessionIdentifier identifier of the particular connection
56 | * @param reason object explaining why the connection was lost
57 | */
58 | void connectionLost(Object sessionIdentifier, Object reason);
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/resources/org/prowl/kisset/userinterface/desktop/fx/ConnectionPreferenceHost.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
8 |
9 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/protocols/xrouter/INP3RoutingTable.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.protocols.xrouter;
2 |
3 | import org.prowl.kisset.objects.routing.INP3Route;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 | import java.util.Locale;
8 |
9 | public enum INP3RoutingTable {
10 |
11 | INSTANCE;
12 |
13 | private final List nodes = new ArrayList<>();
14 |
15 | /**
16 | * Add a node to the routing table, replacing any existing node with the same callsign
17 | *
18 | * @param node
19 | */
20 | public void addRoute(INP3Route node) {
21 |
22 | // Remove any existing node with the same callsign
23 | for (INP3Route existingNode : nodes) {
24 | if (existingNode.getDestinationNodeCallsign().equals(node.getDestinationNodeCallsign())
25 | && existingNode.getSourceCallsign().equals(node.getSourceCallsign())) {
26 | nodes.remove(existingNode);
27 | break;
28 | }
29 | }
30 |
31 | nodes.add(0, node);
32 | }
33 |
34 | /**
35 | * Add a list of nodes to the routing table, replacing any existing nodes with the same callsign
36 | *
37 | * @param nodes
38 | */
39 | public void addRoutes(List nodes) {
40 | for (INP3Route n : nodes) {
41 | addRoute(n);
42 | }
43 | }
44 |
45 | public void removeNode(INP3Route node) {
46 | nodes.remove(node);
47 | }
48 |
49 | public List getNodes() {
50 | return nodes;
51 | }
52 |
53 | public void clear() {
54 | nodes.clear();
55 | }
56 |
57 |
58 | /**
59 | * Get the best quality route to a callsign. If there are multiple routes with the same quality, the highest quality route is returned.
60 | *
61 | * @param callsignToConnectTo
62 | * @return The first hop to the callsign, or null if no route is found
63 | */
64 | public INP3Route getRoutingToCallsign(String callsignToConnectTo) {
65 | callsignToConnectTo = callsignToConnectTo.toUpperCase(Locale.ENGLISH);
66 | INP3Route bestNode = null;
67 | for (INP3Route node : nodes) {
68 | if (node.getDestinationNodeCallsign().equals(callsignToConnectTo) ||
69 | (node.hasAlias() && node.getAlias().equals(callsignToConnectTo))) {
70 | if (bestNode == null) {
71 | bestNode = node;
72 | } else {
73 | if (node.getTripTime() < bestNode.getTripTime()) {
74 | bestNode = node;
75 | }
76 | }
77 | }
78 | }
79 | return bestNode;
80 | }
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/services/remote/pms/parser/commands/UnHeard.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.services.remote.pms.parser.commands;
2 |
3 | import org.apache.commons.lang.StringUtils;
4 | import org.prowl.kisset.KISSet;
5 | import org.prowl.kisset.annotations.PMSCommand;
6 | import org.prowl.kisset.io.Interface;
7 | import org.prowl.kisset.protocols.core.Node;
8 | import org.prowl.kisset.services.remote.pms.parser.Mode;
9 | import org.prowl.kisset.statistics.types.MHeard;
10 | import org.prowl.kisset.util.ANSI;
11 |
12 | import java.io.IOException;
13 | import java.text.SimpleDateFormat;
14 | import java.util.List;
15 |
16 | @PMSCommand
17 | public class UnHeard extends Command {
18 |
19 | @Override
20 | public boolean doCommand(String[] data) throws IOException {
21 |
22 | // We're only interesteed in comamnd moed.
23 | if (!getMode().equals(Mode.CMD)) {
24 | return false;
25 | }
26 |
27 | write(CR);
28 | SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yy hh:mm:ss");
29 |
30 | MHeard heard = KISSet.INSTANCE.getStatistics().getUnHeard();
31 | List connectors = KISSet.INSTANCE.getInterfaceHandler().getInterfaces();
32 | List nodes = heard.listHeard();
33 | if (nodes.size() == 0) {
34 | write("*** No nearby nodes unheard yet" + CR);
35 | } else {
36 | write(ANSI.UNDERLINE + ANSI.BOLD + "Int Callsign Last UnHeard CanReach" + ANSI.NORMAL + CR);
37 |
38 | for (Node node : nodes) {
39 | String rssi = "-" + node.getRSSI() + " dBm";
40 | if (node.getRSSI() == Double.MAX_VALUE) {
41 | rssi = "- ";
42 | }
43 |
44 | write(StringUtils.rightPad(Integer.toString(connectors.indexOf(node.getInterface())), 5) + StringUtils.rightPad(node.getCallsign(), 10) + StringUtils.rightPad(sdf.format(node.getLastHeard()), 18) + " " + StringUtils.rightPad(canReach(node), 14) + CR);
45 | }
46 | }
47 | return true;
48 | }
49 |
50 |
51 | /**
52 | * Returns a nice string of callsigns that can reach this node.
53 | *
54 | * @param node
55 | * @return
56 | */
57 | public String canReach(Node node) {
58 | StringBuilder sb = new StringBuilder();
59 | for (Node n : node.getCanReachNodes()) {
60 | sb.append(n.getCallsign());
61 | sb.append(",");
62 | }
63 | if (sb.length() > 0) {
64 | sb.deleteCharAt(sb.length() - 1);
65 | }
66 | return sb.toString();
67 | }
68 |
69 | @Override
70 | public String[] getCommandNames() {
71 | return new String[]{"unheard", "uh"};
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/KISSetMain/src/main/java/org/prowl/kisset/io/Stream.java:
--------------------------------------------------------------------------------
1 | package org.prowl.kisset.io;
2 |
3 | import org.prowl.kisset.services.host.parser.ExtensionState;
4 |
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.io.OutputStream;
8 |
9 | public class Stream {
10 |
11 | private StreamState streamState;
12 |
13 | private volatile InputStream in;
14 |
15 | private volatile OutputStream out;
16 |
17 | private ExtensionState extensionState = ExtensionState.NONE;
18 |
19 | private String remoteCall;
20 |
21 | private final Interface anInterface;
22 |
23 | public Stream(Interface anInterface) {
24 | this.anInterface = anInterface;
25 | streamState = StreamState.DISCONNECTED;
26 | }
27 |
28 | public StreamState getStreamState() {
29 | return streamState;
30 | }
31 |
32 | public void setStreamState(StreamState streamState) {
33 | if (streamState.equals(StreamState.DISCONNECTED)) {
34 | remoteCall = null;
35 | extensionState = ExtensionState.NONE;
36 | }
37 | this.streamState = streamState;
38 | }
39 |
40 | public ExtensionState getExtensionState() {
41 | return extensionState;
42 | }
43 |
44 | public void setExtensionState(ExtensionState extensionState) {
45 | this.extensionState = extensionState;
46 | }
47 |
48 | public void setIOStreams(InputStream in, OutputStream out) {
49 | this.in = in;
50 | this.out = out;
51 | }
52 |
53 | public InputStream getInputStream() {
54 | return in;
55 | }
56 |
57 | public OutputStream getOutputStream() {
58 | return out;
59 | }
60 |
61 | // Convenience method to write to the stream
62 | public void write(byte[] data) throws IOException {
63 | out.write(data);
64 | }
65 |
66 | // Convenience method to flush the stream
67 | public void flush() throws IOException {
68 | out.flush();
69 | }
70 |
71 | // Send a disconnect frame and the wait for the ax.25 stack to disconnect
72 | public void disconnect() throws IOException {
73 | anInterface.disconnect(this);
74 | in.close();
75 | out.close();
76 | streamState = StreamState.DISCONNECTING;
77 | }
78 |
79 | // Disconnect immediately without sending a disconnect request frame.
80 | public void disconnectNow() throws IOException {
81 | in.close();
82 | out.close();
83 | streamState = StreamState.DISCONNECTED;
84 | extensionState = ExtensionState.NONE;
85 | }
86 |
87 | public String getRemoteCall() {
88 | return remoteCall;
89 | }
90 |
91 | public void setRemoteCall(String remoteCall) {
92 | this.remoteCall = remoteCall;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------