You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ServerState.kt 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. package com.dmdirc.ktirc.model
  2. import com.dmdirc.ktirc.SaslConfig
  3. import com.dmdirc.ktirc.events.EventMetadata
  4. import com.dmdirc.ktirc.events.IrcEvent
  5. import com.dmdirc.ktirc.io.CaseMapping
  6. import com.dmdirc.ktirc.util.logger
  7. import kotlinx.coroutines.channels.SendChannel
  8. import java.util.concurrent.atomic.AtomicLong
  9. import kotlin.reflect.KClass
  10. /**
  11. * Contains the current state of a single IRC server.
  12. */
  13. class ServerState internal constructor(
  14. private val initialNickname: String,
  15. private val initialServerName: String,
  16. saslConfig: SaslConfig? = null) {
  17. private val log by logger()
  18. /** Whether we've received the 'Welcome to IRC' (001) message. */
  19. internal var receivedWelcome = false
  20. /** The current status of the server. */
  21. var status = ServerStatus.Disconnected
  22. internal set
  23. /**
  24. * What we believe our current nickname to be on the server.
  25. *
  26. * Initially this will be the nickname provided in the config. It will be updated to the actual nickname
  27. * in use when connecting. Once you have received a [com.dmdirc.ktirc.events.ServerWelcome] event you can
  28. * rely on this value being current.
  29. * */
  30. var localNickname: String = initialNickname
  31. internal set
  32. /**
  33. * The name of the server we are connected to.
  34. *
  35. * Initially this will be the hostname or IP address provided in the config. It will be updated to the server's
  36. * self-reported hostname when connecting. Once you have received a [com.dmdirc.ktirc.events.ServerWelcome] event
  37. * you can rely on this value being current.
  38. */
  39. var serverName: String = initialServerName
  40. internal set
  41. /** The features that the server has declared it supports (from the 005 header). */
  42. val features = ServerFeatureMap()
  43. /** The capabilities we have negotiated with the server (from IRCv3). */
  44. val capabilities = CapabilitiesState()
  45. /** The current state of SASL authentication. */
  46. internal val sasl = SaslState(saslConfig)
  47. /**
  48. * Convenience accessor for the [ServerFeature.ModePrefixes] feature, which will always have a value.
  49. */
  50. val channelModePrefixes
  51. get() = features[ServerFeature.ModePrefixes] ?: throw IllegalStateException("lost mode prefixes")
  52. /**
  53. * Convenience accessor for the [ServerFeature.ChannelTypes] feature, which will always have a value.
  54. */
  55. val channelTypes
  56. get() = features[ServerFeature.ChannelTypes] ?: throw IllegalStateException("lost channel types")
  57. /**
  58. * Batches that are currently in progress.
  59. */
  60. internal val batches = mutableMapOf<String, Batch>()
  61. /**
  62. * Asynchronous command state.
  63. */
  64. internal val asyncResponseState = AsyncResponseState(capabilities)
  65. /**
  66. * Determines if the given mode is one applied to a user of a channel, such as 'o' for operator.
  67. */
  68. fun isChannelUserMode(mode: Char) = channelModePrefixes.isMode(mode)
  69. /**
  70. * Determines what type of channel mode the given character is, based on the server features.
  71. *
  72. * If the mode isn't found, or the server hasn't provided modes, it is presumed to be [ChannelModeType.NoParameter].
  73. */
  74. fun channelModeType(mode: Char): ChannelModeType {
  75. features[ServerFeature.ChannelModes]?.forEachIndexed { index, modes ->
  76. if (mode in modes) {
  77. return ChannelModeType.values()[index]
  78. }
  79. }
  80. log.warning { "Unknown channel mode $mode, assuming it's boolean" }
  81. return ChannelModeType.NoParameter
  82. }
  83. internal fun reset() {
  84. receivedWelcome = false
  85. status = ServerStatus.Disconnected
  86. localNickname = initialNickname
  87. serverName = initialServerName
  88. features.clear()
  89. capabilities.reset()
  90. sasl.reset()
  91. batches.clear()
  92. asyncResponseState.reset()
  93. }
  94. }
  95. internal class AsyncResponseState(private val capabilities : CapabilitiesState) {
  96. /**
  97. * Counter for ensuring sent labels are unique.
  98. */
  99. internal val labelCounter = AtomicLong(0)
  100. /**
  101. * Whether or not the server supports labeled responses.
  102. */
  103. internal val supportsLabeledResponses: Boolean
  104. get() = Capability.LabeledResponse in capabilities.enabledCapabilities
  105. /**
  106. * Channels waiting for a response to be received.
  107. */
  108. internal val pendingResponses = mutableMapOf<String, Pair<SendChannel<IrcEvent>, (IrcEvent) -> Boolean>>()
  109. internal fun reset() {
  110. labelCounter.set(0)
  111. pendingResponses.clear()
  112. }
  113. }
  114. /**
  115. * Maps known features onto their values, enforcing type safety.
  116. */
  117. class ServerFeatureMap {
  118. private val features = HashMap<ServerFeature<*>, Any?>()
  119. /**
  120. * Gets the value, or the default value, of the given feature.
  121. */
  122. @Suppress("UNCHECKED_CAST")
  123. operator fun <T : Any> get(feature: ServerFeature<T>) = features.getOrDefault(feature, feature.default) as? T?
  124. ?: feature.default
  125. internal operator fun set(feature: ServerFeature<*>, value: Any) {
  126. require(feature.type.isInstance(value)) {
  127. "Value given for feature ${feature::class} must be type ${feature.type} but was ${value::class}"
  128. }
  129. features[feature] = value
  130. }
  131. internal fun setAll(featureMap: ServerFeatureMap) = featureMap.features.forEach { feature, value -> features[feature] = value }
  132. internal fun reset(feature: ServerFeature<*>) = features.put(feature, null)
  133. internal fun clear() = features.clear()
  134. internal fun isEmpty() = features.isEmpty()
  135. }
  136. /**
  137. * Stores the mapping of mode prefixes received from the server.
  138. */
  139. data class ModePrefixMapping(val modes: String, val prefixes: String) {
  140. /** Determines whether the given character is a mode prefix (e.g. "@", "+"). */
  141. fun isPrefix(char: Char) = prefixes.contains(char)
  142. /** Determines whether the given character is a channel user mode (e.g. "o", "v"). */
  143. fun isMode(char: Char) = modes.contains(char)
  144. /** Gets the mode corresponding to the given prefix (e.g. "@" -> "o"). */
  145. fun getMode(prefix: Char) = modes[prefixes.indexOf(prefix)]
  146. /** Gets the modes corresponding to the given prefixes (e.g. "@+" -> "ov"). */
  147. fun getModes(prefixes: String) = String(prefixes.map(this::getMode).toCharArray())
  148. }
  149. /**
  150. * Describes a server feature determined from the 005 response.
  151. */
  152. sealed class ServerFeature<T : Any>(val name: String, val type: KClass<T>, val default: T? = null) {
  153. /** The network the server says it belongs to. */
  154. object Network : ServerFeature<String>("NETWORK", String::class)
  155. /** The case mapping the server uses, defaulting to RFC. */
  156. object ServerCaseMapping : ServerFeature<CaseMapping>("CASEMAPPING", CaseMapping::class, CaseMapping.Rfc)
  157. /** The mode prefixes the server uses, defaulting to ov/@+. */
  158. object ModePrefixes : ServerFeature<ModePrefixMapping>("PREFIX", ModePrefixMapping::class, ModePrefixMapping("ov", "@+"))
  159. /** The maximum number of channels a client may join. */
  160. object MaximumChannels : ServerFeature<Int>("MAXCHANNELS", Int::class) // TODO: CHANLIMIT also exists
  161. /** The modes supported in channels. */
  162. object ChannelModes : ServerFeature<Array<String>>("CHANMODES", Array<String>::class)
  163. /** The types of channels supported. */
  164. object ChannelTypes : ServerFeature<String>("CHANTYPES", String::class, "#&")
  165. /** The maximum length of a channel name, defaulting to 200. */
  166. object MaximumChannelNameLength : ServerFeature<Int>("CHANNELLEN", Int::class, 200)
  167. /** Whether or not the server supports extended who. */
  168. object WhoxSupport : ServerFeature<Boolean>("WHOX", Boolean::class, false)
  169. }
  170. /**
  171. * Enumeration of the possible states of a server.
  172. */
  173. enum class ServerStatus {
  174. /** The server is not connected. */
  175. Disconnected,
  176. /** We are attempting to connect to the server. It is not yet ready for use. */
  177. Connecting,
  178. /** We are logging in, dealing with capabilities, etc. The server is not yet ready for use. */
  179. Negotiating,
  180. /** We are connected and commands can be sent. */
  181. Ready,
  182. }
  183. /**
  184. * Represents an in-progress batch.
  185. */
  186. internal data class Batch(val type: String, val arguments: List<String>, val metadata: EventMetadata, val events: MutableList<IrcEvent> = mutableListOf())
  187. internal val serverFeatures: Map<String, ServerFeature<*>> by lazy {
  188. ServerFeature::class.nestedClasses.map { it.objectInstance as ServerFeature<*> }.associateBy { it.name }
  189. }