BinaryDumper.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. <?php
  2. namespace React\Dns\Protocol;
  3. use React\Dns\Model\Message;
  4. use React\Dns\Model\Record;
  5. use React\Dns\Query\Query;
  6. final class BinaryDumper
  7. {
  8. /**
  9. * @param Message $message
  10. * @return string
  11. */
  12. public function toBinary(Message $message)
  13. {
  14. $data = '';
  15. $data .= $this->headerToBinary($message);
  16. $data .= $this->questionToBinary($message->questions);
  17. $data .= $this->recordsToBinary($message->answers);
  18. $data .= $this->recordsToBinary($message->authority);
  19. $data .= $this->recordsToBinary($message->additional);
  20. return $data;
  21. }
  22. /**
  23. * @param Message $message
  24. * @return string
  25. */
  26. private function headerToBinary(Message $message)
  27. {
  28. $data = '';
  29. $data .= pack('n', $message->id);
  30. $flags = 0x00;
  31. $flags = ($flags << 1) | ($message->qr ? 1 : 0);
  32. $flags = ($flags << 4) | $message->opcode;
  33. $flags = ($flags << 1) | ($message->aa ? 1 : 0);
  34. $flags = ($flags << 1) | ($message->tc ? 1 : 0);
  35. $flags = ($flags << 1) | ($message->rd ? 1 : 0);
  36. $flags = ($flags << 1) | ($message->ra ? 1 : 0);
  37. $flags = ($flags << 3) | 0; // skip unused zero bit
  38. $flags = ($flags << 4) | $message->rcode;
  39. $data .= pack('n', $flags);
  40. $data .= pack('n', count($message->questions));
  41. $data .= pack('n', count($message->answers));
  42. $data .= pack('n', count($message->authority));
  43. $data .= pack('n', count($message->additional));
  44. return $data;
  45. }
  46. /**
  47. * @param Query[] $questions
  48. * @return string
  49. */
  50. private function questionToBinary(array $questions)
  51. {
  52. $data = '';
  53. foreach ($questions as $question) {
  54. $data .= $this->domainNameToBinary($question->name);
  55. $data .= pack('n*', $question->type, $question->class);
  56. }
  57. return $data;
  58. }
  59. /**
  60. * @param Record[] $records
  61. * @return string
  62. */
  63. private function recordsToBinary(array $records)
  64. {
  65. $data = '';
  66. foreach ($records as $record) {
  67. /* @var $record Record */
  68. switch ($record->type) {
  69. case Message::TYPE_A:
  70. case Message::TYPE_AAAA:
  71. $binary = \inet_pton($record->data);
  72. break;
  73. case Message::TYPE_CNAME:
  74. case Message::TYPE_NS:
  75. case Message::TYPE_PTR:
  76. $binary = $this->domainNameToBinary($record->data);
  77. break;
  78. case Message::TYPE_TXT:
  79. case Message::TYPE_SPF:
  80. $binary = $this->textsToBinary($record->data);
  81. break;
  82. case Message::TYPE_MX:
  83. $binary = \pack(
  84. 'n',
  85. $record->data['priority']
  86. );
  87. $binary .= $this->domainNameToBinary($record->data['target']);
  88. break;
  89. case Message::TYPE_SRV:
  90. $binary = \pack(
  91. 'n*',
  92. $record->data['priority'],
  93. $record->data['weight'],
  94. $record->data['port']
  95. );
  96. $binary .= $this->domainNameToBinary($record->data['target']);
  97. break;
  98. case Message::TYPE_SOA:
  99. $binary = $this->domainNameToBinary($record->data['mname']);
  100. $binary .= $this->domainNameToBinary($record->data['rname']);
  101. $binary .= \pack(
  102. 'N*',
  103. $record->data['serial'],
  104. $record->data['refresh'],
  105. $record->data['retry'],
  106. $record->data['expire'],
  107. $record->data['minimum']
  108. );
  109. break;
  110. case Message::TYPE_CAA:
  111. $binary = \pack(
  112. 'C*',
  113. $record->data['flag'],
  114. \strlen($record->data['tag'])
  115. );
  116. $binary .= $record->data['tag'];
  117. $binary .= $record->data['value'];
  118. break;
  119. case Message::TYPE_SSHFP:
  120. $binary = \pack(
  121. 'CCH*',
  122. $record->data['algorithm'],
  123. $record->data['type'],
  124. $record->data['fingerprint']
  125. );
  126. break;
  127. case Message::TYPE_OPT:
  128. $binary = '';
  129. foreach ($record->data as $opt => $value) {
  130. if ($opt === Message::OPT_TCP_KEEPALIVE && $value !== null) {
  131. $value = \pack('n', round($value * 10));
  132. }
  133. $binary .= \pack('n*', $opt, \strlen((string) $value)) . $value;
  134. }
  135. break;
  136. default:
  137. // RDATA is already stored as binary value for unknown record types
  138. $binary = $record->data;
  139. }
  140. $data .= $this->domainNameToBinary($record->name);
  141. $data .= \pack('nnNn', $record->type, $record->class, $record->ttl, \strlen($binary));
  142. $data .= $binary;
  143. }
  144. return $data;
  145. }
  146. /**
  147. * @param string[] $texts
  148. * @return string
  149. */
  150. private function textsToBinary(array $texts)
  151. {
  152. $data = '';
  153. foreach ($texts as $text) {
  154. $data .= \chr(\strlen($text)) . $text;
  155. }
  156. return $data;
  157. }
  158. /**
  159. * @param string $host
  160. * @return string
  161. */
  162. private function domainNameToBinary($host)
  163. {
  164. if ($host === '') {
  165. return "\0";
  166. }
  167. // break up domain name at each dot that is not preceeded by a backslash (escaped notation)
  168. return $this->textsToBinary(
  169. \array_map(
  170. 'stripcslashes',
  171. \preg_split(
  172. '/(?<!\\\\)\./',
  173. $host . '.'
  174. )
  175. )
  176. );
  177. }
  178. }