/* * Copyright 2023 Hongen Wang All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; import 'package:proxypin/network/util/logger.dart'; class WebSocketFrame { final bool fin; /* 0x00 denotes a continuation frame 0x01 表示一个text frame 0x02 表示一个binary frame 0x03 ~~ 0x07 are reserved for further non-control frames,为将来的非控制消息片段保留测操作码 0x08 表示连接关闭 0x09 表示 ping (心跳检测相关) 0x0a 表示 pong (心跳检测相关) */ final int opcode; //4bit final bool mask; //1bit final int maskingKey; final int payloadLength; final Uint8List payloadData; bool isFromClient = false; DateTime time; WebSocketFrame({ required this.fin, required this.opcode, required this.mask, required this.payloadLength, required this.maskingKey, required this.payloadData, DateTime? time, }) : time = time ?? DateTime.now(); bool get isText => opcode == 0x01; bool get isBinary => opcode == 0x02; String get payloadDataAsString { if (opcode == 0x08) { return '连接关闭'; } if (opcode == 0x02) { return '二进制数据'; } try { return utf8.decode(payloadData); } catch (e) { return String.fromCharCodes(payloadData); } } Map toJson() { return { 'fin': fin, 'opcode': opcode, 'mask': mask, 'maskingKey': maskingKey, 'payloadLength': payloadLength, // use base64 to avoid binary corruption in JSON 'payloadData': base64Encode(payloadData), 'isFromClient': isFromClient, 'time': time.millisecondsSinceEpoch, }; } factory WebSocketFrame.fromJson(Map json) { final payload = base64Decode(json['payloadData']?.toString() ?? ''); final frame = WebSocketFrame( fin: json['fin'] == true, opcode: (json['opcode'] ?? 0) as int, mask: json['mask'] == true, payloadLength: (json['payloadLength'] ?? payload.length) as int, maskingKey: (json['maskingKey'] ?? 0) as int, payloadData: Uint8List.fromList(payload), time: json['time'] == null ? DateTime.now() : DateTime.fromMillisecondsSinceEpoch((json['time'] as num).toInt()), ); frame.isFromClient = json['isFromClient'] == true; return frame; } } ///websocket 解码器 class WebSocketDecoder { ByteBuffer buffer = ByteBuffer(); WebSocketFrame? decode(Uint8List newData) { buffer.putBytes(newData); if (!canParseWebSocketFrame(buffer.bytes)) { return null; } try { WebSocketFrame frame = _parseWebSocketFrame(buffer.bytes); buffer.clear(); return frame; } catch (e, stackTrace) { logger.e("WebSocket decode error", error: e, stackTrace: stackTrace); return null; } } bool canParseWebSocketFrame(Uint8List data) { if (data.length < 2) { return false; } var reader = ByteData.sublistView(data); var opcode = reader.getUint8(0) & 0x0f; if (opcode > 0xA) { return false; } var mask = reader.getUint8(1) >> 7; int payloadStart = 2; int payloadLength = reader.getUint8(1) & 0x7f; if (payloadLength == 126) { if (data.length < 4) return false; payloadLength = reader.getUint16(2); payloadStart += 2; } else if (payloadLength == 127) { if (data.length < 10) return false; payloadLength = reader.getUint64(2); payloadStart += 8; } if (mask == 1) { if (data.length < payloadStart + 4) { return false; } payloadStart += 4; } if (data.length < payloadStart + payloadLength) { return false; } return true; } WebSocketFrame _parseWebSocketFrame(Uint8List data) { var reader = ByteData.sublistView(data); var fin = reader.getUint8(0) >> 7; //解析 rsv1 var rsv1 = (reader.getUint8(0) >> 6) & 0x01; var opcode = reader.getUint8(0) & 0x0f; var mask = reader.getUint8(1) >> 7; int payloadLength = reader.getUint8(1) & 0x7f; int payloadStart = 2; if (payloadLength == 126) { payloadLength = reader.getUint16(2); payloadStart += 2; } else if (payloadLength == 127) { payloadLength = reader.getUint64(2); payloadStart += 8; } var maskingKey = 0; if (mask == 1) { maskingKey = reader.getUint32(payloadStart); payloadStart += 4; } int payloadDataLength = payloadLength; if (payloadStart + payloadDataLength > data.length) { payloadDataLength = data.length - payloadStart; logger.w("Payload data length exceeds available data, truncating."); } var payloadData = data.sublist(payloadStart, payloadStart + payloadDataLength); if (mask == 1) { payloadData = unmaskPayload(payloadData, maskingKey); } if (rsv1 == 1) { //inflate payloadData = decompress(payloadData); } return WebSocketFrame( fin: fin == 1, opcode: opcode, mask: mask == 1, maskingKey: maskingKey, payloadLength: payloadLength, payloadData: payloadData, ); } ZLibDecoder? _decoder; ZLibDecoder _ensureDecoder() => _decoder ?? ZLibDecoder(raw: true); Uint8List decompress(Uint8List msg) { try { return Uint8List.fromList(_ensureDecoder().convert(msg)); } catch (e) { logger.e("Decompression error", error: e); return msg; } } Uint8List unmaskPayload(Uint8List payloadData, int maskingKey) { var unmaskedData = Uint8List(payloadData.length); for (var i = 0; i < payloadData.length; i++) { var keyByte = (maskingKey >> ((3 - (i % 4)) * 8)) & 0xff; unmaskedData[i] = payloadData[i] ^ keyByte; } return unmaskedData; } } class ByteBuffer { Uint8List _bytes = Uint8List(0); Uint8List get bytes => _bytes; void putBytes(Uint8List newBytes) { Uint8List tmp = Uint8List(_bytes.length + newBytes.length); tmp.setAll(0, _bytes); tmp.setAll(_bytes.length, newBytes); _bytes = tmp; } void clear() { _bytes = Uint8List(0); } }