Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ostrace service #24

Merged
merged 4 commits into from
Jan 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Netimobiledevice/Backup/DeviceBackup.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Netimobiledevice.Afc;
using Netimobiledevice.Diagnostics;
using Netimobiledevice.EndianBitConversion;
using Netimobiledevice.Exceptions;
using Netimobiledevice.Lockdown;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using Netimobiledevice.Plist;
using Netimobiledevice.Lockdown;
using Netimobiledevice.Lockdown.Services;
using Netimobiledevice.Plist;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace Netimobiledevice.Lockdown.Services
namespace Netimobiledevice.Diagnostics
{
/// <summary>
/// Provides a service to query MobileGestalt & IORegistry keys, as well functionality to
Expand Down
178 changes: 178 additions & 0 deletions Netimobiledevice/Diagnostics/OsTraceService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using Netimobiledevice.EndianBitConversion;
using Netimobiledevice.Lockdown;
using Netimobiledevice.Lockdown.Services;
using Netimobiledevice.Plist;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Netimobiledevice.Diagnostics
{
/// <summary>
/// Provides the service to show process lists, stream formatted and/or filtered syslogs
/// as well as getting old stored syslog archives in the PAX format.
/// </summary>
public sealed class OsTraceService : BaseService
{
protected override string ServiceName => "com.apple.os_trace_relay";

public OsTraceService(LockdownClient client) : base(client) { }

private SyslogEntry ParseSyslogData(List<byte> data)
{
data.RemoveRange(0, 9); // Skip the first 9 bytes
int pid = EndianBitConverter.LittleEndian.ToInt32(data.ToArray(), 0);
data.RemoveRange(0, sizeof(int) + 42); // Skip size of int + 42 bytes
DateTime timestamp = OsTraceService.ParseTimeStamp(data.Take(12));
data.RemoveRange(0, 12 + 1); // Remove the size of the timestamp + 1 byte
SyslogLevel level = (SyslogLevel) data[0];
data.RemoveRange(0, 1 + 38); // Remove the enum byte followed by the next 38 bytes
short imageNameSize = EndianBitConverter.LittleEndian.ToInt16(data.ToArray(), 0);
short messageSize = EndianBitConverter.LittleEndian.ToInt16(data.ToArray(), 2);
data.RemoveRange(0, sizeof(short) + sizeof(short) + 6); // Skip size of the two shorts + 6 bytes
int subsystemSize = EndianBitConverter.LittleEndian.ToInt32(data.ToArray(), 0);
int categorySize = EndianBitConverter.LittleEndian.ToInt32(data.ToArray(), 4);
data.RemoveRange(0, sizeof(int) + sizeof(int) + 6); // Skip size of the two ints + 4 bytes

int filenameSize = 0;
for (int i = 0; i < data.Count; i++) {
if (data[i] == 0x00) {
filenameSize = i + 1;
break;
}
}
string filename = Encoding.UTF8.GetString(data.Take(filenameSize - 1).ToArray());
data.RemoveRange(0, filenameSize); // Remove the filename bytes

string imageName = Encoding.UTF8.GetString(data.Take(imageNameSize - 1).ToArray());
data.RemoveRange(0, imageNameSize);

string message = Encoding.UTF8.GetString(data.Take(messageSize - 1).ToArray());
data.RemoveRange(0, messageSize);

SyslogLabel? label = null;
if (data.Count > 0) {
string subsystem = Encoding.UTF8.GetString(data.Take(subsystemSize - 1).ToArray());
data.RemoveRange(0, subsystemSize);
string category = Encoding.UTF8.GetString(data.Take(categorySize - 1).ToArray());
data.RemoveRange(0, categorySize);
label = new SyslogLabel(category, subsystem);
}

return new SyslogEntry(pid, timestamp, level, imageName, filename, message, label);
}

private static DateTime ParseTimeStamp(IEnumerable<byte> data)
{
int seconds = EndianBitConverter.LittleEndian.ToInt32(data.ToArray(), 0);
int microseconds = EndianBitConverter.LittleEndian.ToInt32(data.ToArray(), 8) / 1000000;
return DateTime.UnixEpoch.AddSeconds(seconds).AddMilliseconds(microseconds * 1000);
}

public async Task<DictionaryNode> GetPidList(CancellationToken cancellationToken = default)
{
DictionaryNode request = new DictionaryNode() {
{ "Request", new StringNode("PidList") },
};
await Service.SendPlistAsync(request, cancellationToken);

// Ignore the first received unknown byte
await Service.ReceiveAsync(1, cancellationToken);

DictionaryNode response = (await Service.ReceivePlistAsync(cancellationToken))?.AsDictionaryNode() ?? new DictionaryNode();
return response;
}

public void CreateArchive(string outputPath, int? sizeLimit = null, int? ageLimit = null, int? startTime = null)
{
var request = new DictionaryNode() {
{ "Request", new StringNode("CreateArchive") }
};

if (sizeLimit != null) {
request.Add("SizeLimit", new IntegerNode((int) sizeLimit));
}
if (ageLimit != null) {
request.Add("AgeLimit", new IntegerNode((int) ageLimit));
}
if (startTime != null) {
request.Add("StartTime", new IntegerNode((int) startTime));
}

Service.SendPlist(request);

int value = Service.Receive(1)[0];
if (value != 1) {
throw new Exception($"Invalid response got {value} instead of 1");
}

DictionaryNode plistResponse = Service.ReceivePlist()?.AsDictionaryNode() ?? new DictionaryNode();
if (!plistResponse.TryGetValue("Status", out PropertyNode? status) || status.AsStringNode().Value != "RequestSuccessful") {
throw new Exception($"Invalid status: {PropertyList.SaveAsString(plistResponse, PlistFormat.Xml)}");
}

using (FileStream f = new FileStream(outputPath, FileMode.Create, FileAccess.Write)) {
while (true) {
try {
value = Service.Receive(1)[0];
if (value != 3) {
throw new Exception("Invalid magic");
}
}
catch (Exception) {
break;
}

byte[] data = Service.ReceivePrefixed();
f.Write(data);
}
}
}

public IEnumerable<SyslogEntry> WatchSyslog(int pid = -1)
{
DictionaryNode request = new DictionaryNode() {
{ "Request", new StringNode("StartActivity") },
{ "MessageFilter", new IntegerNode(65535) },
{ "Pid", new IntegerNode(pid) },
{ "StreamFlags", new IntegerNode(60) }
};
Service.SendPlist(request);

byte[] lengthSizeBytes = Service.Receive(4);
int lengthSize = EndianBitConverter.LittleEndian.ToInt32(lengthSizeBytes, 0);

byte[] lengthBytes = Service.Receive(lengthSize);
if (lengthBytes.Length < 4) {
byte[] tmpArr = new byte[4];
lengthBytes.CopyTo(tmpArr, 0);
lengthBytes = tmpArr;
}
int length = EndianBitConverter.LittleEndian.ToInt32(lengthBytes, 0);

byte[] responseBytes = Service.Receive(length);
DictionaryNode response = PropertyList.LoadFromByteArray(responseBytes).AsDictionaryNode();

if (!response.ContainsKey("Status") || response["Status"].AsStringNode().Value != "RequestSuccessful") {
throw new Exception($"Received an invalid response: {response}");
}

while (true) {
byte checkValue = Service.Receive(1)[0];
if (checkValue != 0x02) {
throw new Exception($"Entry started with incorrect byte value: {checkValue}");
}

lengthBytes = Service.Receive(4);
length = EndianBitConverter.LittleEndian.ToInt32(lengthBytes, 0);

byte[] lineBytes = Service.Receive(length);
yield return ParseSyslogData(lineBytes.ToList());
}
}
}
}
6 changes: 6 additions & 0 deletions Netimobiledevice/Diagnostics/SyslogEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System;

namespace Netimobiledevice.Diagnostics
{
public record SyslogEntry(int Pid, DateTime Timestamp, SyslogLevel Level, string Imagename, string Filename, string Message, SyslogLabel? Label);
}
4 changes: 4 additions & 0 deletions Netimobiledevice/Diagnostics/SyslogLabel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace Netimobiledevice.Diagnostics
{
public record SyslogLabel(string Category, string Subsystem);
}
12 changes: 12 additions & 0 deletions Netimobiledevice/Diagnostics/SyslogLevel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Netimobiledevice.Diagnostics
{
public enum SyslogLevel : int
{
Notice = 0x00,
Info = 0x01,
Debug = 0x02,
UserAction = 0x03,
Error = 0x10,
Fault = 0x11
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System;
using Netimobiledevice.Lockdown;
using Netimobiledevice.Lockdown.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Netimobiledevice.Lockdown.Services
namespace Netimobiledevice.Diagnostics
{
/// <summary>
/// Read the raw system logs
Expand Down
59 changes: 30 additions & 29 deletions Netimobiledevice/Lockdown/ServiceConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,35 +55,6 @@ private static ServiceConnection CreateUsingUsbmux(string udid, ushort port, Usb
return new ServiceConnection(sock, targetDevice);
}

/// <summary>
/// Receive a data block prefixed with a u32 length field
/// </summary>
/// <returns>The data without the u32 field length as a byte array</returns>
private byte[] ReceivePrefixed()
{
byte[] sizeBytes = Receive(4);
if (sizeBytes.Length != 4) {
return Array.Empty<byte>();
}

int size = EndianBitConverter.BigEndian.ToInt32(sizeBytes, 0);
return Receive(size);
}

/// <summary>
/// Receive a data block prefixed with a u32 length field
/// </summary>
/// <returns>The data without the u32 field length as a byte array</returns>
private async Task<byte[]> ReceivePrefixedAsync(CancellationToken cancellationToken)
{
byte[] sizeBytes = await ReceiveAsync(4, cancellationToken);
if (sizeBytes.Length != 4) {
return Array.Empty<byte>();
}

int size = EndianBitConverter.BigEndian.ToInt32(sizeBytes, 0);
return await ReceiveAsync(size, cancellationToken);
}

private bool UserCertificateValidationCallback(object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors)
{
Expand Down Expand Up @@ -194,6 +165,36 @@ public async Task<byte[]> ReceiveAsync(int length, CancellationToken cancellatio
return await PropertyList.LoadFromByteArrayAsync(plistBytes);
}

/// <summary>
/// Receive a data block prefixed with a u32 length field
/// </summary>
/// <returns>The data without the u32 field length as a byte array</returns>
public byte[] ReceivePrefixed()
{
byte[] sizeBytes = Receive(4);
if (sizeBytes.Length != 4) {
return Array.Empty<byte>();
}

int size = EndianBitConverter.BigEndian.ToInt32(sizeBytes, 0);
return Receive(size);
}

/// <summary>
/// Receive a data block prefixed with a u32 length field
/// </summary>
/// <returns>The data without the u32 field length as a byte array</returns>
public async Task<byte[]> ReceivePrefixedAsync(CancellationToken cancellationToken = default)
{
byte[] sizeBytes = await ReceiveAsync(4, cancellationToken);
if (sizeBytes.Length != 4) {
return Array.Empty<byte>();
}

int size = EndianBitConverter.BigEndian.ToInt32(sizeBytes, 0);
return await ReceiveAsync(size, cancellationToken);
}

public void Send(byte[] data)
{
networkStream.Write(data);
Expand Down
31 changes: 0 additions & 31 deletions Netimobiledevice/Lockdown/Services/OsTraceService.cs

This file was deleted.

16 changes: 16 additions & 0 deletions NetimobiledeviceDemo/Program.cs
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Netimobiledevice.Afc;
using Netimobiledevice.Backup;
using Netimobiledevice.Diagnostics;
using Netimobiledevice.Lockdown;
using Netimobiledevice.Lockdown.Services;
using Netimobiledevice.Misagent;
Expand Down Expand Up @@ -39,6 +40,21 @@
}
}

using (LockdownClient lockdown = LockdownClient.CreateLockdownClient(testDevice?.Serial ?? string.Empty)) {
using (OsTraceService osTrace = new OsTraceService(lockdown)) {
osTrace.CreateArchive("output");

int counter = 0;
foreach (SyslogEntry entry in osTrace.WatchSyslog()) {
Console.WriteLine($"[{entry.Level}] {entry.Timestamp} {entry.Label?.Subsystem} - {entry.Message}");
if (counter == 1000) {
break;
}
counter++;
}
}
}

await Task.Delay(1000);

using (LockdownClient lockdown = LockdownClient.CreateLockdownClient(testDevice?.Serial ?? string.Empty)) {
Expand Down Expand Up @@ -68,7 +84,7 @@
using (MobilesyncService mobilesyncService = await MobilesyncService.StartServiceAsync(lockdown)) {
string anchor = new DateTime(2001, 1, 1, 0, 0, 0, DateTimeKind.Utc).ToString();
MobilesyncAnchors anchors = new MobilesyncAnchors() {
DeviceAnchor = null,

Check warning on line 87 in NetimobiledeviceDemo/Program.cs

View workflow job for this annotation

GitHub Actions / test

Cannot convert null literal to non-nullable reference type.

Check warning on line 87 in NetimobiledeviceDemo/Program.cs

View workflow job for this annotation

GitHub Actions / test

Cannot convert null literal to non-nullable reference type.
ComputerAnchor = anchor
};
string mobilesyncdataPath = "mobileSyncedData.plist";
Expand Down
Loading