OLD | NEW |
1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library mdns.src.packet; | 5 library mdns.src.packet; |
6 | 6 |
7 import 'dart:convert'; | 7 import 'dart:convert'; |
8 import 'dart:io'; | 8 import 'dart:io'; |
9 import 'dart:typed_data'; | 9 import 'dart:typed_data'; |
10 | 10 |
(...skipping 70 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
81 throw new StateError("'target' is only supported for type SRV."); | 81 throw new StateError("'target' is only supported for type SRV."); |
82 } | 82 } |
83 return _data; | 83 return _data; |
84 } | 84 } |
85 | 85 |
86 toString() => 'RR $type $_data'; | 86 toString() => 'RR $type $_data'; |
87 } | 87 } |
88 | 88 |
89 /// Result of reading a FQDN. | 89 /// Result of reading a FQDN. |
90 class FQDNReadResult { | 90 class FQDNReadResult { |
91 final List<String> fqdn; | 91 final List<String> fqdnParts; |
92 final int bytesRead; | 92 final int bytesRead; |
93 FQDNReadResult(this.fqdn, this.bytesRead); | 93 String get fqdn => fqdnParts.join('.'); |
| 94 FQDNReadResult(this.fqdnParts, this.bytesRead); |
| 95 } |
| 96 |
| 97 String readFQDN(List<int> packet, [int offset = 0]) { |
| 98 Uint8List data = |
| 99 packet is Uint8List ? packet : new Uint8List.fromList(packet); |
| 100 ByteData bd = new ByteData.view(data.buffer); |
| 101 |
| 102 return _readFQDN(data, bd, offset, data.length).fqdn; |
| 103 } |
| 104 |
| 105 // Read a FQDN at the given offset. Returns a pair with the FQDN |
| 106 // parts and the number of bytes consumed. |
| 107 // |
| 108 // If decoding fails (e.g. due to an invalid packet) `null` is returned. |
| 109 FQDNReadResult _readFQDN(Uint8List data, ByteData bd, int offset, int length) { |
| 110 void checkLength(int required) { |
| 111 if (length < required) throw new MDnsDecodeException(required); |
| 112 } |
| 113 |
| 114 List<String> parts = []; |
| 115 int prevOffset = offset; |
| 116 while (true) { |
| 117 // At least one byte is required. |
| 118 checkLength(offset + 1); |
| 119 |
| 120 // Check for compressed. |
| 121 if (data[offset] & 0xc0 == 0xc0) { |
| 122 // At least two bytes are required for a compressed FQDN. |
| 123 checkLength(offset + 2); |
| 124 |
| 125 // A compressed FQDN has a new offset in the lower 14 bits. |
| 126 FQDNReadResult result = _readFQDN( |
| 127 data, bd, bd.getUint16(offset) & ~0xc000, length); |
| 128 parts.addAll(result.fqdnParts); |
| 129 offset += 2; |
| 130 break; |
| 131 } else { |
| 132 // A normal FQDN part has a length and a UTF-8 encoded name |
| 133 // part. If the length is 0 this is the end of the FQDN. |
| 134 var partLength = data[offset]; |
| 135 offset++; |
| 136 if (partLength > 0) { |
| 137 checkLength(offset + partLength); |
| 138 var partBytes = new Uint8List.view(data.buffer, offset, partLength); |
| 139 offset += partLength; |
| 140 parts.add(UTF8.decode(partBytes)); |
| 141 } else { |
| 142 break; |
| 143 } |
| 144 } |
| 145 } |
| 146 return new FQDNReadResult(parts, offset - prevOffset); |
94 } | 147 } |
95 | 148 |
96 /// Decode a mDNS package. | 149 /// Decode a mDNS package. |
97 /// | 150 /// |
98 /// If decoding fails (e.g. due to an invalid packet) `null` is returned. | 151 /// If decoding fails (e.g. due to an invalid packet) `null` is returned. |
99 /// | 152 /// |
100 /// See https://tools.ietf.org/html/rfc1035 for the format. | 153 /// See https://tools.ietf.org/html/rfc1035 for the format. |
101 List<ResourceRecord> decodeMDnsResponse(List<int> packet) { | 154 List<ResourceRecord> decodeMDnsResponse(List<int> packet) { |
102 int length = packet.length; | 155 int length = packet.length; |
103 if (length < headerSize) return null; | 156 if (length < headerSize) return null; |
(...skipping 14 matching lines...) Expand all Loading... |
118 // Number of name server records. | 171 // Number of name server records. |
119 int nscount = bd.getUint16(nscountOffset); | 172 int nscount = bd.getUint16(nscountOffset); |
120 // Number of resource records. | 173 // Number of resource records. |
121 int arcount = bd.getUint16(arcountOffset); | 174 int arcount = bd.getUint16(arcountOffset); |
122 int offset = headerSize; | 175 int offset = headerSize; |
123 | 176 |
124 void checkLength(int required) { | 177 void checkLength(int required) { |
125 if (length < required) throw new MDnsDecodeException(required); | 178 if (length < required) throw new MDnsDecodeException(required); |
126 } | 179 } |
127 | 180 |
128 // Read a FQDN at the given offset. Returns a pair with the FQDN | |
129 // parts and the number of bytes consumed. | |
130 // | |
131 // If decoding fails (e.g. due to an invalid packet) `null` is returned. | |
132 FQDNReadResult readFQDN(int offset) { | |
133 List<String> parts = []; | |
134 int prevOffset = offset; | |
135 while (true) { | |
136 // At least two bytes required. | |
137 checkLength(offset + 2); | |
138 | |
139 // Check for compressed. | |
140 if (data[offset] & 0xc0 == 0xc0) { | |
141 // A compressed FQDN has a new offset in the lower 14 bits. | |
142 FQDNReadResult result = readFQDN(bd.getUint16(offset) & ~0xc000); | |
143 parts.addAll(result.fqdn); | |
144 offset += 2; | |
145 break; | |
146 } else { | |
147 // A normal FQDN part has a length and a UTF-8 encoded name | |
148 // part. If the length is 0 this is the end of the FQDN. | |
149 var partLength = data[offset]; | |
150 offset++; | |
151 if (partLength != 0) { | |
152 checkLength(offset + partLength); | |
153 var partBytes = new Uint8List.view(data.buffer, offset, partLength); | |
154 offset += partLength; | |
155 parts.add(UTF8.decode(partBytes)); | |
156 } else { | |
157 break; | |
158 } | |
159 } | |
160 } | |
161 return new FQDNReadResult(parts, offset - prevOffset); | |
162 } | |
163 | |
164 ResourceRecord readResourceRecord() { | 181 ResourceRecord readResourceRecord() { |
165 // First read the FQDN. | 182 // First read the FQDN. |
166 FQDNReadResult result = readFQDN(offset); | 183 FQDNReadResult result = _readFQDN(data, bd, offset, length); |
167 var fqdn = result.fqdn.join('.'); | 184 var fqdn = result.fqdn; |
168 offset += result.bytesRead; | 185 offset += result.bytesRead; |
169 checkLength(offset + 2); | 186 checkLength(offset + 2); |
170 int type = bd.getUint16(offset); | 187 int type = bd.getUint16(offset); |
171 offset += 2; | 188 offset += 2; |
172 // The first bit of the rrclass field is set to indicate that the answer is | 189 // The first bit of the rrclass field is set to indicate that the answer is |
173 // unique and the querier should flush the cached answer for this name | 190 // unique and the querier should flush the cached answer for this name |
174 // (RFC 6762, Sec. 10.2). We ignore it for now since we don't cache answers. | 191 // (RFC 6762, Sec. 10.2). We ignore it for now since we don't cache answers. |
175 checkLength(offset + 2); | 192 checkLength(offset + 2); |
176 int cls = bd.getUint16(offset) & 0x7fff; | 193 int cls = bd.getUint16(offset) & 0x7fff; |
177 offset += 2; | 194 offset += 2; |
(...skipping 16 matching lines...) Expand all Loading... |
194 case RRType.SRV: | 211 case RRType.SRV: |
195 checkLength(offset + 2); | 212 checkLength(offset + 2); |
196 int priority = bd.getUint16(offset); | 213 int priority = bd.getUint16(offset); |
197 offset += 2; | 214 offset += 2; |
198 checkLength(offset + 2); | 215 checkLength(offset + 2); |
199 int weight = bd.getUint16(offset); | 216 int weight = bd.getUint16(offset); |
200 offset += 2; | 217 offset += 2; |
201 checkLength(offset + 2); | 218 checkLength(offset + 2); |
202 int port = bd.getUint16(offset); | 219 int port = bd.getUint16(offset); |
203 offset += 2; | 220 offset += 2; |
204 FQDNReadResult result = readFQDN(offset); | 221 FQDNReadResult result = _readFQDN(data, bd, offset, length); |
205 rData = result.fqdn.join('.'); | 222 rData = result.fqdn; |
206 offset += rDataLength - 6; | 223 offset += rDataLength - 6; |
207 break; | 224 break; |
208 case RRType.PTR: | 225 case RRType.PTR: |
209 checkLength(offset + rDataLength); | 226 checkLength(offset + rDataLength); |
210 FQDNReadResult result = readFQDN(offset); | 227 FQDNReadResult result = _readFQDN(data, bd, offset, length); |
211 offset += rDataLength; | 228 offset += rDataLength; |
212 rData = result.fqdn.join('.'); | 229 rData = result.fqdn; |
213 break; | 230 break; |
214 case RRType.TXT: | 231 case RRType.TXT: |
215 // TODO(karlklose): convert to a String or Map. | 232 // TODO(karlklose): convert to a String or Map. |
216 default: | 233 default: |
217 checkLength(offset + rDataLength); | 234 checkLength(offset + rDataLength); |
218 rData = new Uint8List.view(data.buffer, offset, rDataLength); | 235 rData = new Uint8List.view(data.buffer, offset, rDataLength); |
219 offset += rDataLength; | 236 offset += rDataLength; |
220 break; | 237 break; |
221 } | 238 } |
222 assert(rData != null); | 239 assert(rData != null); |
(...skipping 23 matching lines...) Expand all Loading... |
246 return result; | 263 return result; |
247 } | 264 } |
248 | 265 |
249 /// Exceptions thrown by decoder when the packet is invalid. | 266 /// Exceptions thrown by decoder when the packet is invalid. |
250 class MDnsDecodeException implements Exception { | 267 class MDnsDecodeException implements Exception { |
251 /// Exception message. | 268 /// Exception message. |
252 final int offset; | 269 final int offset; |
253 const MDnsDecodeException(this.offset); | 270 const MDnsDecodeException(this.offset); |
254 String toString() => 'Decoding error at $offset'; | 271 String toString() => 'Decoding error at $offset'; |
255 } | 272 } |
OLD | NEW |