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