Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 66b08b7

Browse files
committed
Merge pull request #68 from strifel/master
Adding RCON Connector
2 parents 1ae8d78 + c509b13 commit 66b08b7

File tree

4 files changed

+193
-0
lines changed

4 files changed

+193
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<connector>
2+
<display>RCON</display>
3+
<description>The RCON service allows to send commands to a game server using the RCON protocol.</description>
4+
<wiki>https://github.com/codeoverflow-org/chatoverflow/wiki/RCON</wiki>
5+
<icon48>
6+
data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAASESURBVGhD7Zh/bBNlGMff964lTOcsIC5U0Czhx3BDpQwhNhsLRl2CbMRNolE30YD6h4NkiFnQRJLGgfKXMwLTbGEkSIwZDokRI8ucWIE5NiQq2yDCRrZp2rnKRoe9H95797Tr3b1v261l/HOfpLnnea5/PD/e9/veHbKwsLCwsLC4jWC4JoT4wwdPyle9HtnXnYvGAzPQ9UEebiVHxnwB3TFrHM9Z3IWz8rfx7q0dcCcuCRUgnjlwj/zb0VNy74klELp1cDzCS4tP46zCQt5deROiTOIWQJKXzuy/jAa7MiA0PWQVXONyShfGK4KDKxPp/OHOaU+e8GfbfPlyy4/gMYk5AfH7XZulk+/VgavheCCEc8tqcWbOET5vUztEk0I8W7dWHrrwgtx1+GUUHJ5oqrKcuKf2uPmCKi9ETMQsIFS3pp90AlyE5iwa5/KrFvCrXvNBJKWI3tplUounC439HSkCLy1pt5V/9Si4JmIvoYHz94GlwrnKXzcmH6rGMvmBGxexxVMRqs37J7TPPURUDcIq/GNvXsAPP/cpuCqq4sWAWYDYXv8guhmYmFDabIlf+85B8KaMdK5xPxrocKA+b6b0e3MjhCNgp+t9MDX8vWlgUWFP4L/R1WBpOO4fBSs5QkEbWIp9w5Qcv6KiD915rwSuUrGIxF8aVoJnglmA7O91g6mC0zMHwUwKvPzFXerBRfZTbmklhPVkOPXNGg+4wDLB3MRC44az8h/Nkcrxik3HbGX1JYmud3uNHPeMYSHUF12MPjTxqjcO2TZ8Ug6uDvYEgv4J9VHAc7N/BvPWk+HsBksjcI25kdl74Ppfs8DSSHN0kgvpbPRPvafAiseDpkrGZsk3/E4wTbALCPTNBEtjpiMlh5YRqipBsyKMDumbGQW1AFVChahHEEUV+Ic2DoOXWmiqZGzWyNUZYJmgT8AooUZVSCE0VVKblaCUUguYjIROds0bsRXVeOzV/Xb79p403UGZoJTSJzDSvwwsDaMqTAPGpsm+Hl1Tw1A7FzqQP4CunJoHLuKK9lTza3bsBjcCURCpdXcDCo1NbQLKMuEKtj/OF7zVCpEIwpevNMsdDcXgIpy9vtNWccw0BfoEGBIaTdLJE5SnTqlt70mx7cNCiERIVErpBcSR0JQkH4ZVRIJSaiognoSmNPkwtCISlFJTEuJPH5VLx7dOqMG8R/61V3beTUxW8jjv1SZb6Wel4CaEcLD4nHzx6+Xgahj2RMiTKUa/3HDPNi7iXS9dAlfFvIR42xhYGiN96aL348XCd+9uoya/cvORySZPIBuSbExwNcKTaK0pVacR9OvyMyZPME/g1y9mS01bfLqXGQZq8s/UPQ/ulKBOgobyLm5/+4ppGZkmQNY7zin5FlwmqUieQJ0EBfIhAUwd5iWkgJes24gWrKa/uJMPT+T5PAXJh1GLUF7ewTWBFz5xybZubxW4OmIuE6Fpy+dy/+mnka8nHUkCQk7XMJdbVqEcasfhLylFOLFzh9z9zU7k671L3Wtzs4NKM/exkrewsLCwsLC4vSD0P+HX+0oudAkiAAAAAElFTkSuQmCC
7+
</icon48>
8+
</connector>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package org.codeoverflow.chatoverflow.requirement.service.rcon
2+
3+
import java.io.{DataInputStream, IOException, InputStream, OutputStream}
4+
import java.net.{Socket, SocketException}
5+
import java.nio.{ByteBuffer, ByteOrder}
6+
import java.util.Random
7+
8+
import org.codeoverflow.chatoverflow.WithLogger
9+
import org.codeoverflow.chatoverflow.connector.Connector
10+
11+
class RconConnector(override val sourceIdentifier: String) extends Connector(sourceIdentifier) with WithLogger {
12+
override protected var requiredCredentialKeys: List[String] = List("password", "address")
13+
override protected var optionalCredentialKeys: List[String] = List("port")
14+
15+
private var socket: Socket = _
16+
private var outputStream: OutputStream = _
17+
private var inputStream: InputStream = _
18+
private var requestId: Int = 0
19+
20+
def sendCommand(command: String): String = {
21+
logger debug s"Sending $command to RCON"
22+
requestId += 1
23+
if (write(2, command.getBytes("ASCII"))) {
24+
return read()
25+
}
26+
null
27+
}
28+
29+
30+
/**
31+
* Starts the connector, e.g. creates a connection with its platform.
32+
*/
33+
override def start(): Boolean = {
34+
logger info s"Starting rcon connection to ${credentials.get.getValue("address").get}"
35+
var port: Int = 25575
36+
if (credentials.get.exists("port")) {
37+
try{
38+
port = credentials.get.getValue("port").get.toInt
39+
} catch {
40+
case e: NumberFormatException => {
41+
logger error "Please enter a valid port"
42+
return false
43+
}
44+
}
45+
if (port < 1 || port > 65535) {
46+
logger error "Please enter a valid port"
47+
return false
48+
}
49+
}
50+
try {
51+
socket = new Socket(credentials.get.getValue("address").get, port)
52+
socket.setKeepAlive(true)
53+
outputStream = socket.getOutputStream
54+
inputStream = socket.getInputStream
55+
} catch {
56+
case e: IOException => {
57+
logger error "No Connection to RCON Server. Is it up?"
58+
return false
59+
}
60+
}
61+
val loggedIn = login()
62+
// Sleeping here to allow the (minecraft) server to start its own rcon procedure. Otherwise it caused errors in my tests.
63+
Thread.sleep(5000)
64+
loggedIn
65+
}
66+
67+
private def login(): Boolean = {
68+
requestId = new Random().nextInt(Integer.MAX_VALUE)
69+
logger info "Logging RCON in..."
70+
val password = credentials.get.getValue("password").get
71+
if (write(3, password.getBytes("ASCII"))) {
72+
if (read() == null) {
73+
logger error "Could not log in to RCON Server. Password is Wrong!"
74+
return false
75+
} else {
76+
logger debug "Login to RCON was successful"
77+
return true
78+
}
79+
}
80+
false
81+
}
82+
83+
private def write(packageType: Int, payload: Array[Byte]): Boolean = {
84+
try {
85+
val length = 4 + 4 + payload.length + 1 + 1
86+
var byteBuffer: ByteBuffer = ByteBuffer.allocate(length + 4)
87+
byteBuffer.order(ByteOrder.LITTLE_ENDIAN)
88+
89+
byteBuffer.putInt(length)
90+
byteBuffer.putInt(requestId)
91+
byteBuffer.putInt(packageType)
92+
byteBuffer.put(payload)
93+
byteBuffer.put(0x00.toByte)
94+
byteBuffer.put(0x00.toByte)
95+
96+
outputStream.write(byteBuffer.array())
97+
outputStream.flush()
98+
} catch {
99+
case e: SocketException => {
100+
logger error "Connection Error to RCON Server. This request will not be sended!"
101+
return false
102+
}
103+
}
104+
true
105+
}
106+
107+
private def read(): String = {
108+
try {
109+
val header: Array[Byte] = Array.ofDim[Byte](4*3)
110+
inputStream.read(header)
111+
val headerBuffer: ByteBuffer = ByteBuffer.wrap(header)
112+
headerBuffer.order(ByteOrder.LITTLE_ENDIAN)
113+
val length = headerBuffer.getInt()
114+
val packageType = headerBuffer.getInt
115+
val payload: Array[Byte] = Array.ofDim[Byte](length - 4 - 4 - 2)
116+
val dataInputStream: DataInputStream = new DataInputStream(inputStream)
117+
dataInputStream.readFully(payload)
118+
dataInputStream.read(Array.ofDim[Byte](2))
119+
if (packageType == -1) {
120+
return null
121+
}
122+
new String(payload, "ASCII")
123+
} catch {
124+
case e: NegativeArraySizeException => null;
125+
}
126+
}
127+
128+
/**
129+
* This stops the activity of the connector, e.g. by closing the platform connection.
130+
*/
131+
override def stop(): Boolean = {
132+
logger info s"Stopped RCON connector to ${credentials.get.getValue("address").get}!"
133+
socket.close()
134+
true
135+
}
136+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package org.codeoverflow.chatoverflow.requirement.service.rcon.impl
2+
3+
import org.codeoverflow.chatoverflow.WithLogger
4+
import org.codeoverflow.chatoverflow.api.io.input.RconInput
5+
import org.codeoverflow.chatoverflow.registry.Impl
6+
import org.codeoverflow.chatoverflow.requirement.impl.InputImpl
7+
import org.codeoverflow.chatoverflow.requirement.service.rcon.RconConnector
8+
9+
@Impl(impl = classOf[RconInput], connector = classOf[RconConnector])
10+
class RconInputImpl extends InputImpl[RconConnector] with RconInput with WithLogger {
11+
override def getCommandOutput(command: String): String = sourceConnector.get.sendCommand(command)
12+
13+
/**
14+
* Start the input, called after source connector did init
15+
*
16+
* @return true if starting the input was successful, false if some problems occurred
17+
*/
18+
override def start(): Boolean = true
19+
20+
override def stop(): Boolean = true
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.codeoverflow.chatoverflow.requirement.service.rcon.impl
2+
3+
import org.codeoverflow.chatoverflow.WithLogger
4+
import org.codeoverflow.chatoverflow.api.io.output.RconOutput
5+
import org.codeoverflow.chatoverflow.registry.Impl
6+
import org.codeoverflow.chatoverflow.requirement.impl.OutputImpl
7+
import org.codeoverflow.chatoverflow.requirement.service.rcon.RconConnector
8+
9+
@Impl(impl = classOf[RconOutput], connector = classOf[RconConnector])
10+
class RconOutputImpl extends OutputImpl[RconConnector] with RconOutput with WithLogger {
11+
override def sendCommand(command: String): Boolean = {
12+
sourceConnector.get.sendCommand(command) != null
13+
}
14+
15+
/**
16+
* Start the input, called after source connector did init
17+
*
18+
* @return true if starting the input was successful, false if some problems occurred
19+
*/
20+
override def start(): Boolean = true
21+
22+
/**
23+
* Stops the output, called before source connector will shutdown
24+
*
25+
* @return true if stopping was successful
26+
*/
27+
override def stop(): Boolean = true
28+
}

0 commit comments

Comments
 (0)