PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/app/WebSocketFrame.php

https://github.com/Firehed/Websockets-Server
PHP | 142 lines | 107 code | 24 blank | 11 comment | 14 complexity | 949408d8ee92cc8df2aebb6180ab48c1 MD5 | raw file
  1. <?php
  2. // Reserved for protocol violations, status code 1002
  3. class FailWebSocketConnectionException extends Exception {}
  4. class WebSocketFrame {
  5. public $payload;
  6. private $mask;
  7. public function __construct($payload, $mask = null) {
  8. $this->payload = $payload;
  9. $this->mask = $mask;
  10. } // function __construct
  11. public function __toString() {
  12. $head = chr(0x81); // fin=1, rsv1-3=0, opcode=1
  13. $len = strlen($this->payload);
  14. if ($len <= 125) {
  15. $lenFrame = chr($len);
  16. }
  17. elseif ($len <= 0xFFFF) {
  18. $lenFrame = chr(126) . pack('n', $len);
  19. }
  20. else {
  21. $lenFrame = chr(127) . pack('NN', ($len & 0xFFFFFFFF00000000), ($len & 0x00000000FFFFFFFF));
  22. }
  23. if ($this->mask) {
  24. $lenFrame[0] = chr(ord($lenFrame[0]) | 0x80); // bitmask in "masked" flag
  25. return $head . $lenFrame . $this->mask . self::transformData($this->payload, $this->mask);
  26. }
  27. else {
  28. return $head . $lenFrame . $this->payload;
  29. }
  30. } // function __toString
  31. public static function decode($frame) {
  32. $snip = 0; // this will be trimmed off the beginning of the frame as non-payload
  33. $header = unpack('ninfo', substr($frame, 0, 2));
  34. $snip += 2;
  35. $info = $header['info'];
  36. $fin = (bool) ($info & 0x8000);
  37. $rsv1 = (bool) ($info & 0x4000);
  38. $rsv2 = (bool) ($info & 0x2000);
  39. $rsv3 = (bool) ($info & 0x1000);
  40. $opcode = ($info & 0x0F00) >> 8;
  41. $masked = (bool) ($info & 0x0080);
  42. $len = $info & 0x007F;
  43. if ($rsv1) {
  44. throw new FailWebSocketConnectionException('RSV1 set without known meaning');
  45. }
  46. if ($rsv2) {
  47. throw new FailWebSocketConnectionException('RSV2 set without known meaning');
  48. }
  49. if ($rsv3) {
  50. throw new FailWebSocketConnectionException('RSV3 set without known meaning');
  51. }
  52. switch ($opcode) {
  53. case 0:
  54. // continuation frame
  55. break;
  56. case 1:
  57. // text frame
  58. break;
  59. case 2:
  60. // binary frame
  61. break;
  62. case 3:
  63. case 4:
  64. case 5:
  65. case 6:
  66. case 7:
  67. // reseved for non-control frames
  68. throw new FailWebSocketConnectionException('Use of reserved non-control frame opcode');
  69. break;
  70. case 8:
  71. // Disconnect
  72. break;
  73. case 9:
  74. // ping
  75. break;
  76. case 10:
  77. // pong
  78. break;
  79. case 11:
  80. case 12:
  81. case 13:
  82. case 14:
  83. case 15:
  84. // reserved for control frames
  85. throw new FailWebSocketConnectionException('Use of reserved control frame opcode');
  86. break;
  87. }
  88. // If basic length field was one of the magic numbers, read further into the header to get the actual length
  89. if ($len == 126) {
  90. $len = substr($frame, $snip, 2);
  91. $snip += 2;
  92. $unpacked = unpack('nlen', $len);
  93. $len = $unpacked['len'];
  94. }
  95. elseif ($len == 127) {
  96. $len = substr($frame, $snip, 8);
  97. $snip += 8;
  98. $unpacked = unpack('Nh/Nl', $len); // php's pack doesn't have a specific unsigned 64-bit int format, hack it
  99. $len = ($unpacked['h'] << 32) | $unpacked['l'];
  100. }
  101. if ($masked) {
  102. $maskingKey = substr($frame, $snip, 4);
  103. $snip += 4;
  104. $payload = self::transformData(substr($frame, $snip), $maskingKey);
  105. }
  106. else {
  107. // The spec is unclear if this condition should actually fail the connection, since it says clients MUST mask payload
  108. $payload = substr($frame, $snip);
  109. }
  110. return new WebSocketFrame($payload);
  111. }
  112. private static function transformData($data, $maskingKey) {
  113. for ($i=0, $len = strlen($data); $i < $len; $i++) {
  114. $data[$i] = $data[$i] ^ $maskingKey[$i%4];
  115. }
  116. return $data;
  117. }
  118. }