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.7KB

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