PhysicalConnectivity/src/main/kotlin/net/shadowfacts/phycon/network/DeviceBlockEntity.kt

236 lines
7.4 KiB
Kotlin

package net.shadowfacts.phycon.network
import net.fabricmc.fabric.api.block.entity.BlockEntityClientSerializable
import net.minecraft.block.BlockState
import net.minecraft.block.entity.BlockEntity
import net.minecraft.block.entity.BlockEntityType
import net.minecraft.nbt.CompoundTag
import net.minecraft.util.Tickable
import net.shadowfacts.phycon.api.PacketSink
import net.shadowfacts.phycon.api.PacketSource
import net.shadowfacts.phycon.api.Interface
import net.shadowfacts.phycon.api.NetworkComponentBlock
import net.shadowfacts.phycon.api.frame.EthernetFrame
import net.shadowfacts.phycon.api.frame.PacketFrame
import net.shadowfacts.phycon.api.packet.Packet
import net.shadowfacts.phycon.api.util.IPAddress
import net.shadowfacts.phycon.api.util.MACAddress
import net.shadowfacts.phycon.network.frame.ARPQueryFrame
import net.shadowfacts.phycon.network.frame.ARPResponseFrame
import net.shadowfacts.phycon.network.frame.BasePacketFrame
import net.shadowfacts.phycon.network.packet.DeviceRemovedPacket
import java.lang.RuntimeException
import java.util.*
/**
* @author shadowfacts
*/
abstract class DeviceBlockEntity(type: BlockEntityType<*>): BlockEntity(type),
BlockEntityClientSerializable,
Tickable,
PacketSink,
PacketSource,
InterfaceDelegate {
companion object {
private const val ARP_RETRY_TIMEOUT = 200
}
// var macAddress: MACAddress = MACAddress.random()
// protected set
var ipAddress: IPAddress = IPAddress.random()
protected set
open val interfaces: List<BaseInterface> = listOf()
// abstract val itf: BaseInterface
private val arpTable = mutableMapOf<IPAddress, MACAddress>()
private val packetQueue = LinkedList<PendingPacket>()
protected var counter: Long = 0
override fun getIPAddress() = ipAddress
override fun getDeviceInterfaces() = interfaces
abstract override fun handle(packet: Packet, itf: Interface)
override fun handle(frame: EthernetFrame, fromItf: Interface) {
println("$this ($ipAddress, ${fromItf.macAddress}) received frame from ${frame.source}: $frame")
when (frame) {
is ARPQueryFrame -> handleARPQuery(frame, fromItf)
is ARPResponseFrame -> handleARPResponse(frame, fromItf)
is PacketFrame -> {
if (frame.packet.destination.isBroadcast || frame.packet.destination == ipAddress) {
handle(frame.packet, fromItf)
}
}
}
}
private fun handleARPQuery(frame: ARPQueryFrame, fromItf: Interface) {
println("$this ($ipAddress) received ARP query for ${frame.queryIP}")
arpTable[frame.sourceIP] = frame.source
if (frame.queryIP == ipAddress) {
println("$this ($ipAddress) sending ARP response to ${frame.source} with ${fromItf.macAddress}")
fromItf.send(ARPResponseFrame(ipAddress, fromItf.macAddress, frame.source))
}
}
private fun handleARPResponse(frame: ARPResponseFrame, fromItf: Interface) {
arpTable[frame.query] = frame.source
println("$this ($ipAddress) received ARP response for ${frame.query} with ${frame.source}")
packetQueue.removeIf { (packet, itf, _) ->
if (packet.destination == frame.query) {
itf.send(BasePacketFrame(packet, itf.macAddress, frame.source))
true
} else {
false
}
}
}
// protected abstract fun handlePacket(packet: Packet, itf: Interface)
//
// protected open fun acceptsPacket(packet: Packet, itf: Interface): Boolean {
// return when (packet.destination.type) {
// MACAddress.Type.BROADCAST -> true
// MACAddress.Type.UNICAST -> itf.macAddress == packet.destination
// MACAddress.Type.MULTICAST -> acceptsMulticastPacket(packet)
// }
// }
// open fun acceptsMulticastPacket(packet: Packet): Boolean {
// return false
// }
fun sendPacket(packet: Packet) {
sendPacket(packet, null)
}
override fun sendPacket(packet: Packet, itf: Interface?) {
@Suppress("NAME_SHADOWING") var itf = itf
if (itf == null) {
if (interfaces.size == 1) {
itf = interfaces.first()
} else {
throw RuntimeException("Cannot send packet from device with multiple interfaces without explicitly specifying interface")
}
}
val cached = arpTable[packet.destination]
if (cached != null) {
itf.send(BasePacketFrame(packet, itf.macAddress, cached))
} else {
// packetQueue.add(packet to itf)
packetQueue.add(PendingPacket(packet, itf, counter))
println("$this ($ipAddress) sending ARP query for ${packet.destination}")
itf.send(ARPQueryFrame(packet.destination, ipAddress, itf.macAddress))
// after sending an ARP query we expect to have received a response and added an entry to our ARP table
// todo: this makes the assumption that packets are sent the entire way synchronously, and then a response
// is immediately sent and forwarded also synchronously
// cached = arpTable[packet.destination]
}
}
// fun findMACAddressFor(ipAddress: IPAddress): MACAddress? {
// if (arpTable.containsKey(ipAddress)) {
// return arpTable[ipAddress]
// } else {
//
// }
// }
// override fun sendToSingle(packet: Packet) {
// val destinations = NetworkUtil.findDestinations(world!!, pos)
// if (destinations.size != 1) {
// // todo: handle this better
// println("Can't send packet, multiple destinations available: $destinations")
// return
// }
// send(packet))
// }
// override fun sendToAll(packet: Packet) {
// sendToAll(packet, NetworkUtil.findDestinations(world!!, pos))
// }
//
// override fun sendToAll(packet: Packet, destinations: Iterable<PacketSink>) {
// destinations.forEach {
// it.handle(packet)
// }
// }
override fun findDestination(fromItf: Interface): Interface? {
val sides = (cachedState.block as NetworkComponentBlock).getNetworkConnectedSides(cachedState, world!!, pos)
if (sides.size != 1) {
throw RuntimeException("DeviceBlockEntity.findDestination must be overridden by devices which have more than 1 network connected side")
}
return NetworkUtil.findConnectedInterface(world!!, pos, sides.first())
}
override fun tick() {
if (!world!!.isClient) {
counter++
packetQueue.removeIf { entry ->
val (packet, itf, timestamp) = entry
if (arpTable.containsKey(packet.destination)) {
itf.send(BasePacketFrame(packet, itf.macAddress, arpTable[packet.destination]!!))
true
} else if (counter - timestamp >= ARP_RETRY_TIMEOUT) {
itf.send(ARPQueryFrame(packet.destination, ipAddress, itf.macAddress))
entry.timestamp = counter
// todo: should there be a retry counter?
true
} else {
false
}
}
}
}
override fun toTag(tag: CompoundTag): CompoundTag {
tag.putInt("IPAddress", ipAddress.address)
// tag.putLong("MACAddress", macAddress.address)
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return super.toTag(tag)
}
override fun fromTag(state: BlockState, tag: CompoundTag) {
super.fromTag(state, tag)
ipAddress = IPAddress(tag.getInt("IPAddress"))
// todo: what happens if the defined number of ports changes between mod versions?
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
}
override fun toClientTag(tag: CompoundTag): CompoundTag {
tag.putInt("IPAddress", ipAddress.address)
tag.putLongArray("InterfaceAddresses", interfaces.map { it.macAddress.address })
return tag
}
override fun fromClientTag(tag: CompoundTag) {
ipAddress = IPAddress(tag.getInt("IPAddress"))
tag.getLongArray("InterfaceAddresses")?.forEachIndexed { i, l ->
interfaces[i].macAddress = MACAddress(l)
}
}
fun onBreak() {
sendPacket(DeviceRemovedPacket(this))
}
data class PendingPacket(
val packet: Packet,
val sourceItf: Interface,
var timestamp: Long,
)
}