Skip to content

Commit

Permalink
add networkmanager to be able to introduce automatic ip assignments i…
Browse files Browse the repository at this point in the history
…n the future, cleans up additional vam handling
  • Loading branch information
froks committed Sep 25, 2024
1 parent 3b26b9c commit 9bb6223
Show file tree
Hide file tree
Showing 20 changed files with 797 additions and 524 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ plugins {
apply<NexusReleasePlugin>()

group = "io.github.doip-sim-ecu"
version = "0.13.0"
version = "0.14.0"

repositories {
gradlePluginPortal()
Expand Down
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
351 changes: 351 additions & 0 deletions src/main/kotlin/NetworkHandler.kt

Large diffs are not rendered by default.

127 changes: 127 additions & 0 deletions src/main/kotlin/NetworkManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import library.DoipEntity
import org.slf4j.LoggerFactory
import java.net.Inet4Address
import java.net.InetAddress
import java.net.NetworkInterface

public open class NetworkManager(
public val config: NetworkingData,
public val doipEntities: List<DoipEntity<*>>,
) {
private val log = LoggerFactory.getLogger(NetworkManager::class.java)

protected fun findInterfaceByName(): NetworkInterface? {
var foundInterface: NetworkInterface? = null
NetworkInterface.getNetworkInterfaces()?.let { netIntf ->
while (netIntf.hasMoreElements()) {
val entry = netIntf.nextElement()
if (entry.displayName != null && entry.displayName.equals(config.networkInterface, true)) {
foundInterface = entry
break
}
entry.subInterfaces?.let { subInterfaces ->
while (subInterfaces.hasMoreElements()) {
val subInterface = subInterfaces.nextElement()
if (subInterface.displayName != null && subInterface.displayName.equals(
config.networkInterface,
true
)
) {
foundInterface = entry;
break
}
}
}
if (foundInterface != null) {
break
}
}
}

return foundInterface
}

protected fun getAvailableIPAddresses(): List<InetAddress> {
if (config.networkInterface.isNullOrBlank() || config.networkInterface == "0.0.0.0") {
return listOf(InetAddress.getByName(config.networkInterface))
}
val list = mutableListOf<InetAddress>()
findInterfaceByName()?.let { intf ->
intf.inetAddresses?.let { inetAddresses ->
while (inetAddresses.hasMoreElements()) {
val address = inetAddresses.nextElement()
if (address is Inet4Address) {
list.add(address)
}
}
}
}
if (list.isEmpty()) {
InetAddress.getByName(config.networkInterface)?.let { addr ->
list.add(addr)
}
}
return list
}

protected open fun buildStartupMap(): Map<String, List<DoipEntity<*>>> {
val ipAddresses = getAvailableIPAddresses().toMutableList()
if (ipAddresses.isEmpty()) {
throw IllegalArgumentException("No network interface with the identifier ${config.networkInterface} could be found")
}
log.info("There are ${ipAddresses.size} ip address available, and we have ${doipEntities.size} doip entities")
val entitiesByIP = mutableMapOf<String, MutableList<DoipEntity<*>>>()
doipEntities.forEach { entity ->
val ip = if (ipAddresses.size == 1) {
ipAddresses.first()
} else {
ipAddresses.removeFirst()
}
log.info("Assigning entity ${entity.name} to $ip")
var entityList = entitiesByIP[ip.hostAddress]
if (entityList == null) {
entityList = mutableListOf()
entitiesByIP[ip.hostAddress] = entityList
}
entityList.add(entity)
}
return entitiesByIP
}

public fun start() {
val map = buildStartupMap()

// UDP
map.forEach { (address, entities) ->
val unb = createUdpNetworkBinding(address, entities)
unb.start()
}

if (config.bindOnAnyForUdpAdditional && !map.containsKey("0.0.0.0")) {
val unb = createUdpNetworkBindingAny()
unb.start()
}

// TCP
map.forEach { (address, entities) ->
val tnb = createTcpNetworkBinding(address, entities)
tnb.start()
}
}

protected open fun createTcpNetworkBinding(
address: String,
entities: List<DoipEntity<*>>
): TcpNetworkBinding =
TcpNetworkBinding(this, address, config.localPort, config.tlsOptions, entities)

protected open fun createUdpNetworkBinding(
address: String,
entities: List<DoipEntity<*>>
): UdpNetworkBinding =
UdpNetworkBinding(address, config.localPort, config.broadcastEnable, config.broadcastAddress, entities)

protected open fun createUdpNetworkBindingAny(): UdpNetworkBinding =
UdpNetworkBinding("0.0.0.0", config.localPort, config.broadcastEnable, config.broadcastAddress, doipEntities)
}

86 changes: 9 additions & 77 deletions src/main/kotlin/SimGateway.kt → src/main/kotlin/SimDoipEntity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,13 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.slf4j.MDCContext
import library.*
import org.slf4j.MDC
import kotlin.properties.Delegates
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds

@Suppress("unused")
public open class GatewayData(name: String) : RequestsData(name) {
/**
* Network address this gateway should bind on (default: 0.0.0.0)
*/
public var localAddress: String = "0.0.0.0"

/**
* Should udp be bound additionally on any?
* There's an issue when binding it to an network interface of not receiving 255.255.255.255 broadcasts
*/
public var bindOnAnyForUdpAdditional: Boolean = true

/**
* Network port this gateway should bind on (default: 13400)
*/
public var localPort: Int = 13400

/**
* Multicast address
*/
public var multicastAddress: String? = null

/**
* Whether VAM broadcasts shall be sent on startup (default: true)
*/
public var broadcastEnable: Boolean = true

/**
* Default broadcast address for VAM messages (default: 255.255.255.255)
*/
public var broadcastAddress: String = "255.255.255.255"

/**
* The logical address under which the gateway shall be reachable
*/
public var logicalAddress: Short by Delegates.notNull()

/**
* The functional address under which the gateway (and other ecus) shall be reachable
*/
public var functionalAddress: Short by Delegates.notNull()

public open class DoipEntityData(name: String, public val nodeType: DoipNodeType = DoipNodeType.GATEWAY) : EcuData(name) {
/**
* Vehicle identifier, 17 chars, will be filled with '0`, or if left null, set to 0xFF
*/
public var vin: String? = null // 17 byte VIN
public var vin: String? = null // 17 byte VIN

/**
* Group ID of the gateway
Expand All @@ -65,22 +21,12 @@ public open class GatewayData(name: String) : RequestsData(name) {
*/
public var eid: ByteArray = byteArrayOf(0, 0, 0, 0, 0, 0) // 6 byte entity identification (usually MAC)

/**
* Interval between sending pending NRC messages (0x78)
*/
public var pendingNrcSendInterval: Duration = 2.seconds

/**
* Maximum payload data size allowed for a DoIP message
*/
public var maxDataSize: Int = Int.MAX_VALUE

public var tlsMode: TlsMode = TlsMode.DISABLED
public var tlsPort: Int = 3496
public var tlsOptions: TlsOptions = TlsOptions()

private val _ecus: MutableList<EcuData> = mutableListOf()
private val _additionalVams: MutableList<DoipUdpVehicleAnnouncementMessage> = mutableListOf()

public val ecus: List<EcuData>
get() = this._ecus.toList()
Expand All @@ -93,33 +39,19 @@ public open class GatewayData(name: String) : RequestsData(name) {
receiver.invoke(ecuData)
_ecus.add(ecuData)
}

public fun doipEntity(name: String, vam: DoipUdpVehicleAnnouncementMessage, receiver: EcuData.() -> Unit) {
val ecuData = EcuData(name)
receiver.invoke(ecuData)
_ecus.add(ecuData)
_additionalVams.add(vam)
}
}

private fun GatewayData.toGatewayConfig(): DoipEntityConfig {
private fun DoipEntityData.toDoipEntityConfig(): DoipEntityConfig {
val config = DoipEntityConfig(
name = this.name,
gid = this.gid,
eid = this.eid,
localAddress = this.localAddress,
bindOnAnyForUdpAdditional = this.bindOnAnyForUdpAdditional,
localPort = this.localPort,
logicalAddress = this.logicalAddress,
broadcastEnabled = this.broadcastEnable,
broadcastAddress = this.broadcastAddress,
pendingNrcSendInterval = this.pendingNrcSendInterval,
tlsMode = this.tlsMode,
tlsPort = this.tlsPort,
tlsOptions = this.tlsOptions,
// Fill up too short vin's with 'Z' - if no vin is given, use 0xFF, as defined in ISO 13400 for when no vin is set (yet)
vin = this.vin?.padEnd(17, 'Z')?.toByteArray() ?: ByteArray(17).let { it.fill(0xFF.toByte()); it },
vin = this.vin?.padEnd(17, '0')?.toByteArray() ?: ByteArray(17).let { it.fill(0xFF.toByte()); it },
maxDataSize = this.maxDataSize,
nodeType = nodeType,
)

// Add the gateway itself as an ecu, so it too can receive requests
Expand All @@ -137,13 +69,13 @@ private fun GatewayData.toGatewayConfig(): DoipEntityConfig {
}

@Suppress("MemberVisibilityCanBePrivate")
public class SimGateway(private val data: GatewayData) : DoipEntity<SimEcu>(data.toGatewayConfig()) {
public open class SimDoipEntity(private val data: DoipEntityData) : DoipEntity<SimEcu>(data.toDoipEntityConfig()) {
public val requests: RequestList
get() = data.requests

override fun createEcu(config: EcuConfig): SimEcu {
// To be able to handle requests for the gateway itself, insert a dummy ecu with the gateways logicalAddress
if (config.name == data.name) {
// To be able to handle requests for the gateway itself, insert a dummy ecu with the gateways logicalAddress
val ecu = EcuData(
name = data.name,
logicalAddress = data.logicalAddress,
Expand All @@ -163,12 +95,12 @@ public class SimGateway(private val data: GatewayData) : DoipEntity<SimEcu>(data
return SimEcu(ecuData)
}

public fun reset(recursiveEcus: Boolean = true) {
public override fun reset(recursiveEcus: Boolean) {
runBlocking {
MDC.put("ecu", name)

launch(MDCContext()) {
logger.infoIf { "Resetting gateway" }
logger.infoIf { "Resetting doip entity" }
requests.forEach { it.reset() }
if (recursiveEcus) {
ecus.forEach { it.reset() }
Expand Down
47 changes: 28 additions & 19 deletions src/main/kotlin/SimDsl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ public typealias InterceptorRequestData = ResponseData<RequestInterceptorData>
public typealias InterceptorRequestHandler = InterceptorRequestData.(request: RequestMessage) -> Boolean
public typealias InterceptorResponseHandler = InterceptorResponseData.(response: ByteArray) -> Boolean
public typealias EcuDataHandler = EcuData.() -> Unit
public typealias GatewayDataHandler = GatewayData.() -> Unit
public typealias DoipEntityDataHandler = DoipEntityData.() -> Unit
public typealias NetworkingDataHandler = NetworkingData.() -> Unit
public typealias CreateEcuFunc = (name: String, receiver: EcuDataHandler) -> Unit
public typealias CreateGatewayFunc = (name: String, receiver: GatewayDataHandler) -> Unit
public typealias CreateDoipEntityFunc = (name: String, receiver: DoipEntityDataHandler) -> Unit
public typealias CreateNetworkFunc = (receiver: NetworkingDataHandler) -> Unit

@Suppress("unused")
public class InterceptorResponseData(
Expand Down Expand Up @@ -520,10 +522,18 @@ public open class RequestsData(
*/
public open class EcuData(
name: String,
/**
* The logical address under which the gateway shall be reachable
*/
public var logicalAddress: Short = 0,
/**
* The functional address under which the gateway (and other ecus) shall be reachable
*/
public var functionalAddress: Short = 0,
/**
* Interval between sending pending NRC messages (0x78)
*/
public var pendingNrcSendInterval: Duration = 2.seconds,
public var additionalVam: EcuAdditionalVamData? = null,
nrcOnNoMatch: Boolean = true,
requests: List<RequestMatcher> = emptyList(),
resetHandler: List<ResetHandler> = emptyList(),
Expand All @@ -536,33 +546,32 @@ public open class EcuData(
ackBytesLengthMap = ackBytesLengthMap,
)

internal val gateways: MutableList<GatewayData> = mutableListOf()
internal val gatewayInstances: MutableList<SimGateway> = mutableListOf()
internal val networks: MutableList<NetworkingData> = mutableListOf()
internal val networkInstances: MutableList<SimDoipNetworking> = mutableListOf()

public fun gatewayInstances(): List<SimGateway> =
gatewayInstances.toList()
public fun networks(): List<NetworkingData> =
networks.toList()

public fun gateways(): List<GatewayData> =
gateways.toList()
public fun networkInstances(): List<SimDoipNetworking> =
networkInstances.toList()

/**
* Defines a DoIP-Gateway and the ECUs behind it
*/
public fun gateway(name: String, receiver: GatewayDataHandler) {
val gatewayData = GatewayData(name)
receiver.invoke(gatewayData)
gateways.add(gatewayData)
public fun network(receiver: NetworkingDataHandler) {
val networkingData = NetworkingData()
receiver.invoke(networkingData)
networks.add(networkingData)
}

public fun reset() {
gatewayInstances.forEach { it.reset() }
networkInstances.forEach { it.reset() }
}

@Suppress("unused")
public fun start() {
gatewayInstances.addAll(gateways.map { SimGateway(it) })
networkInstances.addAll(networks.map { SimDoipNetworking(it) })

val networkManager = networkInstances.map { NetworkManager(it.data, it.doipEntities) }

gatewayInstances.forEach {
networkManager.forEach {
it.start()
}
}
Loading

0 comments on commit 9bb6223

Please sign in to comment.