| // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| library http2.src.connection_preface; |
| |
| import 'dart:async'; |
| import 'dart:math'; |
| |
| import 'byte_utils.dart'; |
| |
| /// This is a set of bytes with which a client connection begins in the normal |
| /// case. It can be used on a server to distinguish HTTP/1.1 and HTTP/2 clients. |
| const List<int> CONNECTION_PREFACE = const [ |
| 0x50, |
| 0x52, |
| 0x49, |
| 0x20, |
| 0x2a, |
| 0x20, |
| 0x48, |
| 0x54, |
| 0x54, |
| 0x50, |
| 0x2f, |
| 0x32, |
| 0x2e, |
| 0x30, |
| 0x0d, |
| 0x0a, |
| 0x0d, |
| 0x0a, |
| 0x53, |
| 0x4d, |
| 0x0d, |
| 0x0a, |
| 0x0d, |
| 0x0a |
| ]; |
| |
| /// Reads the connection preface from [incoming]. |
| /// |
| /// The returned `Stream` will be a duplicate of `incoming` without the |
| /// connection preface. If an error occurs while reading the connection |
| /// preface, the returned stream will have only an error. |
| Stream<List<int>> readConnectionPreface(Stream<List<int>> incoming) { |
| StreamController<List<int>> result; |
| StreamSubscription subscription; |
| bool connectionPrefaceRead = false; |
| var prefaceBuffer = <int>[]; |
| bool terminated = false; |
| |
| terminate(error) { |
| if (!terminated) { |
| result.addError(error); |
| result.close(); |
| subscription.cancel(); |
| } |
| terminated = true; |
| } |
| |
| bool compareConnectionPreface(List<int> data) { |
| for (int i = 0; i < CONNECTION_PREFACE.length; i++) { |
| if (data[i] != CONNECTION_PREFACE[i]) { |
| terminate('Connection preface does not match.'); |
| return false; |
| } |
| } |
| prefaceBuffer = null; |
| connectionPrefaceRead = true; |
| return true; |
| } |
| |
| void onData(List<int> data) { |
| if (connectionPrefaceRead) { |
| // Forward data after reading preface. |
| result.add(data); |
| } else { |
| if (prefaceBuffer.isEmpty && data.length > CONNECTION_PREFACE.length) { |
| if (!compareConnectionPreface(data)) return; |
| data = data.sublist(CONNECTION_PREFACE.length); |
| } else if (prefaceBuffer.length < CONNECTION_PREFACE.length) { |
| int remaining = CONNECTION_PREFACE.length - prefaceBuffer.length; |
| |
| int end = min(data.length, remaining); |
| var part1 = viewOrSublist(data, 0, end); |
| var part2 = viewOrSublist(data, end, data.length - end); |
| prefaceBuffer.addAll(part1); |
| |
| if (prefaceBuffer.length == CONNECTION_PREFACE.length) { |
| if (!compareConnectionPreface(prefaceBuffer)) return; |
| } |
| data = part2; |
| } |
| if (data.length > 0) { |
| result.add(data); |
| } |
| } |
| } |
| |
| result = new StreamController( |
| onListen: () { |
| subscription = incoming.listen(onData, |
| onError: (e, StackTrace s) => result.addError(e, s), |
| onDone: () { |
| if (prefaceBuffer != null) { |
| terminate('EOS before connection preface could be read.'); |
| } else { |
| result.close(); |
| } |
| }); |
| }, |
| onPause: () => subscription.pause(), |
| onResume: () => subscription.resume(), |
| onCancel: () => subscription.cancel()); |
| |
| return result.stream; |
| } |