Initial commit

This commit is contained in:
2025-09-29 01:43:17 +08:00
commit 18c0e54926
15 files changed
+750

No files matched your search

@@ -0,0 +1,43 @@
/*
* Copyright © 2025 RTAkland
* Author: RTAkland
* Date: 9/29/25
*/
package cn.rtast.nmdns
private val macAddresses = listOf(
"ef:42:0d:76:f1:97",
"b4:91:7c:be:84:7b",
"38:d0:d3:f5:63:d1",
"90:d0:d5:3e:5b:fa",
"4d:84:d4:dd:7e:b4",
"11:1c:fb:06:6a:4a",
"cf:97:be:b3:18:5b",
"0c:34:bf:d8:55:71",
"95:a0:71:10:31:ab",
"4c:33:b8:4a:60:8e",
"fc:89:a5:f2:b6:3c",
"a7:42:99:34:b5:21",
"9a:55:3f:5d:bb:9d",
"2b:35:ce:77:a0:7c",
"7d:51:21:72:c5:b4",
"79:7d:cc:e1:30:9b",
"aa:a2:cf:b5:e3:3c",
"8d:73:3f:8e:ef:d7",
"21:3c:a4:56:80:c5",
"c1:fa:ca:cb:ec:f3",
"51:76:e2:12:2f:af",
"a8:03:22:2d:b9:80",
"2a:84:3b:ad:9c:d9",
"24:79:c3:ab:0f:b5",
"bc:44:e3:c4:9e:be",
"cb:1b:84:c2:94:ba",
"9d:66:ce:d2:6e:4f",
"43:bf:e2:b2:e3:78",
"48:25:03:bb:9d:35",
"c3:c9:1c:3d:ae:dd"
)
fun randomMacAddress(): String = macAddresses.random()
@@ -0,0 +1,163 @@
/*
* Copyright © 2025 RTAkland
* Author: RTAkland
* Date: 9/28/25
*/
package cn.rtast.nmdns
import io.ktor.network.selector.*
import io.ktor.network.sockets.*
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.io.Buffer
data class NMDNSServer(
/**
* coroutine dispatcher for running server
*/
val dispatcher: CoroutineDispatcher = Dispatchers.Default,
/**
* service type
* default airplay
*/
val serviceType: String = "_airplay._tcp.local.",
/**
* service name
* finally: $serviceName.$serviceType
*/
val serviceName: String = "APS",
/**
* local hostname
*/
val hostname: String,
/**
* service ip, lan ip mostly
*/
val ipAddress: String,
/**
* service port
*/
val port: Int = 7004,
/**
* mdns server port
*/
val mdnsPort: Int = 3025,
/**
* Txt record content
*/
val txtRecords: List<String> = listOf(
"deviceid=01:23:45:67:89:AB",
"model=AppleTV3,2C",
"features=0x5A7FFFF7,0x1E",
"srcvers=220.68",
"pk=f3769a660475d27b4f6040381d784645e13e21c53e6d2da6a8c3d757086fc336"
),
/**
* mdns socket server
*/
val server: BoundDatagramSocket,
/**
* srv packet
*/
val packet: Buffer,
)
suspend fun createService(
serviceType: String,
serviceName: String,
hostname: String,
ipAddress: String,
port: Int,
mdnsPort: Int = 9872,
bindAddress: String,
txtRecords: List<String> = listOf(
"deviceid=${randomMacAddress()}",
"model=AppleTV3,2C",
"features=0x5A7FFFF7,0x1E",
"srcvers=220.68",
"pk=f3769a660475d27b4f6040381d784645e13e21c53e6d2da6a8c3d757086fc336"
),
): NMDNSServer {
val manager = SelectorManager(Dispatchers.Default)
val serverSocket = aSocket(manager).udp()
.bind(InetSocketAddress(bindAddress, mdnsPort))
return NMDNSServer(
Dispatchers.Default, serviceType, serviceName,
hostname, ipAddress, port, mdnsPort,
txtRecords, serverSocket, buildPacket(serviceType, "$serviceName.$serviceType", hostname, ipAddress, port, txtRecords)
)
}
suspend fun NMDNSServer.broadcast() {
this.server.send(Datagram(packet.copy(), InetSocketAddress("224.0.0.251", 5353)))
}
internal fun buildPacket(
serviceType: String,
serviceName: String,
hostname: String,
ip: String,
port: Int,
txtRecords: List<String>,
): Buffer {
val records = mutableListOf<ByteArray>()
records += encodeRR(serviceType, 12, encodeName(serviceName))
val srvData = ByteArray(6) { 0 }
srvData[4] = (port shr 8).toByte()
srvData[5] = (port and 0xFF).toByte()
records += encodeRR(serviceName, 33, srvData + encodeName(hostname))
val ipParts = ip.split(".").map { it.toInt().toByte() }
records += encodeRR(hostname, 1, ipParts.toByteArray())
if (txtRecords.isNotEmpty()) {
val txtData = encodeTxt(txtRecords)
records += encodeRR(serviceName, 16, txtData)
}
val header = ByteArray(12)
header[2] = 0x84.toByte()
header[3] = 0x00
header[6] = ((records.size shr 8) and 0xFF).toByte()
header[7] = (records.size and 0xFF).toByte()
val buffer = Buffer().apply { write(header + records.reduce { acc, r -> acc + r }) }
return buffer
}
internal fun encodeRR(name: String, type: Int, data: ByteArray): ByteArray {
val nameBytes = encodeName(name)
val rr = ByteArray(10)
rr[0] = 0x00; rr[1] = type.toByte()
rr[2] = 0x00; rr[3] = 0x01
rr[4] = 0x00; rr[5] = 0x00; rr[6] = 0x00; rr[7] = 0x78
rr[8] = (data.size shr 8).toByte()
rr[9] = (data.size and 0xFF).toByte()
return nameBytes + rr + data
}
internal fun encodeName(name: String): ByteArray {
val parts = name.trimEnd('.').split(".")
val bytes = mutableListOf<Byte>()
for (p in parts) {
bytes += p.length.toByte()
bytes += p.encodeToByteArray().toList()
}
bytes += 0
return bytes.toByteArray()
}
internal fun encodeTxt(records: List<String>): ByteArray {
val bytes = mutableListOf<Byte>()
for (txt in records) {
val txtBytes = txt.encodeToByteArray()
if (txtBytes.size > 255) continue
bytes += txtBytes.size.toByte()
bytes += txtBytes.toList()
}
return bytes.toByteArray()
}
@@ -0,0 +1,10 @@
/*
* Copyright © 2025 RTAkland
* Author: RTAkland
* Date: 9/29/25
*/
package cn.rtast.nmdns
internal expect fun sleep1(time: Int)