
commonMain.com.ditchoom.websocket.Frame.kt Maven / Gradle / Ivy
package com.ditchoom.websocket
import com.ditchoom.buffer.AllocationZone
import com.ditchoom.buffer.PlatformBuffer
import com.ditchoom.buffer.ReadBuffer
import com.ditchoom.buffer.TransformedReadBuffer
import com.ditchoom.buffer.WriteBuffer
import com.ditchoom.buffer.allocate
import com.ditchoom.data.get
import com.ditchoom.data.toByte
import com.ditchoom.socket.EMPTY_BUFFER
import kotlin.experimental.xor
/**
*
* 0 1 2 3
* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
* +-+-+-+-+-------+-+-------------+-------------------------------+
* |F|R|R|R| opcode|M| Payload len | Extended payload length |
* |I|S|S|S| (4) |A| (7) | (16/64) |
* |N|V|V|V| |S| | (if payload len==126/127) |
* | |1|2|3| |K| | |
* +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
* | Extended payload length continued, if payload len == 127 |
* + - - - - - - - - - - - - - - - +-------------------------------+
* | |Masking-key, if MASK set to 1 |
* +-------------------------------+-------------------------------+
* | Masking-key (continued) | Payload Data |
* +-------------------------------- - - - - - - - - - - - - - - - +
* : Payload Data continued ... :
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | Payload Data continued ... |
* +---------------------------------------------------------------+
*
*/
internal data class Frame(
/**
* Indicates that this is the final fragment in a message. The first fragment MAY also be the final fragment.
*/
val fin: Boolean,
/**
* MUST be false unless an extension is negotiated that defines meanings for non-zero values. If a nonzero value is
* received and none of the negotiated extensions defines the meaning of such a nonzero value, the receiving
* endpoint MUST _Fail the WebSocket Connection_.
*/
val rsv1: Boolean,
val rsv2: Boolean,
val rsv3: Boolean,
/** Defines the interpretation of the "Payload data". If an unknown opcode is received, the receiving endpoint
* MUST _Fail the WebSocket Connection_.
*/
val opcode: Opcode,
/**
* All frames sent from the client to the server are masked by a 32-bit value that is contained within the frame.
* This field is present if the mask bit is set to true and is absent if the mask bit is set to false. See Section
* 5.3 for further information on client- to-server masking.
*/
val maskingKey: MaskingKey,
val payloadData: ReadBuffer,
/**
* The length of the "Payload data", in bytes: if 0-125, that is the payload length. If 126, the following 2 bytes
* interpreted as a 16-bit unsigned integer are the payload length. If 127, the following 8 bytes interpreted as a
* 64-bit unsigned integer (the most significant bit MUST be 0) are the payload length. Multibyte length quantities
* are expressed in network byte order. Note that in all cases, the minimal number of bytes MUST be used to encode
* the length, for example, the length of a 124-byte-long string can't be encoded as the sequence 126, 0, 124. The
* payload length is the length of the "Extension data" + the length of the "Application data". The length of the
* "Extension data" may be zero, in which case the payload length is the length of the "Application data".
*/
private val payloadLength: Int = if (payloadData.limit() <= 125) {
payloadData.limit()
} else if (payloadData.limit() <= Int.MAX_VALUE) {
126
} else {
127
}.also { check(it in 0..127) }
) {
constructor(fin: Boolean, opcode: Opcode, maskingKey: MaskingKey, payloadData: ReadBuffer) :
this(fin, false, false, false, opcode, maskingKey, payloadData)
constructor(opcode: Opcode, payloadData: ReadBuffer = EMPTY_BUFFER) : this(
false,
opcode,
MaskingKey.NoMaskingKey,
payloadData
)
fun toBuffer(): ReadBuffer {
val buffer = PlatformBuffer.allocate(size(), AllocationZone.Direct)
serialize(buffer)
return buffer
}
private val actualPayloadLength = if (payloadLength <= 125) {
payloadLength
} else if (payloadLength == 126) {
payloadLength + UShort.SIZE_BYTES
} else if (payloadLength == 127) {
payloadLength + ULong.SIZE_BYTES
} else {
throw IllegalStateException("Internal payload len size cannot be larger than 127")
}
fun size(): Int {
// the first byte includes fin, rsv1-3, and opcode. second byte includes mask and payload len
val ws2ByteOverhead = 2
return actualPayloadLength + ws2ByteOverhead + when (maskingKey) {
is MaskingKey.FourByteMaskingKey -> 4
MaskingKey.NoMaskingKey -> 0
}
}
fun serialize(writeBuffer: WriteBuffer) {
serializeByte1(writeBuffer)
serializeMaskAndPayloadLength(writeBuffer)
serializeMaskingKeyAndPayload(writeBuffer)
}
private fun serializeByte1(writeBuffer: WriteBuffer) {
val byte1Array = BooleanArray(8)
byte1Array[0] = fin
byte1Array[1] = rsv1
byte1Array[2] = rsv2
byte1Array[3] = rsv3
byte1Array[4] = opcode.value[4]
byte1Array[5] = opcode.value[5]
byte1Array[6] = opcode.value[6]
byte1Array[7] = opcode.value[7]
writeBuffer.writeByte(byte1Array.toByte())
}
private fun serializeMaskAndPayloadLength(writeBuffer: WriteBuffer) {
// write mask and payload len
val maskedBitAndPayloadLengthArray = payloadLength.toByte().toBooleanArray()
maskedBitAndPayloadLengthArray[0] = maskingKey is MaskingKey.FourByteMaskingKey
val byte = maskedBitAndPayloadLengthArray.toByte()
writeBuffer.writeByte(byte)
if (payloadLength == 126) {
writeBuffer.writeUShort(payloadData.limit().toUShort())
} else if (payloadLength == 127) {
writeBuffer.writeLong(payloadData.limit().toLong())
}
}
private fun serializeMaskingKeyAndPayload(writeBuffer: WriteBuffer) {
if (maskingKey is MaskingKey.FourByteMaskingKey) {
maskingKey.write(writeBuffer)
}
val data = if (maskingKey is MaskingKey.FourByteMaskingKey) {
payloadData.position(0)
TransformedReadBuffer(payloadData) { i, original ->
original xor maskingKey[i.toLong().mod(4)]
}
} else {
payloadData
}
data.position(0)
writeBuffer.write(data)
}
override fun toString(): String {
val p = if (opcode == Opcode.Text) {
val position = payloadData.position()
val s = payloadData.readString(payloadData.remaining())
payloadData.position(position)
s
} else {
payloadData.toString()
}
return "Frame(fin=$fin, rsv1=$rsv1, rsv2=$rsv2, rsv3=$rsv3, opcode=$opcode, maskingKey=$maskingKey, payloadData=$p, payloadLength=$payloadLength, actualPayloadLength=$actualPayloadLength)"
}
fun Byte.toBooleanArray(): BooleanArray {
val booleanArray = BooleanArray(8)
booleanArray[0] = get(0)
booleanArray[1] = get(1)
booleanArray[2] = get(2)
booleanArray[3] = get(3)
booleanArray[4] = get(4)
booleanArray[5] = get(5)
booleanArray[6] = get(6)
booleanArray[7] = get(7)
return booleanArray
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy