Reuse http_network library.
Reintroduced HTTPD_STREAM_SOCKET for backward compatibility, and ease of usage. Added websocket libraries (client, and protocol).
This commit is contained in:
739
library/network/websocket/client/src/web_socket_impl.e
Normal file
739
library/network/websocket/client/src/web_socket_impl.e
Normal file
@@ -0,0 +1,739 @@
|
||||
note
|
||||
description: "Summary description for {WEB_SOCKET_IMPL}."
|
||||
author: ""
|
||||
date: "$Date$"
|
||||
revision: "$Revision$"
|
||||
|
||||
class
|
||||
WEB_SOCKET_IMPL
|
||||
|
||||
inherit
|
||||
|
||||
WEB_SOCKET
|
||||
|
||||
create
|
||||
make, make_with_port, make_with_protocols, make_with_protocols_and_port
|
||||
|
||||
feature {NONE} -- Initialization
|
||||
|
||||
make (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL)
|
||||
-- Create a websocket instante with a default port.
|
||||
do
|
||||
reset
|
||||
subscriber := a_subscriber
|
||||
uri := a_uri
|
||||
create protocol.make_empty
|
||||
set_default_port
|
||||
create ready_state.make
|
||||
ensure
|
||||
uri_set: a_uri = uri
|
||||
port_wss: is_tunneled implies port = wss_port_default
|
||||
port_ws: not is_tunneled implies port = ws_port_default
|
||||
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
|
||||
subscriber_set: subscriber = a_subscriber
|
||||
protocol_set: protocol.is_empty
|
||||
end
|
||||
|
||||
make_with_port (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_port: INTEGER)
|
||||
-- Create a websocket instance with port `a_port',
|
||||
do
|
||||
make (a_subscriber, a_uri)
|
||||
port := a_port
|
||||
ensure
|
||||
uri_set: a_uri = uri
|
||||
port_set: port = a_port
|
||||
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
|
||||
subscriber_set: subscriber = a_subscriber
|
||||
end
|
||||
|
||||
make_with_protocols (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_protocols: detachable LIST [STRING])
|
||||
-- Create a web socket instance with a list of protocols `a_protocols' and default port.
|
||||
do
|
||||
reset
|
||||
subscriber := a_subscriber
|
||||
uri := a_uri
|
||||
create protocol.make_empty
|
||||
protocols := a_protocols
|
||||
set_default_port
|
||||
create ready_state.make
|
||||
ensure
|
||||
uri_set: a_uri = uri
|
||||
port_wss: is_tunneled implies port = wss_port_default
|
||||
port_ws: not is_tunneled implies port = ws_port_default
|
||||
protocols_set: protocols = a_protocols
|
||||
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
|
||||
subscriber_set: subscriber = a_subscriber
|
||||
protocol_set: protocol.is_empty
|
||||
end
|
||||
|
||||
make_with_protocols_and_port (a_subscriber: WEB_SOCKET_SUBSCRIBER; a_uri: READABLE_STRING_GENERAL; a_protocols: detachable LIST [STRING]; a_port: INTEGER)
|
||||
-- Create a web socket instance with a list of protocols `a_protocols' and port `a_port'.
|
||||
do
|
||||
make_with_protocols (a_subscriber, a_uri, a_protocols)
|
||||
port := a_port
|
||||
ensure
|
||||
uri_set: a_uri = uri
|
||||
protocols_set: protocols = a_protocols
|
||||
port_set: port = a_port
|
||||
ready_state_set: ready_state.state = {WEB_SOCKET_READY_STATE}.connecting
|
||||
subscriber_set: subscriber = a_subscriber
|
||||
end
|
||||
|
||||
reset
|
||||
do
|
||||
has_error := False
|
||||
end
|
||||
|
||||
feature -- Access
|
||||
|
||||
has_error: BOOLEAN
|
||||
|
||||
is_verbose: BOOLEAN
|
||||
|
||||
feature -- Handshake
|
||||
|
||||
start_handshake (a_handshake: STRING)
|
||||
do
|
||||
subscriber.on_websocket_handshake (a_handshake)
|
||||
end
|
||||
|
||||
feature -- Receive
|
||||
|
||||
receive
|
||||
local
|
||||
l_frame: detachable WEB_SOCKET_FRAME
|
||||
l_client_message: detachable READABLE_STRING_8
|
||||
do
|
||||
l_frame := next_frame (subscriber.connection)
|
||||
if l_frame /= Void and then l_frame.is_valid then
|
||||
if attached l_frame.injected_control_frames as l_injections then
|
||||
-- Process injected control frames now.
|
||||
-- FIXME
|
||||
across
|
||||
l_injections as ic
|
||||
loop
|
||||
if ic.item.is_connection_close then
|
||||
-- FIXME: we should probably send this event .. after the `l_frame.parent' frame event.
|
||||
subscriber.on_websocket_close ("Normal Close")
|
||||
elseif ic.item.is_ping then
|
||||
-- FIXME reply only to the most recent ping ...
|
||||
subscriber.on_websocket_ping (ic.item.payload_data)
|
||||
elseif ic.item.is_pong then
|
||||
subscriber.on_websocket_pong (ic.item.payload_data)
|
||||
else
|
||||
subscriber.on_websocket_error ("Wrong Opcode")
|
||||
end
|
||||
end
|
||||
end
|
||||
l_client_message := l_frame.payload_data
|
||||
if l_client_message = Void then
|
||||
l_client_message := ""
|
||||
end
|
||||
debug ("ws")
|
||||
print ("%NExecute: %N")
|
||||
print (" [opcode: " + opcode_name (l_frame.opcode) + "]%N")
|
||||
if l_frame.is_text then
|
||||
print (" [client message: %"" + l_client_message + "%"]%N")
|
||||
elseif l_frame.is_binary then
|
||||
print (" [client binary message length: %"" + l_client_message.count.out + "%"]%N")
|
||||
end
|
||||
print (" [is_control: " + l_frame.is_control.out + "]%N")
|
||||
print (" [is_binary: " + l_frame.is_binary.out + "]%N")
|
||||
print (" [is_text: " + l_frame.is_text.out + "]%N")
|
||||
end
|
||||
if l_frame.is_connection_close then
|
||||
subscriber.on_websocket_close ("Normal Close")
|
||||
elseif l_frame.is_binary then
|
||||
subscriber.on_websocket_binary_message (l_client_message)
|
||||
elseif l_frame.is_text then
|
||||
subscriber.on_websocket_text_message (l_client_message)
|
||||
elseif l_frame.is_ping then
|
||||
subscriber.on_websocket_ping (l_client_message)
|
||||
elseif l_frame.is_pong then
|
||||
subscriber.on_websocket_pong (l_client_message)
|
||||
else
|
||||
subscriber.on_websocket_error ("Wrong Opcode")
|
||||
end
|
||||
else
|
||||
debug ("ws")
|
||||
print ("%NExecute: %N")
|
||||
print (" [ERROR: invalid frame]%N")
|
||||
if l_frame /= Void and then attached l_frame.error as err then
|
||||
print (" [Code: " + err.code.out + "]%N")
|
||||
print (" [Description: " + err.description + "]%N")
|
||||
end
|
||||
end
|
||||
subscriber.on_websocket_close ("")
|
||||
end
|
||||
|
||||
-- if l_utf.is_valid_utf_8_string_8 (l_message) then
|
||||
-- if is_data_frame_ok then
|
||||
-- if opcode = text_frame then
|
||||
-- subscriber.on_websocket_text_message (l_message)
|
||||
-- elseif opcode = binary_frame then
|
||||
-- subscriber.on_websocket_binary_message (l_message)
|
||||
-- elseif opcode = ping_frame then
|
||||
-- subscriber.on_websocket_ping (l_message)
|
||||
-- elseif opcode = pong_frame then
|
||||
-- subscriber.on_websocket_pong (l_message)
|
||||
-- elseif opcode = Connection_close_frame then
|
||||
-- subscriber.on_websocket_close ("Normal Close")
|
||||
-- elseif opcode = ping_frame then
|
||||
-- subscriber.on_websocket_ping (l_message)
|
||||
-- elseif opcode = pong_frame then
|
||||
-- subscriber.on_websocket_pong (l_message)
|
||||
-- else
|
||||
-- subscriber.on_websocket_error ("Wrong Opcode")
|
||||
-- end
|
||||
-- else
|
||||
-- subscriber.on_websocket_close ("Invalid data frame")
|
||||
-- end
|
||||
-- else
|
||||
-- subscriber.on_websocket_error ("Invalid UTF-8")
|
||||
-- end
|
||||
|
||||
end
|
||||
|
||||
feature -- Methods
|
||||
|
||||
send (a_message: STRING)
|
||||
do
|
||||
end
|
||||
|
||||
close (a_id: INTEGER)
|
||||
-- Close a websocket connection with a close id : `a_id'
|
||||
do
|
||||
end
|
||||
|
||||
close_with_description (a_id: INTEGER; a_description: READABLE_STRING_GENERAL)
|
||||
-- Close a websocket connection with a close id : `a_id' and a description `a_description'
|
||||
do
|
||||
end
|
||||
|
||||
feature {NONE} -- Implementation
|
||||
|
||||
set_default_port
|
||||
do
|
||||
if is_tunneled then
|
||||
port := wss_port_default
|
||||
else
|
||||
port := ws_port_default
|
||||
end
|
||||
end
|
||||
|
||||
subscriber: WEB_SOCKET_SUBSCRIBER
|
||||
|
||||
next_frame (a_socket: HTTP_STREAM_SOCKET): detachable WEB_SOCKET_FRAME
|
||||
-- TODO Binary messages
|
||||
-- Handle error responses in a better way.
|
||||
-- IDEA:
|
||||
-- class FRAME
|
||||
-- is_fin: BOOLEAN
|
||||
-- opcode: WEB_SOCKET_STATUS_CODE (TEXT, BINARY, CLOSE, CONTINUE,PING, PONG)
|
||||
-- data/payload
|
||||
-- status_code: #see Status Codes http://tools.ietf.org/html/rfc6455#section-7.3
|
||||
-- has_error
|
||||
--
|
||||
-- See Base Framing Protocol: http://tools.ietf.org/html/rfc6455#section-5.2
|
||||
-- 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 ... |
|
||||
-- +---------------------------------------------------------------+
|
||||
note
|
||||
EIS: "name=WebSocket RFC", "protocol=URI", "src=http://tools.ietf.org/html/rfc6455#section-5.2"
|
||||
require
|
||||
a_socket_in_blocking_mode: a_socket.is_blocking
|
||||
local
|
||||
l_opcode: INTEGER
|
||||
l_len: INTEGER
|
||||
l_remaining_len: INTEGER
|
||||
l_payload_len: NATURAL_64
|
||||
l_chunk: STRING
|
||||
l_rsv: BOOLEAN
|
||||
l_fin: BOOLEAN
|
||||
l_has_mask: BOOLEAN
|
||||
l_chunk_size: INTEGER
|
||||
l_byte: INTEGER
|
||||
l_fetch_count: INTEGER
|
||||
l_bytes_read: INTEGER
|
||||
s: STRING
|
||||
is_data_frame_ok: BOOLEAN -- Is the last process data framing ok?
|
||||
retried: BOOLEAN
|
||||
do
|
||||
if not retried then
|
||||
debug ("ws")
|
||||
print ("next_frame:%N")
|
||||
end
|
||||
from
|
||||
is_data_frame_ok := True
|
||||
until
|
||||
l_fin or not is_data_frame_ok
|
||||
loop
|
||||
-- multi-frames or continue is only valid for Binary or Text
|
||||
s := next_bytes (a_socket, 1)
|
||||
if s.is_empty then
|
||||
is_data_frame_ok := False
|
||||
debug ("ws")
|
||||
print ("[ERROR] incomplete_data!%N")
|
||||
end
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" fin,rsv(3),opcode(4)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
print ("%N")
|
||||
end
|
||||
l_fin := l_byte & (0b10000000) /= 0
|
||||
l_rsv := l_byte & (0b01110000) = 0
|
||||
l_opcode := l_byte & 0b00001111
|
||||
if Result /= Void then
|
||||
if l_opcode = Result.opcode then
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected injected frame")
|
||||
elseif l_opcode = continuation_frame then
|
||||
-- Expected
|
||||
Result.update_fin (l_fin)
|
||||
elseif is_control_frame (l_opcode) then
|
||||
-- Control frames (see Section 5.5) MAY be injected in the middle of
|
||||
-- a fragmented message. Control frames themselves MUST NOT be fragmented.
|
||||
-- if the l_opcode is a control frame then there is an error!!!
|
||||
-- CLOSE, PING, PONG
|
||||
create Result.make_as_injected_control (l_opcode, Result)
|
||||
else
|
||||
-- should not occur in multi-fragment frame!
|
||||
create Result.make (l_opcode, l_fin)
|
||||
Result.report_error (protocol_error, "Unexpected frame")
|
||||
end
|
||||
else
|
||||
create Result.make (l_opcode, l_fin)
|
||||
if Result.is_continuation then
|
||||
-- Continuation frame is not expected without parent frame!
|
||||
Result.report_error (protocol_error, "There is no message to continue!")
|
||||
end
|
||||
end
|
||||
if Result.is_valid then
|
||||
--| valid frame/fragment
|
||||
if is_verbose then
|
||||
log ("+ frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")")
|
||||
end
|
||||
|
||||
-- rsv validation
|
||||
if not l_rsv then
|
||||
-- RSV1, RSV2, RSV3: 1 bit each
|
||||
|
||||
-- MUST be 0 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_
|
||||
|
||||
-- FIXME: add support for extension ?
|
||||
Result.report_error (protocol_error, "RSV values MUST be 0 unless an extension is negotiated that defines meanings for non-zero values")
|
||||
end
|
||||
else
|
||||
if is_verbose then
|
||||
log ("+ INVALID frame " + opcode_name (l_opcode) + " (fin=" + l_fin.out + ")")
|
||||
end
|
||||
end
|
||||
|
||||
-- At the moment only TEXT, (pending Binary)
|
||||
if Result.is_valid then
|
||||
if Result.is_text or Result.is_binary or Result.is_control then
|
||||
-- Reading next byte (mask+payload_len)
|
||||
s := next_bytes (a_socket, 1)
|
||||
if s.is_empty then
|
||||
Result.report_error (invalid_data, "Incomplete data for mask and payload len")
|
||||
else
|
||||
l_byte := s [1].code
|
||||
debug ("ws")
|
||||
print (" mask,payload_len(7)=")
|
||||
print (to_byte_representation (l_byte))
|
||||
io.put_new_line
|
||||
end
|
||||
l_has_mask := l_byte & (0b10000000) /= 0 -- MASK
|
||||
l_len := l_byte & 0b01111111 -- 7bits
|
||||
|
||||
debug ("ws")
|
||||
print (" payload_len=" + l_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_control and then l_len > 125 then
|
||||
-- All control frames MUST have a payload length of 125 bytes or less
|
||||
-- and MUST NOT be fragmented.
|
||||
Result.report_error (protocol_error, "Control frame MUST have a payload length of 125 bytes or less")
|
||||
elseif l_len = 127 then -- TODO proof of concept read 8 bytes.
|
||||
-- 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.
|
||||
s := next_bytes (a_socket, 8) -- 64 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 8 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 64 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [8].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [7].natural_32_code.to_natural_64 |<< 8)
|
||||
l_payload_len := l_payload_len | (s [6].natural_32_code.to_natural_64 |<< 16)
|
||||
l_payload_len := l_payload_len | (s [5].natural_32_code.to_natural_64 |<< 24)
|
||||
l_payload_len := l_payload_len | (s [4].natural_32_code.to_natural_64 |<< 32)
|
||||
l_payload_len := l_payload_len | (s [3].natural_32_code.to_natural_64 |<< 40)
|
||||
l_payload_len := l_payload_len | (s [2].natural_32_code.to_natural_64 |<< 48)
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 56)
|
||||
end
|
||||
elseif l_len = 126 then
|
||||
s := next_bytes (a_socket, 2) -- 16 bits
|
||||
debug ("ws")
|
||||
print (" extended payload length bits=" + string_to_byte_representation (s))
|
||||
io.put_new_line
|
||||
end
|
||||
if s.count < 2 then
|
||||
Result.report_error (Invalid_data, "Incomplete data for 16 bit Extended payload length")
|
||||
else
|
||||
l_payload_len := s [2].natural_32_code.to_natural_64
|
||||
l_payload_len := l_payload_len | (s [1].natural_32_code.to_natural_64 |<< 8)
|
||||
end
|
||||
else
|
||||
l_payload_len := l_len.to_natural_64
|
||||
end
|
||||
debug ("ws")
|
||||
print (" Full payload length=" + l_payload_len.out)
|
||||
io.put_new_line
|
||||
end
|
||||
if Result.is_valid then
|
||||
if l_has_mask then
|
||||
Result.report_error (protocol_error, "Server sent a masked frame!")
|
||||
else
|
||||
end
|
||||
if Result.is_valid then
|
||||
l_chunk_size := 0x4000 -- 16 K
|
||||
if l_payload_len > {INTEGER_32}.max_value.to_natural_64 then
|
||||
-- Issue .. to big to store in STRING
|
||||
-- FIXME !!!
|
||||
Result.report_error (Message_too_large, "Can not handle payload data (len=" + l_payload_len.out + ")")
|
||||
else
|
||||
l_len := l_payload_len.to_integer_32
|
||||
end
|
||||
from
|
||||
l_fetch_count := 0
|
||||
l_remaining_len := l_len
|
||||
until
|
||||
l_fetch_count >= l_len or l_len = 0 or not Result.is_valid
|
||||
loop
|
||||
if l_remaining_len < l_chunk_size then
|
||||
l_chunk_size := l_remaining_len
|
||||
end
|
||||
a_socket.read_stream (l_chunk_size)
|
||||
l_bytes_read := a_socket.bytes_read
|
||||
debug ("ws")
|
||||
print ("read chunk size=" + l_chunk_size.out + " fetch_count=" + l_fetch_count.out + " l_len=" + l_len.out + " -> " + l_bytes_read.out + "bytes%N")
|
||||
end
|
||||
if a_socket.bytes_read > 0 then
|
||||
l_remaining_len := l_remaining_len - l_bytes_read
|
||||
l_chunk := a_socket.last_string
|
||||
l_fetch_count := l_fetch_count + l_bytes_read
|
||||
Result.append_payload_data_chop (l_chunk, l_bytes_read, l_remaining_len = 0)
|
||||
else
|
||||
Result.report_error (internal_error, "Issue reading payload data...")
|
||||
end
|
||||
end
|
||||
if is_verbose then
|
||||
log (" Received " + l_fetch_count.out + " out of " + l_len.out + " bytes <===============")
|
||||
end
|
||||
debug ("ws")
|
||||
print (" -> ")
|
||||
if attached Result.payload_data as l_payload_data then
|
||||
s := l_payload_data.tail (l_fetch_count)
|
||||
if s.count > 50 then
|
||||
print (string_to_byte_hexa_representation (s.head (50) + ".."))
|
||||
else
|
||||
print (string_to_byte_hexa_representation (s))
|
||||
end
|
||||
print ("%N")
|
||||
if Result.is_text and Result.is_fin and Result.fragment_count = 0 then
|
||||
print (" -> ")
|
||||
if s.count > 50 then
|
||||
print (s.head (50) + "..")
|
||||
else
|
||||
print (s)
|
||||
end
|
||||
print ("%N")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if Result /= Void then
|
||||
if attached Result.error as err then
|
||||
if is_verbose then
|
||||
log (" !Invalid frame: " + err.string)
|
||||
end
|
||||
end
|
||||
if Result.is_injected_control then
|
||||
if attached Result.parent as l_parent then
|
||||
if not Result.is_valid then
|
||||
l_parent.report_error (protocol_error, "Invalid injected frame")
|
||||
end
|
||||
if Result.is_connection_close then
|
||||
-- Return this and process the connection close right away!
|
||||
l_parent.update_fin (True)
|
||||
l_fin := Result.is_fin
|
||||
else
|
||||
Result := l_parent
|
||||
l_fin := l_parent.is_fin
|
||||
check
|
||||
-- This is a control frame but occurs in fragmented frame.
|
||||
inside_fragmented_frame: not l_fin
|
||||
end
|
||||
end
|
||||
else
|
||||
check
|
||||
has_parent: False
|
||||
end
|
||||
l_fin := False -- This is a control frame but occurs in fragmented frame.
|
||||
end
|
||||
end
|
||||
if not Result.is_valid then
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
else
|
||||
is_data_frame_ok := False
|
||||
end
|
||||
end
|
||||
end
|
||||
rescue
|
||||
retried := True
|
||||
if Result /= Void then
|
||||
Result.report_error (internal_error, "Internal error")
|
||||
end
|
||||
retry
|
||||
end
|
||||
|
||||
next_bytes (a_socket: HTTP_STREAM_SOCKET; nb: INTEGER): STRING
|
||||
require
|
||||
nb > 0
|
||||
local
|
||||
n, l_bytes_read: INTEGER
|
||||
do
|
||||
create Result.make (nb)
|
||||
from
|
||||
n := nb
|
||||
until
|
||||
n = 0
|
||||
loop
|
||||
a_socket.read_stream (nb)
|
||||
l_bytes_read := a_socket.bytes_read
|
||||
if l_bytes_read > 0 then
|
||||
Result.append (a_socket.last_string)
|
||||
n := n - l_bytes_read
|
||||
else
|
||||
n := 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
unmask (a_chunk: STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8)
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_chunk.put_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code), i)
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
append_chunk_unmasked (a_chunk: READABLE_STRING_8; a_pos: INTEGER; a_key: READABLE_STRING_8; a_target: STRING)
|
||||
-- To convert masked data into unmasked data, or vice versa, the following
|
||||
-- algorithm is applied. The same algorithm applies regardless of the
|
||||
-- direction of the translation, e.g., the same steps are applied to
|
||||
-- mask the data as to unmask the data.
|
||||
|
||||
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
|
||||
-- octet i of the original data ("original-octet-i") with octet at index
|
||||
-- i modulo 4 of the masking key ("masking-key-octet-j"):
|
||||
|
||||
-- j = i MOD 4
|
||||
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
|
||||
|
||||
-- The payload length, indicated in the framing as frame-payload-length,
|
||||
-- does NOT include the length of the masking key. It is the length of
|
||||
-- the "Payload data", e.g., the number of bytes following the masking
|
||||
-- key.
|
||||
note
|
||||
EIS: "name=Masking", "src=http://tools.ietf.org/html/rfc6455#section-5.3", "protocol=uri"
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
-- debug ("ws")
|
||||
-- print ("append_chunk_unmasked (%"" + string_to_byte_representation (a_chunk) + "%",%N%Ta_pos=" + a_pos.out+ ", a_key, a_target #.count=" + a_target.count.out + ")%N")
|
||||
-- end
|
||||
from
|
||||
i := 1
|
||||
n := a_chunk.count
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
a_target.append_code (a_chunk.code (i).bit_xor (a_key [((i + (a_pos - 1) - 1) \\ 4) + 1].natural_32_code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
|
||||
feature {NONE} -- Debug
|
||||
|
||||
log (m: STRING)
|
||||
do
|
||||
io.put_string (m + "%N")
|
||||
end
|
||||
|
||||
to_byte (a_integer: INTEGER): ARRAY [INTEGER]
|
||||
require
|
||||
valid: a_integer >= 0 and then a_integer <= 255
|
||||
local
|
||||
l_val: INTEGER
|
||||
l_index: INTEGER
|
||||
do
|
||||
create Result.make_filled (0, 1, 8)
|
||||
from
|
||||
l_val := a_integer
|
||||
l_index := 8
|
||||
until
|
||||
l_val < 2
|
||||
loop
|
||||
Result.put (l_val \\ 2, l_index)
|
||||
l_val := l_val // 2
|
||||
l_index := l_index - 1
|
||||
end
|
||||
Result.put (l_val, l_index)
|
||||
end
|
||||
|
||||
feature -- Masking
|
||||
|
||||
unmmask (a_frame: READABLE_STRING_8; a_key: READABLE_STRING_8): STRING
|
||||
-- To convert masked data into unmasked data, or vice versa, the following
|
||||
-- algorithm is applied. The same algorithm applies regardless of the
|
||||
-- direction of the translation, e.g., the same steps are applied to
|
||||
-- mask the data as to unmask the data.
|
||||
|
||||
-- Octet i of the transformed data ("transformed-octet-i") is the XOR of
|
||||
-- octet i of the original data ("original-octet-i") with octet at index
|
||||
-- i modulo 4 of the masking key ("masking-key-octet-j"):
|
||||
|
||||
-- j = i MOD 4
|
||||
-- transformed-octet-i = original-octet-i XOR masking-key-octet-j
|
||||
|
||||
-- The payload length, indicated in the framing as frame-payload-length,
|
||||
-- does NOT include the length of the masking key. It is the length of
|
||||
-- the "Payload data", e.g., the number of bytes following the masking
|
||||
-- key.
|
||||
note
|
||||
EIS: "name=Masking", "src=S", "protocol=uri"
|
||||
local
|
||||
l_frame: STRING
|
||||
i: INTEGER
|
||||
do
|
||||
l_frame := a_frame.twin
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > l_frame.count
|
||||
loop
|
||||
l_frame [i] := (l_frame [i].code.to_integer_8.bit_xor (a_key [((i - 1) \\ 4) + 1].code.to_integer_8)).to_character_8
|
||||
i := i + 1
|
||||
end
|
||||
Result := l_frame
|
||||
end
|
||||
|
||||
to_byte_representation (a_integer: INTEGER): STRING
|
||||
require
|
||||
valid: a_integer >= 0 and then a_integer <= 255
|
||||
local
|
||||
l_val: INTEGER
|
||||
do
|
||||
create Result.make (8)
|
||||
from
|
||||
l_val := a_integer
|
||||
until
|
||||
l_val < 2
|
||||
loop
|
||||
Result.prepend_integer (l_val \\ 2)
|
||||
l_val := l_val // 2
|
||||
end
|
||||
Result.prepend_integer (l_val)
|
||||
end
|
||||
|
||||
string_to_byte_representation (s: STRING): STRING
|
||||
require
|
||||
valid: s.count > 0
|
||||
local
|
||||
i, n: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
Result.append (to_byte_representation (s [i].code))
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
string_to_byte_hexa_representation (s: STRING): STRING
|
||||
local
|
||||
i, n: INTEGER
|
||||
c: INTEGER
|
||||
do
|
||||
n := s.count
|
||||
create Result.make (8 * n)
|
||||
if n > 0 then
|
||||
from
|
||||
i := 1
|
||||
until
|
||||
i > n
|
||||
loop
|
||||
if not Result.is_empty then
|
||||
Result.append_character (':')
|
||||
end
|
||||
c := s [i].code
|
||||
check
|
||||
c <= 0xFF
|
||||
end
|
||||
Result.append_character (((c |>> 4) & 0xF).to_hex_character)
|
||||
Result.append_character (((c) & 0xF).to_hex_character)
|
||||
i := i + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user