From 6a42ed3f5502f590b16eb712edc8b28f1bad9fd7 Mon Sep 17 00:00:00 2001 From: Chris Bracken Date: Fri, 12 Jan 2018 15:12:26 -0800 Subject: [PATCH] Decode syslog-encoded iOS logs (#14075) Apple encodes syslog entries using a 7-bit encoding where input UTF-8 bytes are encoded as follows: 1. 0x00 to 0x19: non-printing range. Some ignored, some encoded as <...>. 2. 0x20 to 0x7f: as-is, with the exception of 0x5c (backslash). 3. 0x5c (backslash): octal representation \134. 4. 0x80 to 0x9f: \M^x (using control-character notation for range 0x00 to 0x40). 5. 0xa0: octal representation \240. 6. 0xa1 to 0xf7: \M-x (where x is the input byte stripped of its high-order bit). 7. 0xf8 to 0xff: unused in 4-byte UTF-8. As there doesn't appear to be a system tool to decode these strings, we implement here in Dart. If we're unable to decode a string (e.g. decoding results in an invalid UTF-8 string), we fall back to emitting the log line as-is. --- .../flutter_tools/lib/src/ios/devices.dart | 60 ++++++++++++++++++- .../flutter_tools/test/ios/devices_test.dart | 11 ++++ 2 files changed, 70 insertions(+), 1 deletion(-) diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart index 0e76a27ec9a..1fc495cf77e 100644 --- a/packages/flutter_tools/lib/src/ios/devices.dart +++ b/packages/flutter_tools/lib/src/ios/devices.dart @@ -348,6 +348,64 @@ Your Xcode version may be too old for your iOS version. } } +/// Decodes an encoded syslog string to a UTF-8 representation. +/// +/// Apple's syslog logs are encoded in 7-bit form. Input bytes are encoded as follows: +/// 1. 0x00 to 0x19: non-printing range. Some ignored, some encoded as <...>. +/// 2. 0x20 to 0x7f: as-is, with the exception of 0x5c (backslash). +/// 3. 0x5c (backslash): octal representation \134. +/// 4. 0x80 to 0x9f: \M^x (using control-character notation for range 0x00 to 0x40). +/// 5. 0xa0: octal representation \240. +/// 6. 0xa1 to 0xf7: \M-x (where x is the input byte stripped of its high-order bit). +/// 7. 0xf8 to 0xff: unused in 4-byte UTF-8. +String decodeSyslog(String line) { + // UTF-8 values for \, M, -, ^. + const int kBackslash = 0x5c; + const int kM = 0x4d; + const int kDash = 0x2d; + const int kCaret = 0x5e; + + // Mask for the UTF-8 digit range. + const int kNum = 0x30; + + // Returns true when `byte` is within the UTF-8 7-bit digit range (0x30 to 0x39). + bool isDigit(int byte) => (byte & 0xf0) == kNum; + + // Converts a three-digit ASCII (UTF-8) representation of an octal number `xyz` to an integer. + int decodeOctal(int x, int y, int z) => (x & 0x3) << 6 | (y & 0x7) << 3 | z & 0x7; + + try { + final List bytes = UTF8.encode(line); + final List out = []; + for (int i = 0; i < bytes.length; ) { + if (bytes[i] != kBackslash || i > bytes.length - 4) { + // Unmapped byte: copy as-is. + out.add(bytes[i++]); + } else { + // Mapped byte: decode next 4 bytes. + if (bytes[i + 1] == kM && bytes[i + 2] == kCaret) { + // \M^x form: bytes in range 0x80 to 0x9f. + out.add((bytes[i + 3] & 0x7f) + 0x40); + } else if (bytes[i + 1] == kM && bytes[i + 2] == kDash) { + // \M-x form: bytes in range 0xa0 to 0xf7. + out.add(bytes[i + 3] | 0x80); + } else if (bytes.getRange(i + 1, i + 3).every(isDigit)) { + // \ddd form: octal representation (only used for \134 and \240). + out.add(decodeOctal(bytes[i + 1], bytes[i + 2], bytes[i + 3])); + } else { + // Unknown form: copy as-is. + out.addAll(bytes.getRange(0, 4)); + } + i += 4; + } + } + return UTF8.decode(out); + } catch (_) { + // Unable to decode line: return as-is. + return line; + } +} + class _IOSDeviceLogReader extends DeviceLogReader { RegExp _lineRegex; @@ -394,7 +452,7 @@ class _IOSDeviceLogReader extends DeviceLogReader { if (match != null) { final String logLine = line.substring(match.end); // Only display the log line after the initial device and executable information. - _linesController.add(logLine); + _linesController.add(decodeSyslog(logLine)); } } diff --git a/packages/flutter_tools/test/ios/devices_test.dart b/packages/flutter_tools/test/ios/devices_test.dart index 61b33569987..60e9914acd1 100644 --- a/packages/flutter_tools/test/ios/devices_test.dart +++ b/packages/flutter_tools/test/ios/devices_test.dart @@ -74,6 +74,17 @@ f577a7903cc54959be2e34bc4f7f80b7009efcf4 }); }); + group('decodeSyslog', () { + test('decodes a syslog-encoded line', () { + final String decoded = decodeSyslog(r'I \M-b\M^]\M-$\M-o\M-8\M^O syslog \M-B\M-/\134_(\M-c\M^C\M^D)_/\M-B\M-/ \M-l\M^F\240!'); + expect(decoded, r'I ❤️ syslog ¯\_(ツ)_/¯ 솠!'); + }); + + test('passes through un-decodeable lines as-is', () { + final String decoded = decodeSyslog(r'I \M-b\M^O syslog!'); + expect(decoded, r'I \M-b\M^O syslog!'); + }); + }); group('logging', () { MockIMobileDevice mockIMobileDevice;