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 = listOf() // abstract val itf: BaseInterface private val arpTable = mutableMapOf() private val packetQueue = LinkedList() 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) { // 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, ) }