< Summary

Information
Class: Ice.Internal.WSTransceiver
Assembly: Ice
File(s): /_/csharp/src/Ice/Internal/WSTransceiver.cs
Tag: 125_37167941578
Line coverage
83%
Covered lines: 573
Uncovered lines: 113
Coverable lines: 686
Total lines: 1810
Line coverage: 83.5%
Branch coverage
81%
Covered branches: 426
Total branches: 524
Branch coverage: 81.2%
Method coverage
90%
Covered methods: 29
Fully covered methods: 14
Total methods: 32
Method coverage: 90.6%
Full method coverage: 43.7%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
fd()100%210%
initialize(...)87.1%706287.23%
closing(...)83.33%272483.33%
close()100%44100%
bind()100%210%
destroy()100%11100%
write(...)93.33%313090%
read(...)88.89%493678.38%
startRead(...)90%121073.68%
finishRead(...)90%101088.89%
startWrite(...)87.5%8881.82%
finishWrite(...)91.67%121286.67%
protocol()100%11100%
getInfo(...)100%11100%
checkSendSize(...)100%11100%
setBufferSize(...)100%11100%
ToString()100%11100%
toDetailedString()100%210%
.ctor(...)100%11100%
.ctor(...)100%11100%
hasUpgradeToken(...)100%22100%
init(...)100%11100%
handleRequest(...)67.5%504081.54%
handleResponse()50%592460.71%
preRead(...)70.69%27811677.1%
postRead(...)100%1010100%
preWrite(...)86.96%524686.15%
postWrite(...)81.67%1136075.56%
readBuffered(...)100%88100%
prepareWriteHeader(...)100%1010100%
.cctor()100%11100%
canonicalizeOrigin(...)100%1212100%

File(s)

/_/csharp/src/Ice/Internal/WSTransceiver.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using System.Diagnostics;
 4using System.Net.Sockets;
 5using System.Security.Cryptography;
 6using System.Text;
 7
 8namespace Ice.Internal;
 9
 10internal sealed class WSTransceiver : Transceiver
 11{
 012    public Socket fd() => _delegate.fd();
 13
 14    public int initialize(Buffer readBuffer, Buffer writeBuffer, ref bool hasMoreData)
 15    {
 16        //
 17        // Delegate logs exceptions that occur during initialize(), so there's no need to trap them here.
 18        //
 119        if (_state == StateInitializeDelegate)
 20        {
 121            int op = _delegate.initialize(readBuffer, writeBuffer, ref hasMoreData);
 122            if (op != 0)
 23            {
 124                return op;
 25            }
 126            _state = StateConnected;
 27        }
 28
 29        try
 30        {
 131            if (_state == StateConnected)
 32            {
 33                //
 34                // We don't know how much we'll need to read.
 35                //
 136                _readBuffer.resize(1024, true);
 137                _readBuffer.b.position(0);
 138                _readBufferPos = 0;
 39
 40                //
 41                // The server waits for the client's upgrade request, the
 42                // client sends the upgrade request.
 43                //
 144                _state = StateUpgradeRequestPending;
 145                if (!_incoming)
 46                {
 47                    //
 48                    // Compose the upgrade request.
 49                    //
 150                    var @out = new StringBuilder();
 151                    @out.Append("GET " + _resource + " HTTP/1.1\r\n");
 152                    @out.Append("Host: " + _host + "\r\n");
 153                    @out.Append("Upgrade: websocket\r\n");
 154                    @out.Append("Connection: Upgrade\r\n");
 155                    @out.Append("Sec-WebSocket-Protocol: " + _iceProtocol + "\r\n");
 156                    @out.Append("Sec-WebSocket-Version: 13\r\n");
 157                    @out.Append("Sec-WebSocket-Key: ");
 58
 59                    //
 60                    // The value for Sec-WebSocket-Key is a 16-byte random number,
 61                    // encoded with Base64.
 62                    //
 163                    byte[] key = new byte[16];
 164                    RandomNumberGenerator.Fill(key);
 165                    _key = System.Convert.ToBase64String(key);
 166                    @out.Append(_key + "\r\n\r\n"); // EOM
 67
 168                    byte[] bytes = _utf8.GetBytes(@out.ToString());
 169                    _writeBuffer.resize(bytes.Length, false);
 170                    _writeBuffer.b.position(0);
 171                    _writeBuffer.b.put(bytes);
 172                    _writeBuffer.b.flip();
 73                }
 74            }
 75
 76            //
 77            // Try to write the client's upgrade request.
 78            //
 179            if (_state == StateUpgradeRequestPending && !_incoming)
 80            {
 181                if (_writeBuffer.b.hasRemaining())
 82                {
 183                    int s = _delegate.write(_writeBuffer);
 184                    if (s != 0)
 85                    {
 186                        return s;
 87                    }
 88                }
 89                Debug.Assert(!_writeBuffer.b.hasRemaining());
 190                _state = StateUpgradeResponsePending;
 91
 192                if (_instance.traceLevel() >= 1)
 93                {
 194                    _instance.logger().trace(
 195                        _instance.traceCategory(),
 196                        "sent " + protocol() + " connection HTTP upgrade request\n" + ToString());
 97                }
 98            }
 99
 100            while (true)
 101            {
 1102                if (_readBuffer.b.hasRemaining())
 103                {
 1104                    int s = _delegate.read(_readBuffer, ref hasMoreData);
 1105                    if (s == SocketOperation.Write || _readBuffer.b.position() == 0)
 106                    {
 1107                        return s;
 108                    }
 109                }
 110
 111                //
 112                // Try to read the client's upgrade request or the server's response.
 113                //
 1114                if ((_state == StateUpgradeRequestPending && _incoming) ||
 1115                   (_state == StateUpgradeResponsePending && !_incoming))
 116                {
 117                    //
 118                    // Check if we have enough data for a complete message.
 119                    //
 1120                    int p = _parser.isCompleteMessage(_readBuffer.b, 0, _readBuffer.b.position());
 1121                    if (p == -1)
 122                    {
 0123                        if (_readBuffer.b.hasRemaining())
 124                        {
 0125                            return SocketOperation.Read;
 126                        }
 127
 128                        //
 129                        // Enlarge the buffer and try to read more.
 130                        //
 0131                        int oldSize = _readBuffer.b.position();
 0132                        if (oldSize + 1024 > _instance.messageSizeMax())
 133                        {
 0134                            Ex.throwMemoryLimitException(
 0135                                requested: oldSize + 1024,
 0136                                maximum: _instance.messageSizeMax());
 137                        }
 0138                        _readBuffer.resize(oldSize + 1024, true);
 0139                        _readBuffer.b.position(oldSize);
 0140                        continue; // Try again to read the response/request
 141                    }
 142
 143                    //
 144                    // Set _readBufferPos at the end of the response/request message.
 145                    //
 1146                    _readBufferPos = p;
 147                }
 148
 149                //
 150                // We're done, the client's upgrade request or server's response is read.
 151                //
 152                break;
 153            }
 154
 155            try
 156            {
 157                //
 158                // Parse the client's upgrade request.
 159                //
 1160                if (_state == StateUpgradeRequestPending && _incoming)
 161                {
 1162                    if (_parser.parse(_readBuffer.b, 0, _readBufferPos))
 163                    {
 1164                        handleRequest(_writeBuffer);
 1165                        _state = StateUpgradeResponsePending;
 166                    }
 167                    else
 168                    {
 0169                        throw new Ice.ProtocolException("incomplete request message");
 170                    }
 171                }
 172
 1173                if (_state == StateUpgradeResponsePending)
 174                {
 1175                    if (_incoming)
 176                    {
 1177                        if (_writeBuffer.b.hasRemaining())
 178                        {
 1179                            int s = _delegate.write(_writeBuffer);
 1180                            if (s != 0)
 181                            {
 1182                                return s;
 183                            }
 184                        }
 185                    }
 186                    else
 187                    {
 188                        //
 189                        // Parse the server's response
 190                        //
 1191                        if (_parser.parse(_readBuffer.b, 0, _readBufferPos))
 192                        {
 1193                            handleResponse();
 194                        }
 195                        else
 196                        {
 0197                            throw new Ice.ProtocolException("incomplete response message");
 198                        }
 199                    }
 200                }
 1201            }
 1202            catch (WebSocketException ex)
 203            {
 1204                throw new Ice.ProtocolException(ex.Message);
 205            }
 206
 1207            _state = StateOpened;
 1208            _nextState = StateOpened;
 209
 1210            hasMoreData = _readBufferPos < _readBuffer.b.position();
 1211        }
 1212        catch (Ice.LocalException ex)
 213        {
 1214            if (_instance.traceLevel() >= 2)
 215            {
 1216                _instance.logger().trace(
 1217                    _instance.traceCategory(),
 1218                    protocol() + " connection HTTP upgrade request failed\n" + ToString() + "\n" + ex);
 219            }
 1220            throw;
 221        }
 222
 1223        if (_instance.traceLevel() >= 1)
 224        {
 1225            if (_incoming)
 226            {
 1227                _instance.logger().trace(
 1228                    _instance.traceCategory(),
 1229                    "accepted " + protocol() + " connection HTTP upgrade request\n" + ToString());
 230            }
 231            else
 232            {
 1233                _instance.logger().trace(
 1234                    _instance.traceCategory(),
 1235                    protocol() + " connection HTTP upgrade request accepted\n" + ToString());
 236            }
 237        }
 238
 1239        return SocketOperation.None;
 1240    }
 241
 242    public int closing(bool initiator, Ice.LocalException reason)
 243    {
 1244        if (_instance.traceLevel() >= 1)
 245        {
 1246            _instance.logger().trace(
 1247                _instance.traceCategory(),
 1248                "gracefully closing " + protocol() + " connection\n" + ToString());
 249        }
 250
 1251        int s = _nextState == StateOpened ? _state : _nextState;
 252
 1253        if (s == StateClosingRequestPending && _closingInitiator)
 254        {
 255            //
 256            // If we initiated a close connection but also received a
 257            // close connection, we assume we didn't initiate the
 258            // close and we send the close frame now. This is to
 259            // ensure that if both peers close the connection at the same
 260            // time we don't hang having both peers waiting for the close
 261            // frame of the other.
 262            //
 263            Debug.Assert(!initiator);
 1264            _closingInitiator = false;
 1265            return SocketOperation.Write;
 266        }
 1267        else if (s >= StateClosingRequestPending)
 268        {
 0269            return SocketOperation.None;
 270        }
 271
 1272        _closingInitiator = initiator;
 1273        if (reason is Ice.CloseConnectionException)
 274        {
 1275            _closingReason = CLOSURE_NORMAL;
 276        }
 1277        else if (reason is Ice.ObjectAdapterDeactivatedException ||
 1278                reason is Ice.ObjectAdapterDestroyedException ||
 1279                reason is Ice.CommunicatorDestroyedException)
 280        {
 1281            _closingReason = CLOSURE_SHUTDOWN;
 282        }
 1283        else if (reason is Ice.ProtocolException)
 284        {
 0285            _closingReason = CLOSURE_PROTOCOL_ERROR;
 286        }
 1287        if (_state == StateOpened)
 288        {
 1289            _state = StateClosingRequestPending;
 1290            return initiator ? SocketOperation.Read : SocketOperation.Write;
 291        }
 292        else
 293        {
 0294            _nextState = StateClosingRequestPending;
 0295            return SocketOperation.None;
 296        }
 297    }
 298
 299    public void close()
 300    {
 1301        _delegate.close();
 1302        _state = StateClosed;
 303
 304        //
 305        // Clear the buffers now instead of waiting for destruction.
 306        //
 1307        if (!_readPending)
 308        {
 1309            _readBuffer.clear();
 310        }
 1311        if (!_writePending)
 312        {
 1313            _writeBuffer.clear();
 314        }
 1315    }
 316
 317    public EndpointI bind()
 318    {
 319        Debug.Assert(false);
 0320        return null;
 321    }
 322
 1323    public void destroy() => _delegate.destroy();
 324
 325    public int write(Buffer buf)
 326    {
 1327        if (_writePending)
 328        {
 0329            return SocketOperation.Write;
 330        }
 331
 1332        if (_state < StateOpened)
 333        {
 1334            if (_state < StateConnected)
 335            {
 1336                return _delegate.write(buf);
 337            }
 338            else
 339            {
 1340                return _delegate.write(_writeBuffer);
 341            }
 342        }
 343
 1344        int s = SocketOperation.None;
 345        do
 346        {
 1347            if (preWrite(buf))
 348            {
 1349                if (_writeState == WriteStateFlush)
 350                {
 351                    //
 352                    // Invoke write() even though there's nothing to write.
 353                    //
 354                    Debug.Assert(!buf.b.hasRemaining());
 0355                    s = _delegate.write(buf);
 356                }
 357
 1358                if (s == SocketOperation.None && _writeBuffer.b.hasRemaining())
 359                {
 1360                    s = _delegate.write(_writeBuffer);
 361                }
 1362                else if (s == SocketOperation.None && _incoming && !buf.empty() && _writeState == WriteStatePayload)
 363                {
 1364                    s = _delegate.write(buf);
 365                }
 366            }
 367        }
 1368        while (postWrite(buf, s));
 369
 1370        if (s != SocketOperation.None)
 371        {
 1372            return s;
 373        }
 1374        if (_state == StateClosingResponsePending && !_closingInitiator)
 375        {
 1376            return SocketOperation.Read;
 377        }
 1378        return SocketOperation.None;
 379    }
 380
 381    public int read(Buffer buf, ref bool hasMoreData)
 382    {
 1383        if (_readPending)
 384        {
 0385            return SocketOperation.Read;
 386        }
 387
 1388        if (_state < StateOpened)
 389        {
 1390            if (_state < StateConnected)
 391            {
 1392                return _delegate.read(buf, ref hasMoreData);
 393            }
 394            else
 395            {
 1396                if (_delegate.read(_readBuffer, ref hasMoreData) == SocketOperation.Write)
 397                {
 0398                    return SocketOperation.Write;
 399                }
 400                else
 401                {
 1402                    return SocketOperation.None;
 403                }
 404            }
 405        }
 406
 1407        if (!buf.b.hasRemaining())
 408        {
 1409            hasMoreData |= _readBufferPos < _readBuffer.b.position();
 1410            return SocketOperation.None;
 411        }
 412
 413        int s;
 414        do
 415        {
 1416            if (preRead(buf))
 417            {
 1418                if (_readState == ReadStatePayload)
 419                {
 420                    //
 421                    // If the payload length is smaller than what remains to be read, we read
 422                    // no more than the payload length. The remaining of the buffer will be
 423                    // sent over in another frame.
 424                    //
 1425                    int readSz = _readPayloadLength - (buf.b.position() - _readStart);
 1426                    if (buf.b.remaining() > readSz)
 427                    {
 0428                        int size = buf.size();
 0429                        buf.resize(buf.b.position() + readSz, true);
 0430                        s = _delegate.read(buf, ref hasMoreData);
 0431                        buf.resize(size, true);
 432                    }
 433                    else
 434                    {
 1435                        s = _delegate.read(buf, ref hasMoreData);
 436                    }
 437                }
 438                else
 439                {
 1440                    s = _delegate.read(_readBuffer, ref hasMoreData);
 441                }
 442
 1443                if (s == SocketOperation.Write)
 444                {
 0445                    postRead(buf);
 0446                    return s;
 447                }
 448            }
 449        }
 1450        while (postRead(buf));
 451
 1452        if (!buf.b.hasRemaining())
 453        {
 1454            hasMoreData |= _readBufferPos < _readBuffer.b.position();
 1455            s = SocketOperation.None;
 456        }
 457        else
 458        {
 1459            hasMoreData = false;
 1460            s = SocketOperation.Read;
 461        }
 462
 1463        if (((_state == StateClosingRequestPending && !_closingInitiator) ||
 1464            (_state == StateClosingResponsePending && _closingInitiator) ||
 1465            _state == StatePingPending ||
 1466            _state == StatePongPending) &&
 1467           _writeState == WriteStateHeader)
 468        {
 469            // We have things to write, ask to be notified when writes are ready.
 1470            s |= SocketOperation.Write;
 471        }
 472
 1473        return s;
 474    }
 475
 476    public bool startRead(Buffer buf, AsyncCallback callback, object state)
 477    {
 1478        _readPending = true;
 1479        if (_state < StateOpened)
 480        {
 1481            _finishRead = true;
 1482            if (_state < StateConnected)
 483            {
 1484                return _delegate.startRead(buf, callback, state);
 485            }
 486            else
 487            {
 1488                return _delegate.startRead(_readBuffer, callback, state);
 489            }
 490        }
 491
 1492        if (preRead(buf))
 493        {
 1494            _finishRead = true;
 1495            if (_readState == ReadStatePayload)
 496            {
 497                //
 498                // If the payload length is smaller than what remains to be read, we read
 499                // no more than the payload length. The remaining of the buffer will be
 500                // sent over in another frame.
 501                //
 1502                int readSz = _readPayloadLength - (buf.b.position() - _readStart);
 1503                if (buf.b.remaining() > readSz)
 504                {
 0505                    int size = buf.size();
 0506                    buf.resize(buf.b.position() + readSz, true);
 0507                    bool completedSynchronously = _delegate.startRead(buf, callback, state);
 0508                    buf.resize(size, true);
 0509                    return completedSynchronously;
 510                }
 511                else
 512                {
 1513                    return _delegate.startRead(buf, callback, state);
 514                }
 515            }
 516            else
 517            {
 1518                return _delegate.startRead(_readBuffer, callback, state);
 519            }
 520        }
 521        else
 522        {
 1523            return true;
 524        }
 525    }
 526
 527    public void finishRead(Buffer buf)
 528    {
 529        Debug.Assert(_readPending);
 1530        _readPending = false;
 531
 1532        if (_state < StateOpened)
 533        {
 534            Debug.Assert(_finishRead);
 1535            _finishRead = false;
 1536            if (_state < StateConnected)
 537            {
 1538                _delegate.finishRead(buf);
 539            }
 540            else
 541            {
 1542                _delegate.finishRead(_readBuffer);
 543            }
 1544            return;
 545        }
 546
 1547        if (!_finishRead)
 548        {
 549            // Nothing to do.
 550        }
 1551        else if (_readState == ReadStatePayload)
 552        {
 553            Debug.Assert(_finishRead);
 1554            _finishRead = false;
 1555            _delegate.finishRead(buf);
 556        }
 557        else
 558        {
 559            Debug.Assert(_finishRead);
 1560            _finishRead = false;
 1561            _delegate.finishRead(_readBuffer);
 562        }
 563
 1564        if (_state == StateClosed)
 565        {
 0566            _readBuffer.clear();
 0567            return;
 568        }
 569
 1570        postRead(buf);
 1571    }
 572
 573    public bool startWrite(Buffer buf, AsyncCallback callback, object state, out bool messageWritten)
 574    {
 1575        _writePending = true;
 1576        if (_state < StateOpened)
 577        {
 1578            if (_state < StateConnected)
 579            {
 1580                return _delegate.startWrite(buf, callback, state, out messageWritten);
 581            }
 582            else
 583            {
 1584                return _delegate.startWrite(_writeBuffer, callback, state, out messageWritten);
 585            }
 586        }
 587
 1588        if (preWrite(buf))
 589        {
 1590            if (_writeBuffer.b.hasRemaining())
 591            {
 1592                return _delegate.startWrite(_writeBuffer, callback, state, out messageWritten);
 593            }
 594            else
 595            {
 596                Debug.Assert(_incoming);
 1597                return _delegate.startWrite(buf, callback, state, out messageWritten);
 598            }
 599        }
 600        else
 601        {
 0602            messageWritten = true;
 0603            return false;
 604        }
 605    }
 606
 607    public void finishWrite(Buffer buf)
 608    {
 1609        _writePending = false;
 610
 1611        if (_state < StateOpened)
 612        {
 1613            if (_state < StateConnected)
 614            {
 1615                _delegate.finishWrite(buf);
 616            }
 617            else
 618            {
 1619                _delegate.finishWrite(_writeBuffer);
 620            }
 1621            return;
 622        }
 623
 1624        if (_writeBuffer.b.hasRemaining())
 625        {
 1626            _delegate.finishWrite(_writeBuffer);
 627        }
 1628        else if (!buf.empty() && buf.b.hasRemaining())
 629        {
 630            Debug.Assert(_incoming);
 1631            _delegate.finishWrite(buf);
 632        }
 633
 1634        if (_state == StateClosed)
 635        {
 0636            _writeBuffer.clear();
 0637            return;
 638        }
 639
 1640        postWrite(buf, SocketOperation.None);
 1641    }
 642
 1643    public string protocol() => _instance.protocol();
 644
 645    public ConnectionInfo getInfo(bool incoming, string adapterName, string connectionId) =>
 1646        new WSConnectionInfo(_delegate.getInfo(incoming, adapterName, connectionId), _parser.getHeaders());
 647
 1648    public void checkSendSize(Buffer buf) => _delegate.checkSendSize(buf);
 649
 1650    public void setBufferSize(int rcvSize, int sndSize) => _delegate.setBufferSize(rcvSize, sndSize);
 651
 1652    public override string ToString() => _delegate.ToString();
 653
 0654    public string toDetailedString() => _delegate.toDetailedString();
 655
 1656    internal
 1657    WSTransceiver(ProtocolInstance instance, Transceiver del, string host, string resource)
 658    {
 1659        init(instance, del);
 1660        _host = host;
 1661        _resource = resource;
 1662        _incoming = false;
 663
 664        //
 665        // Use a 16KB write buffer size. We use 16KB for the write
 666        // buffer size because all the data needs to be copied to the
 667        // write buffer for the purpose of masking. A 16KB buffer
 668        // appears to be a good compromise to reduce the number of
 669        // socket write calls and not consume too much memory.
 670        //
 1671        _writeBufferSize = 16 * 1024;
 672
 673        //
 674        // Write and read buffer size must be large enough to hold the frame header!
 675        //
 676        Debug.Assert(_writeBufferSize > 256);
 677        Debug.Assert(_readBufferSize > 256);
 1678    }
 679
 1680    internal WSTransceiver(ProtocolInstance instance, Transceiver del, HashSet<string> allowedOrigins)
 681    {
 1682        init(instance, del);
 1683        _host = "";
 1684        _resource = "";
 1685        _incoming = true;
 1686        _allowedOrigins = allowedOrigins;
 687
 688        //
 689        // Write and read buffer size must be large enough to hold the frame header!
 690        //
 691        Debug.Assert(_writeBufferSize > 256);
 692        Debug.Assert(_readBufferSize > 256);
 1693    }
 694
 695    // Returns true if the Connection header field - a comma-separated list of tokens, already trimmed and lowercased
 696    // by HttpParser.getHeader - includes the "upgrade" token required by RFC 6455 section 4.
 697    private static bool hasUpgradeToken(string connectionField) =>
 1698        connectionField.Split(',').Any(token => token.Trim().Equals("upgrade", StringComparison.Ordinal));
 699
 700    private void init(ProtocolInstance instance, Transceiver del)
 701    {
 1702        _instance = instance;
 1703        _delegate = del;
 1704        _state = StateInitializeDelegate;
 1705        _parser = new HttpParser();
 1706        _readState = ReadStateOpcode;
 1707        _readBuffer = new Buffer(ByteBuffer.ByteOrder.BigEndian); // Network byte order
 1708        _readBufferSize = 1024;
 1709        _readLastFrame = true;
 1710        _readOpCode = 0;
 1711        _readHeaderLength = 0;
 1712        _readPayloadLength = 0;
 1713        _writeState = WriteStateHeader;
 1714        _writeBuffer = new Buffer(ByteBuffer.ByteOrder.BigEndian); // Network byte order
 1715        _writeBufferSize = 1024;
 1716        _readPending = false;
 1717        _finishRead = false;
 1718        _writePending = false;
 1719        _readMask = new byte[4];
 1720        _writeMask = new byte[4];
 1721        _key = "";
 1722        _pingPayload = [];
 1723    }
 724
 725    private void handleRequest(Buffer responseBuffer)
 726    {
 727        //
 728        // The opening handshake must be a GET request (RFC 6455 section 4.1). We check the message type first
 729        // because method() (used just below) and uri() (used later) assert the parser holds a request.
 730        //
 1731        if (_parser.type() != HttpParser.Type.Request)
 732        {
 0733            throw new WebSocketException("WebSocket handshake is not an HTTP request");
 734        }
 1735        if (_parser.method() != "GET")
 736        {
 0737            throw new WebSocketException($"unsupported HTTP method '{_parser.method()}' for WebSocket handshake");
 738        }
 739
 740        //
 741        // HTTP/1.1
 742        //
 1743        if (_parser.versionMajor() != 1 || _parser.versionMinor() != 1)
 744        {
 0745            throw new WebSocketException("unsupported HTTP version");
 746        }
 747
 748        //
 749        // "An |Upgrade| header field containing the value 'websocket',
 750        //  treated as an ASCII case-insensitive value."
 751        //
 1752        string val = _parser.getHeader("Upgrade", true);
 1753        if (val == null)
 754        {
 0755            throw new WebSocketException("missing value for Upgrade field");
 756        }
 1757        else if (val != "websocket")
 758        {
 0759            throw new WebSocketException("invalid value '" + val + "' for Upgrade field");
 760        }
 761
 762        //
 763        // "A |Connection| header field that includes the token 'Upgrade',
 764        //  treated as an ASCII case-insensitive value.
 765        //
 1766        val = _parser.getHeader("Connection", true);
 1767        if (val == null)
 768        {
 0769            throw new WebSocketException("missing value for Connection field");
 770        }
 1771        else if (!hasUpgradeToken(val))
 772        {
 1773            throw new WebSocketException("invalid value '" + val + "' for Connection field");
 774        }
 775
 776        //
 777        // "A |Sec-WebSocket-Version| header field, with a value of 13."
 778        //
 1779        val = _parser.getHeader("Sec-WebSocket-Version", false);
 1780        if (val == null)
 781        {
 0782            throw new WebSocketException("missing value for WebSocket version");
 783        }
 1784        else if (val != "13")
 785        {
 0786            throw new WebSocketException("unsupported WebSocket version '" + val + "'");
 787        }
 788
 789        //
 790        // "Optionally, a |Sec-WebSocket-Protocol| header field, with a list
 791        //  of values indicating which protocols the client would like to
 792        //  speak, ordered by preference."
 793        //
 1794        bool addProtocol = false;
 1795        val = _parser.getHeader("Sec-WebSocket-Protocol", true);
 1796        if (val != null)
 797        {
 1798            string[] protocols = Ice.UtilInternal.StringUtil.splitString(val, ",") ??
 1799                throw new WebSocketException("invalid value '" + val + "' for WebSocket protocol");
 1800            foreach (string p in protocols)
 801            {
 1802                if (!p.Trim().Equals(_iceProtocol, StringComparison.Ordinal))
 803                {
 0804                    throw new WebSocketException("unknown value '" + p + "' for WebSocket protocol");
 805                }
 1806                addProtocol = true;
 807            }
 808        }
 809
 810        //
 811        // "A |Sec-WebSocket-Key| header field with a base64-encoded
 812        //  value that, when decoded, is 16 bytes in length."
 813        //
 1814        string key = _parser.getHeader("Sec-WebSocket-Key", false) ??
 1815            throw new WebSocketException("missing value for WebSocket key");
 816        try
 817        {
 1818            byte[] decodedKey = Convert.FromBase64String(key);
 1819            if (decodedKey.Length != 16)
 820            {
 0821                throw new WebSocketException("WebSocket key '" + key + "' has invalid length");
 822            }
 1823        }
 0824        catch (FormatException)
 825        {
 0826            throw new WebSocketException("invalid base64 value '" + key + "' for WebSocket key");
 827        }
 828
 829        //
 830        // Optionally validate the Origin header against the adapter's allowed-origins list.
 831        // Browsers always send Origin; non-browser clients do not, so an absent header bypasses the check.
 832        // A wildcard ("*") allowlist is normalized to an empty set at parse time, so empty here means "no enforcement".
 833        //
 1834        if (_allowedOrigins.Count > 0)
 835        {
 1836            string origin = _parser.getHeader("Origin", false);
 1837            if (origin is not null)
 838            {
 839                string canonical;
 840                try
 841                {
 1842                    canonical = canonicalizeOrigin(origin.Trim());
 1843                }
 1844                catch (ArgumentException)
 845                {
 1846                    throw new WebSocketException($"invalid Origin header '{origin}'");
 847                }
 1848                if (!_allowedOrigins.Contains(canonical))
 849                {
 1850                    throw new WebSocketException($"origin '{origin}' is not allowed");
 851                }
 852            }
 853        }
 854
 855        //
 856        // Retain the target resource.
 857        //
 1858        _resource = _parser.uri();
 859
 860        //
 861        // Compose the response.
 862        //
 1863        var @out = new StringBuilder();
 1864        @out.Append("HTTP/1.1 101 Switching Protocols\r\n");
 1865        @out.Append("Upgrade: websocket\r\n");
 1866        @out.Append("Connection: Upgrade\r\n");
 1867        if (addProtocol)
 868        {
 1869            @out.Append("Sec-WebSocket-Protocol: " + _iceProtocol + "\r\n");
 870        }
 871
 872        //
 873        // The response includes:
 874        //
 875        // "A |Sec-WebSocket-Accept| header field.  The value of this
 876        //  header field is constructed by concatenating /key/, defined
 877        //  above in step 4 in Section 4.2.2, with the string "258EAFA5-
 878        //  E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this
 879        //  concatenated value to obtain a 20-byte value and base64-
 880        //  encoding (see Section 4 of [RFC4648]) this 20-byte hash.
 881        //
 1882        @out.Append("Sec-WebSocket-Accept: ");
 1883        string input = key + _wsUUID;
 884#pragma warning disable CA5350 // SHA1 is used for compatibility with the WebSocket protocol
 1885        using var sha1 = SHA1.Create();
 1886        byte[] hash = sha1.ComputeHash(_utf8.GetBytes(input));
 887#pragma warning restore CA5350
 1888        @out.Append(Convert.ToBase64String(hash) + "\r\n\r\n"); // EOM
 889
 1890        byte[] bytes = _utf8.GetBytes(@out.ToString());
 891        Debug.Assert(bytes.Length == @out.Length);
 1892        responseBuffer.resize(bytes.Length, false);
 1893        responseBuffer.b.position(0);
 1894        responseBuffer.b.put(bytes);
 1895        responseBuffer.b.flip();
 1896    }
 897
 898    private void handleResponse()
 899    {
 900        string val;
 901
 902        //
 903        // HTTP/1.1
 904        //
 1905        if (_parser.versionMajor() != 1 || _parser.versionMinor() != 1)
 906        {
 0907            throw new WebSocketException("unsupported HTTP version");
 908        }
 909
 910        //
 911        // "If the status code received from the server is not 101, the
 912        //  client handles the response per HTTP [RFC2616] procedures.  In
 913        //  particular, the client might perform authentication if it
 914        //  receives a 401 status code; the server might redirect the client
 915        //  using a 3xx status code (but clients are not required to follow
 916        //  them), etc."
 917        //
 1918        if (_parser.status() != 101)
 919        {
 0920            var @out = new StringBuilder("unexpected status value " + _parser.status());
 0921            if (_parser.reason().Length > 0)
 922            {
 0923                @out.Append(":\n" + _parser.reason());
 924            }
 0925            throw new WebSocketException(@out.ToString());
 926        }
 927
 928        //
 929        // "If the response lacks an |Upgrade| header field or the |Upgrade|
 930        //  header field contains a value that is not an ASCII case-
 931        //  insensitive match for the value "websocket", the client MUST
 932        //  _Fail the WebSocket Connection_."
 933        //
 1934        val = _parser.getHeader("Upgrade", true);
 1935        if (val == null)
 936        {
 0937            throw new WebSocketException("missing value for Upgrade field");
 938        }
 1939        else if (val != "websocket")
 940        {
 0941            throw new WebSocketException("invalid value '" + val + "' for Upgrade field");
 942        }
 943
 944        //
 945        // "If the response lacks a |Connection| header field or the
 946        //  |Connection| header field doesn't contain a token that is an
 947        //  ASCII case-insensitive match for the value "Upgrade", the client
 948        //  MUST _Fail the WebSocket Connection_."
 949        //
 1950        val = _parser.getHeader("Connection", true);
 1951        if (val == null)
 952        {
 0953            throw new WebSocketException("missing value for Connection field");
 954        }
 1955        else if (!hasUpgradeToken(val))
 956        {
 0957            throw new WebSocketException("invalid value '" + val + "' for Connection field");
 958        }
 959
 960        //
 961        // "If the response includes a |Sec-WebSocket-Protocol| header field
 962        //  and this header field indicates the use of a subprotocol that was
 963        //  not present in the client's handshake (the server has indicated a
 964        //  subprotocol not requested by the client), the client MUST _Fail
 965        //  the WebSocket Connection_."
 966        //
 1967        val = _parser.getHeader("Sec-WebSocket-Protocol", true);
 1968        if (val != null && !val.Equals(_iceProtocol, StringComparison.Ordinal))
 969        {
 0970            throw new WebSocketException("invalid value '" + val + "' for WebSocket protocol");
 971        }
 972
 973        //
 974        // "If the response lacks a |Sec-WebSocket-Accept| header field or
 975        //  the |Sec-WebSocket-Accept| contains a value other than the
 976        //  base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
 977        //  Key| (as a string, not base64-decoded) with the string "258EAFA5-
 978        //  E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
 979        //  trailing whitespace, the client MUST _Fail the WebSocket
 980        //  Connection_."
 981        //
 1982        val = _parser.getHeader("Sec-WebSocket-Accept", false) ??
 1983            throw new WebSocketException("missing value for Sec-WebSocket-Accept");
 1984        string input = _key + _wsUUID;
 985#pragma warning disable CA5350 // SHA1 is used for compatibility with the WebSocket protocol
 1986        using var sha1 = SHA1.Create();
 1987        byte[] hash = sha1.ComputeHash(_utf8.GetBytes(input));
 988#pragma warning restore CA5350
 1989        if (!val.Equals(Convert.ToBase64String(hash), StringComparison.Ordinal))
 990        {
 0991            throw new WebSocketException("invalid value '" + val + "' for Sec-WebSocket-Accept");
 992        }
 1993    }
 994
 995    private bool preRead(Buffer buf)
 996    {
 997        while (true)
 998        {
 1999            if (_readState == ReadStateOpcode)
 1000            {
 1001                //
 1002                // Is there enough data available to read the opcode?
 1003                //
 11004                if (!readBuffered(2))
 1005                {
 11006                    return true;
 1007                }
 1008
 1009                //
 1010                // Most-significant bit indicates whether this is the
 1011                // last frame. Least-significant four bits hold the
 1012                // opcode.
 1013                //
 11014                int ch = _readBuffer.b.get(_readBufferPos++);
 11015                _readOpCode = ch & 0xf;
 1016
 1017                //
 1018                // No extension is negotiated, so the RSV1, RSV2, and RSV3 bits must all be 0.
 1019                //
 11020                if ((ch & 0x70) != 0)
 1021                {
 01022                    throw new Ice.ProtocolException("invalid WebSocket frame: RSV bits must be 0");
 1023                }
 1024
 11025                bool finalFrame = (ch & FLAG_FINAL) == FLAG_FINAL;
 1026
 1027                //
 1028                // Remember if last frame if we're going to read a data or
 1029                // continuation frame, this is only for protocol
 1030                // correctness checking purpose.
 1031                //
 11032                if (_readOpCode == OP_DATA)
 1033                {
 11034                    if (!_readLastFrame)
 1035                    {
 01036                        throw new Ice.ProtocolException("invalid data frame, no FIN on previous frame");
 1037                    }
 11038                    _readLastFrame = finalFrame;
 1039                }
 11040                else if (_readOpCode == OP_CONT)
 1041                {
 01042                    if (_readLastFrame)
 1043                    {
 01044                        throw new Ice.ProtocolException("invalid continuation frame, previous frame FIN set");
 1045                    }
 01046                    _readLastFrame = finalFrame;
 1047                }
 1048
 11049                ch = _readBuffer.b.get(_readBufferPos++);
 1050
 1051                //
 1052                // Check the MASK bit. Messages sent by a client must be masked;
 1053                // messages sent by a server must not be masked.
 1054                //
 11055                bool masked = (ch & FLAG_MASKED) == FLAG_MASKED;
 11056                if (masked != _incoming)
 1057                {
 01058                    throw new Ice.ProtocolException("invalid masking");
 1059                }
 1060
 1061                //
 1062                // Extract the payload length, which can have the following values:
 1063                //
 1064                // 0-125: The payload length
 1065                // 126:   The subsequent two bytes contain the payload length
 1066                // 127:   The subsequent eight bytes contain the payload length
 1067                //
 11068                _readPayloadLength = ch & 0x7f;
 1069
 1070                //
 1071                // RFC 6455 section 5.5: control frames (close, ping, and pong) must not be fragmented
 1072                // and must have a payload length of 125 bytes or less - they cannot use the 16-bit
 1073                // or 64-bit extended length encoding. Enforce this before allocating any payload
 1074                // buffer.
 1075                //
 11076                if (_readOpCode == OP_CLOSE || _readOpCode == OP_PING || _readOpCode == OP_PONG)
 1077                {
 11078                    if (!finalFrame)
 1079                    {
 01080                        throw new Ice.ProtocolException("invalid WebSocket control frame: the FIN bit is not set");
 1081                    }
 11082                    if (_readPayloadLength > 125)
 1083                    {
 01084                        throw new Ice.ProtocolException(
 01085                            "invalid WebSocket control frame: the payload length exceeds 125 bytes");
 1086                    }
 1087                }
 1088
 11089                if (_readPayloadLength < 126)
 1090                {
 11091                    _readHeaderLength = 0;
 1092                }
 11093                else if (_readPayloadLength == 126)
 1094                {
 11095                    _readHeaderLength = 2; // Need to read a 16-bit payload length.
 1096                }
 1097                else
 1098                {
 11099                    _readHeaderLength = 8; // Need to read a 64-bit payload length.
 1100                }
 11101                if (masked)
 1102                {
 11103                    _readHeaderLength += 4; // Need to read a 32-bit mask.
 1104                }
 1105
 11106                _readState = ReadStateHeader;
 1107            }
 1108
 11109            if (_readState == ReadStateHeader)
 1110            {
 1111                //
 1112                // Is there enough data available to read the header?
 1113                //
 11114                if (_readHeaderLength > 0 && !readBuffered(_readHeaderLength))
 1115                {
 11116                    return true;
 1117                }
 1118
 11119                if (_readPayloadLength == 126)
 1120                {
 11121                    _readPayloadLength = _readBuffer.b.getShort(_readBufferPos); // Uses network byte order.
 11122                    if (_readPayloadLength < 0)
 1123                    {
 01124                        _readPayloadLength += 65536;
 1125                    }
 11126                    _readBufferPos += 2;
 1127                }
 11128                else if (_readPayloadLength == 127)
 1129                {
 11130                    long l = _readBuffer.b.getLong(_readBufferPos); // Uses network byte order.
 11131                    _readBufferPos += 8;
 11132                    if (l < 0 || l > int.MaxValue)
 1133                    {
 01134                        throw new Ice.ProtocolException("invalid WebSocket payload length: " + l);
 1135                    }
 11136                    _readPayloadLength = (int)l;
 1137                }
 1138
 1139                //
 1140                // Read the mask if this is an incoming connection.
 1141                //
 11142                if (_incoming)
 1143                {
 1144                    //
 1145                    // We must have needed to read the mask.
 1146                    //
 1147                    Debug.Assert(_readBuffer.b.position() - _readBufferPos >= 4);
 11148                    for (int i = 0; i < 4; ++i)
 1149                    {
 11150                        _readMask[i] = _readBuffer.b.get(_readBufferPos++); // Copy the mask.
 1151                    }
 1152                }
 1153
 11154                switch (_readOpCode)
 1155                {
 1156                    case OP_TEXT: // Text frame
 1157                    {
 01158                        throw new Ice.ProtocolException("text frames not supported");
 1159                    }
 1160                    case OP_DATA: // Data frame
 1161                    case OP_CONT: // Continuation frame
 1162                    {
 11163                        if (_instance.traceLevel() >= 2)
 1164                        {
 11165                            _instance.logger().trace(
 11166                                _instance.traceCategory(), "received " + protocol() +
 11167                                (_readOpCode == OP_DATA ? " data" : " continuation") +
 11168                                " frame with payload length of " + _readPayloadLength +
 11169                                " bytes\n" + ToString());
 1170                        }
 1171
 11172                        if (_readPayloadLength <= 0)
 1173                        {
 01174                            throw new Ice.ProtocolException("payload length is 0");
 1175                        }
 11176                        _readState = ReadStatePayload;
 1177                        Debug.Assert(buf.b.hasRemaining());
 11178                        _readFrameStart = buf.b.position();
 11179                        break;
 1180                    }
 1181                    case OP_CLOSE: // Connection close
 1182                    {
 11183                        if (_instance.traceLevel() >= 2)
 1184                        {
 11185                            _instance.logger().trace(
 11186                                _instance.traceCategory(),
 11187                                "received " + protocol() + " connection close frame\n" + ToString());
 1188                        }
 1189
 11190                        _readState = ReadStateControlFrame;
 11191                        int s = _nextState == StateOpened ? _state : _nextState;
 11192                        if (s == StateClosingRequestPending)
 1193                        {
 1194                            //
 1195                            // If we receive a close frame while we were actually
 1196                            // waiting to send one, change the role and send a
 1197                            // close frame response.
 1198                            //
 11199                            if (!_closingInitiator)
 1200                            {
 01201                                _closingInitiator = true;
 1202                            }
 11203                            if (_state == StateClosingRequestPending)
 1204                            {
 11205                                _state = StateClosingResponsePending;
 1206                            }
 1207                            else
 1208                            {
 01209                                _nextState = StateClosingResponsePending;
 1210                            }
 11211                            return false; // No longer interested in reading
 1212                        }
 1213                        else
 1214                        {
 11215                            throw new Ice.ConnectionLostException(peerAddress: null);
 1216                        }
 1217                    }
 1218                    case OP_PING:
 1219                    {
 11220                        if (_instance.traceLevel() >= 2)
 1221                        {
 01222                            _instance.logger().trace(
 01223                                _instance.traceCategory(),
 01224                                "received " + protocol() + " connection ping frame\n" + ToString());
 1225                        }
 11226                        _readState = ReadStateControlFrame;
 11227                        break;
 1228                    }
 1229                    case OP_PONG: // Pong
 1230                    {
 01231                        if (_instance.traceLevel() >= 2)
 1232                        {
 01233                            _instance.logger().trace(
 01234                                _instance.traceCategory(),
 01235                                "received " + protocol() + " connection pong frame\n" + ToString());
 1236                        }
 01237                        _readState = ReadStateControlFrame;
 01238                        break;
 1239                    }
 1240                    default:
 1241                    {
 01242                        throw new Ice.ProtocolException("unsupported opcode: " + _readOpCode);
 1243                    }
 1244                }
 1245            }
 1246
 11247            if (_readState == ReadStateControlFrame)
 1248            {
 11249                if (_readPayloadLength > 0 && !readBuffered(_readPayloadLength))
 1250                {
 01251                    return true;
 1252                }
 1253
 11254                if (_readPayloadLength > 0 && _readOpCode == OP_PING)
 1255                {
 11256                    _pingPayload = new byte[_readPayloadLength];
 11257                    System.Buffer.BlockCopy(
 11258                        _readBuffer.b.rawBytes(),
 11259                        _readBufferPos,
 11260                        _pingPayload,
 11261                        0,
 11262                        _readPayloadLength);
 11263                    if (_incoming)
 1264                    {
 1265                        // A client masks its frames (RFC 6455 section 5.3); unmask the ping payload so the echoed
 1266                        // pong matches the ping (RFC 6455 section 5.5.3), like the data path does in postRead.
 11267                        for (int i = 0; i < _pingPayload.Length; ++i)
 1268                        {
 11269                            _pingPayload[i] = (byte)(_pingPayload[i] ^ _readMask[i % 4]);
 1270                        }
 1271                    }
 1272                }
 1273
 11274                _readBufferPos += _readPayloadLength;
 11275                _readPayloadLength = 0;
 1276
 11277                if (_readOpCode == OP_PING)
 1278                {
 11279                    if (_state == StateOpened)
 1280                    {
 11281                        _state = StatePongPending; // Send pong frame now
 1282                    }
 01283                    else if (_nextState < StatePongPending)
 1284                    {
 01285                        _nextState = StatePongPending; // Send pong frame next
 1286                    }
 1287                }
 1288
 1289                //
 1290                // We've read the payload of the PING/PONG frame, we're ready
 1291                // to read a new frame.
 1292                //
 11293                _readState = ReadStateOpcode;
 1294            }
 1295
 11296            if (_readState == ReadStatePayload)
 1297            {
 1298                //
 1299                // This must be assigned before the check for the buffer. If the buffer is empty
 1300                // or already read, postRead will return false.
 1301                //
 11302                _readStart = buf.b.position();
 1303
 11304                if (buf.empty() || !buf.b.hasRemaining())
 1305                {
 01306                    return false;
 1307                }
 1308
 11309                int n = Math.Min(_readBuffer.b.position() - _readBufferPos, buf.b.remaining());
 11310                if (n > _readPayloadLength)
 1311                {
 01312                    n = _readPayloadLength;
 1313                }
 11314                if (n > 0)
 1315                {
 11316                    System.Buffer.BlockCopy(
 11317                        _readBuffer.b.rawBytes(),
 11318                        _readBufferPos,
 11319                        buf.b.rawBytes(),
 11320                        buf.b.position(),
 11321                        n);
 11322                    buf.b.position(buf.b.position() + n);
 11323                    _readBufferPos += n;
 1324                }
 1325
 1326                //
 1327                // Continue reading if we didn't read the full message, otherwise give back
 1328                // the control to the connection
 1329                //
 11330                return buf.b.hasRemaining() && n < _readPayloadLength;
 1331            }
 1332        }
 1333    }
 1334
 1335    private bool postRead(Buffer buf)
 1336    {
 11337        if (_readState != ReadStatePayload)
 1338        {
 11339            return _readStart < _readBuffer.b.position(); // Returns true if data was read.
 1340        }
 1341
 11342        if (_readStart == buf.b.position())
 1343        {
 11344            return false; // Nothing was read or nothing to read.
 1345        }
 1346        Debug.Assert(_readStart < buf.b.position());
 1347
 11348        if (_incoming)
 1349        {
 1350            //
 1351            // Unmask the data we just read.
 1352            //
 11353            int pos = buf.b.position();
 11354            byte[] arr = buf.b.rawBytes();
 11355            for (int n = _readStart; n < pos; ++n)
 1356            {
 11357                arr[n] = (byte)(arr[n] ^ _readMask[(n - _readFrameStart) % 4]);
 1358            }
 1359        }
 1360
 11361        _readPayloadLength -= buf.b.position() - _readStart;
 11362        _readStart = buf.b.position();
 11363        if (_readPayloadLength == 0)
 1364        {
 1365            //
 1366            // We've read the complete payload, we're ready to read a new frame.
 1367            //
 11368            _readState = ReadStateOpcode;
 1369        }
 11370        return buf.b.hasRemaining();
 1371    }
 1372
 1373    private bool preWrite(Buffer buf)
 1374    {
 11375        if (_writeState == WriteStateHeader)
 1376        {
 11377            if (_state == StateOpened)
 1378            {
 11379                if (buf.empty() || !buf.b.hasRemaining())
 1380                {
 11381                    return false;
 1382                }
 1383
 1384                Debug.Assert(buf.b.position() == 0);
 11385                prepareWriteHeader((byte)OP_DATA, buf.size());
 1386
 11387                _writeState = WriteStatePayload;
 1388            }
 11389            else if (_state == StatePingPending)
 1390            {
 01391                prepareWriteHeader((byte)OP_PING, 0); // Don't send any payload
 1392
 01393                _writeState = WriteStateControlFrame;
 01394                _writeBuffer.b.flip();
 1395            }
 11396            else if (_state == StatePongPending)
 1397            {
 11398                prepareWriteHeader((byte)OP_PONG, _pingPayload.Length);
 11399                if (_pingPayload.Length > _writeBuffer.b.remaining())
 1400                {
 01401                    int pos = _writeBuffer.b.position();
 01402                    _writeBuffer.resize(pos + _pingPayload.Length, false);
 01403                    _writeBuffer.b.position(pos);
 1404                }
 11405                if (_incoming)
 1406                {
 1407                    // Server-to-client frames are not masked (RFC 6455 section 5.1).
 11408                    _writeBuffer.b.put(_pingPayload);
 1409                }
 1410                else
 1411                {
 1412                    // Client-to-server frames are masked with _writeMask, like the data and close paths;
 1413                    // prepareWriteHeader set FLAG_MASKED, so the payload must be masked to match (RFC 6455 5.5.3).
 01414                    for (int i = 0; i < _pingPayload.Length; ++i)
 1415                    {
 01416                        _writeBuffer.b.put((byte)(_pingPayload[i] ^ _writeMask[i % 4]));
 1417                    }
 1418                }
 11419                _pingPayload = [];
 1420
 11421                _writeState = WriteStateControlFrame;
 11422                _writeBuffer.b.flip();
 1423            }
 11424            else if ((_state == StateClosingRequestPending && !_closingInitiator) ||
 11425                    (_state == StateClosingResponsePending && _closingInitiator))
 1426            {
 11427                prepareWriteHeader((byte)OP_CLOSE, 2);
 1428
 1429                // Write closing reason
 11430                _writeBuffer.b.putShort((short)_closingReason);
 1431
 11432                if (!_incoming)
 1433                {
 1434                    byte b;
 11435                    int pos = _writeBuffer.b.position() - 2;
 11436                    b = (byte)(_writeBuffer.b.get(pos) ^ _writeMask[0]);
 11437                    _writeBuffer.b.put(pos, b);
 11438                    pos++;
 11439                    b = (byte)(_writeBuffer.b.get(pos) ^ _writeMask[1]);
 11440                    _writeBuffer.b.put(pos, b);
 1441                }
 1442
 11443                _writeState = WriteStateControlFrame;
 11444                _writeBuffer.b.flip();
 1445            }
 1446            else
 1447            {
 1448                Debug.Assert(_state != StateClosed);
 11449                return false; // Nothing to write in this state
 1450            }
 1451
 11452            _writePayloadLength = 0;
 1453        }
 1454
 11455        if (_writeState == WriteStatePayload)
 1456        {
 1457            //
 1458            // For an outgoing connection, each message must be masked with a random
 1459            // 32-bit value, so we copy the entire message into the internal buffer
 1460            // for writing. For incoming connections, we just copy the start of the
 1461            // message in the internal buffer after the header. If the message is
 1462            // larger, the reminder is sent directly from the message buffer to avoid
 1463            // copying.
 1464            //
 11465            if (!_incoming && (_writePayloadLength == 0 || !_writeBuffer.b.hasRemaining()))
 1466            {
 11467                if (!_writeBuffer.b.hasRemaining())
 1468                {
 11469                    _writeBuffer.b.position(0);
 1470                }
 1471
 11472                int n = buf.b.position();
 11473                int sz = buf.size();
 11474                int pos = _writeBuffer.b.position();
 11475                int count = Math.Min(sz - n, _writeBuffer.b.remaining());
 11476                byte[] src = buf.b.rawBytes();
 11477                byte[] dest = _writeBuffer.b.rawBytes();
 11478                for (int i = 0; i < count; ++i, ++n, ++pos)
 1479                {
 11480                    dest[pos] = (byte)(src[n] ^ _writeMask[n % 4]);
 1481                }
 11482                _writeBuffer.b.position(pos);
 11483                _writePayloadLength = n;
 1484
 11485                _writeBuffer.b.flip();
 1486            }
 11487            else if (_writePayloadLength == 0)
 1488            {
 1489                Debug.Assert(_incoming);
 11490                if (_writeBuffer.b.hasRemaining())
 1491                {
 1492                    Debug.Assert(buf.b.position() == 0);
 11493                    int n = Math.Min(_writeBuffer.b.remaining(), buf.b.remaining());
 11494                    int pos = _writeBuffer.b.position();
 11495                    System.Buffer.BlockCopy(buf.b.rawBytes(), 0, _writeBuffer.b.rawBytes(), pos, n);
 11496                    _writeBuffer.b.position(pos + n);
 11497                    _writePayloadLength = n;
 1498                }
 11499                _writeBuffer.b.flip();
 1500            }
 11501            return true;
 1502        }
 11503        else if (_writeState == WriteStateControlFrame)
 1504        {
 11505            return _writeBuffer.b.hasRemaining();
 1506        }
 1507        else
 1508        {
 1509            Debug.Assert(_writeState == WriteStateFlush);
 01510            return true;
 1511        }
 1512    }
 1513
 1514    private bool postWrite(Buffer buf, int status)
 1515    {
 11516        if (_state > StateOpened && _writeState == WriteStateControlFrame)
 1517        {
 11518            if (!_writeBuffer.b.hasRemaining())
 1519            {
 11520                if (_state == StatePingPending)
 1521                {
 01522                    if (_instance.traceLevel() >= 2)
 1523                    {
 01524                        _instance.logger().trace(
 01525                            _instance.traceCategory(),
 01526                            "sent " + protocol() + " connection ping frame\n" + ToString());
 1527                    }
 1528                }
 11529                else if (_state == StatePongPending)
 1530                {
 11531                    if (_instance.traceLevel() >= 2)
 1532                    {
 01533                        _instance.logger().trace(
 01534                            _instance.traceCategory(),
 01535                            "sent " + protocol() + " connection pong frame\n" + ToString());
 1536                    }
 1537                }
 11538                else if ((_state == StateClosingRequestPending && !_closingInitiator) ||
 11539                        (_state == StateClosingResponsePending && _closingInitiator))
 1540                {
 11541                    if (_instance.traceLevel() >= 2)
 1542                    {
 11543                        _instance.logger().trace(
 11544                            _instance.traceCategory(),
 11545                            "sent " + protocol() + " connection close frame\n" + ToString());
 1546                    }
 1547
 11548                    if (_state == StateClosingRequestPending && !_closingInitiator)
 1549                    {
 11550                        _writeState = WriteStateHeader;
 11551                        _state = StateClosingResponsePending;
 11552                        return false;
 1553                    }
 1554                    else
 1555                    {
 11556                        throw new Ice.ConnectionLostException(peerAddress: null);
 1557                    }
 1558                }
 01559                else if (_state == StateClosed)
 1560                {
 01561                    return false;
 1562                }
 1563
 11564                _state = _nextState;
 11565                _nextState = StateOpened;
 11566                _writeState = WriteStateHeader;
 1567            }
 1568            else
 1569            {
 11570                return status == SocketOperation.None;
 1571            }
 1572        }
 1573
 11574        if ((!_incoming || buf.b.position() == 0) && _writePayloadLength > 0)
 1575        {
 11576            if (!_writeBuffer.b.hasRemaining())
 1577            {
 11578                buf.b.position(_writePayloadLength);
 1579            }
 1580        }
 1581
 11582        if (status == SocketOperation.Write && !buf.b.hasRemaining() && !_writeBuffer.b.hasRemaining())
 1583        {
 1584            //
 1585            // Our buffers are empty but the delegate needs another call to write().
 1586            //
 01587            _writeState = WriteStateFlush;
 01588            return false;
 1589        }
 11590        else if (!buf.b.hasRemaining())
 1591        {
 11592            _writeState = WriteStateHeader;
 11593            if (_state == StatePingPending ||
 11594               _state == StatePongPending ||
 11595               (_state == StateClosingRequestPending && !_closingInitiator) ||
 11596               (_state == StateClosingResponsePending && _closingInitiator))
 1597            {
 11598                return true;
 1599            }
 1600        }
 11601        else if (_state == StateOpened)
 1602        {
 11603            return status == SocketOperation.None;
 1604        }
 1605
 11606        return false;
 1607    }
 1608
 1609    private bool readBuffered(int sz)
 1610    {
 11611        if (_readBufferPos == _readBuffer.b.position())
 1612        {
 11613            _readBuffer.resize(_readBufferSize, true);
 11614            _readBufferPos = 0;
 11615            _readBuffer.b.position(0);
 1616        }
 1617        else
 1618        {
 11619            int available = _readBuffer.b.position() - _readBufferPos;
 11620            if (available < sz)
 1621            {
 11622                if (_readBufferPos > 0)
 1623                {
 11624                    _readBuffer.b.limit(_readBuffer.b.position());
 11625                    _readBuffer.b.position(_readBufferPos);
 11626                    _readBuffer.b.compact();
 1627                    Debug.Assert(_readBuffer.b.position() == available);
 1628                }
 11629                _readBuffer.resize(Math.Max(_readBufferSize, sz), true);
 11630                _readBufferPos = 0;
 11631                _readBuffer.b.position(available);
 1632            }
 1633        }
 1634
 11635        _readStart = _readBuffer.b.position();
 11636        if (_readBufferPos + sz > _readBuffer.b.position())
 1637        {
 11638            return false; // Not enough read.
 1639        }
 1640        Debug.Assert(_readBuffer.b.position() > _readBufferPos);
 11641        return true;
 1642    }
 1643
 1644    private void prepareWriteHeader(byte opCode, int payloadLength)
 1645    {
 1646        //
 1647        // We need to prepare the frame header.
 1648        //
 11649        _writeBuffer.resize(_writeBufferSize, false);
 11650        _writeBuffer.b.limit(_writeBufferSize);
 11651        _writeBuffer.b.position(0);
 1652
 1653        //
 1654        // Set the opcode - this is the one and only data frame.
 1655        //
 11656        _writeBuffer.b.put((byte)(opCode | FLAG_FINAL));
 1657
 1658        //
 1659        // Set the payload length.
 1660        //
 11661        if (payloadLength <= 125)
 1662        {
 11663            _writeBuffer.b.put((byte)payloadLength);
 1664        }
 11665        else if (payloadLength > 125 && payloadLength <= 65535)
 1666        {
 1667            //
 1668            // Use an extra 16 bits to encode the payload length.
 1669            //
 11670            _writeBuffer.b.put(126);
 11671            _writeBuffer.b.putShort((short)payloadLength);
 1672        }
 11673        else if (payloadLength > 65535)
 1674        {
 1675            //
 1676            // Use an extra 64 bits to encode the payload length.
 1677            //
 11678            _writeBuffer.b.put(127);
 11679            _writeBuffer.b.putLong(payloadLength);
 1680        }
 1681
 11682        if (!_incoming)
 1683        {
 1684            //
 1685            // Add a random 32-bit mask to every outgoing frame, copy the payload data,
 1686            // and apply the mask.
 1687            //
 11688            _writeBuffer.b.put(1, (byte)(_writeBuffer.b.get(1) | FLAG_MASKED));
 11689            RandomNumberGenerator.Fill(_writeMask);
 11690            _writeBuffer.b.put(_writeMask);
 1691        }
 11692    }
 1693
 1694    private ProtocolInstance _instance;
 1695    private Transceiver _delegate;
 1696    private readonly string _host;
 1697    private string _resource;
 1698    private readonly bool _incoming;
 11699    private readonly HashSet<string> _allowedOrigins = new HashSet<string>();
 1700
 1701    private const int StateInitializeDelegate = 0;
 1702    private const int StateConnected = 1;
 1703    private const int StateUpgradeRequestPending = 2;
 1704    private const int StateUpgradeResponsePending = 3;
 1705    private const int StateOpened = 4;
 1706    private const int StatePingPending = 5;
 1707    private const int StatePongPending = 6;
 1708    private const int StateClosingRequestPending = 7;
 1709    private const int StateClosingResponsePending = 8;
 1710    private const int StateClosed = 9;
 1711
 1712    private int _state;
 1713    private int _nextState;
 1714
 1715    private HttpParser _parser;
 1716    private string _key;
 1717
 1718    private const int ReadStateOpcode = 0;
 1719    private const int ReadStateHeader = 1;
 1720    private const int ReadStateControlFrame = 2;
 1721    private const int ReadStatePayload = 3;
 1722
 1723    private int _readState;
 1724    private Buffer _readBuffer;
 1725    private int _readBufferPos;
 1726    private int _readBufferSize;
 1727
 1728    private bool _readLastFrame;
 1729    private int _readOpCode;
 1730    private int _readHeaderLength;
 1731    private int _readPayloadLength;
 1732    private int _readStart;
 1733    private int _readFrameStart;
 1734    private byte[] _readMask;
 1735
 1736    private const int WriteStateHeader = 0;
 1737    private const int WriteStatePayload = 1;
 1738    private const int WriteStateControlFrame = 2;
 1739    private const int WriteStateFlush = 3;
 1740
 1741    private int _writeState;
 1742    private Buffer _writeBuffer;
 1743    private int _writeBufferSize;
 1744    private byte[] _writeMask;
 1745    private int _writePayloadLength;
 1746
 1747    private bool _closingInitiator;
 1748    private int _closingReason;
 1749
 1750    private bool _readPending;
 1751    private bool _finishRead;
 1752    private bool _writePending;
 1753
 1754    private byte[] _pingPayload;
 1755
 1756    //
 1757    // WebSocket opcodes
 1758    //
 1759    private const int OP_CONT = 0x0;    // Continuation frame
 1760    private const int OP_TEXT = 0x1;    // Text frame
 1761    private const int OP_DATA = 0x2;    // Data frame
 1762    // private const int OP_RES_0x3 = 0x3;    // Reserved
 1763    // private const int OP_RES_0x4 = 0x4;    // Reserved
 1764    // private const int OP_RES_0x5 = 0x5;    // Reserved
 1765    // private const int OP_RES_0x6 = 0x6;    // Reserved
 1766    // private const int OP_RES_0x7 = 0x7;    // Reserved
 1767    private const int OP_CLOSE = 0x8;    // Connection close
 1768    private const int OP_PING = 0x9;    // Ping
 1769    private const int OP_PONG = 0xA;    // Pong
 1770    // private const int OP_RES_0xB = 0xB;    // Reserved
 1771    // private const int OP_RES_0xC = 0xC;    // Reserved
 1772    // private const int OP_RES_0xD = 0xD;    // Reserved
 1773    // private const int OP_RES_0xE = 0xE;    // Reserved
 1774    // private const int OP_RES_0xF = 0xF;    // Reserved
 1775    private const int FLAG_FINAL = 0x80;   // Last frame
 1776    private const int FLAG_MASKED = 0x80;   // Payload is masked
 1777
 1778    private const int CLOSURE_NORMAL = 1000;
 1779    private const int CLOSURE_SHUTDOWN = 1001;
 1780    private const int CLOSURE_PROTOCOL_ERROR = 1002;
 1781
 1782    private const string _iceProtocol = "ice.zeroc.com";
 1783    private const string _wsUUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
 1784
 11785    private static readonly UTF8Encoding _utf8 = new UTF8Encoding(false, true);
 1786
 1787    // Throws ArgumentException for any input that is not a serialized origin per RFC 6454.
 1788    internal static string canonicalizeOrigin(string origin)
 1789    {
 1790        Uri uri;
 1791        try
 1792        {
 11793            uri = new Uri(origin, UriKind.Absolute);
 11794        }
 11795        catch (UriFormatException ex)
 1796        {
 11797            throw new ArgumentException($"malformed origin '{origin}'", nameof(origin), ex);
 1798        }
 1799        // RFC 6454: a serialized origin is exactly "scheme://host[:port]". Reject path, query, fragment, and userinfo.
 1800        // System.Uri normalizes "https://x" to AbsolutePath="/", so we accept either empty or "/".
 11801        if ((uri.AbsolutePath.Length > 0 && uri.AbsolutePath != "/")
 11802            || uri.Query.Length > 0
 11803            || uri.Fragment.Length > 0
 11804            || uri.UserInfo.Length > 0)
 1805        {
 11806            throw new ArgumentException($"malformed origin '{origin}'", nameof(origin));
 1807        }
 11808        return uri.IsDefaultPort ? $"{uri.Scheme}://{uri.Host}" : $"{uri.Scheme}://{uri.Host}:{uri.Port}";
 1809    }
 1810}