Skip to content
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
13 changes: 10 additions & 3 deletions IOTracesCORE/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using IOTracesCORE.cloudstorage;
using IOTracesCORE.utils;
using Microsoft.Win32;
using System;
using System.Diagnostics;
Expand All @@ -22,8 +23,8 @@
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

const int SW_HIDE = 0;
private static CancellationTokenSource cancellationTokenSource;

Check warning on line 26 in IOTracesCORE/Program.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Non-nullable field 'cancellationTokenSource' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.

Check warning on line 26 in IOTracesCORE/Program.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Non-nullable field 'cancellationTokenSource' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
private static NotifyIcon trayIcon;

Check warning on line 27 in IOTracesCORE/Program.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Non-nullable field 'trayIcon' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the field as nullable.
private static bool isElevated;

[STAThread]
Expand All @@ -44,6 +45,8 @@
var iconStream = assembly.GetManifestResourceStream("IOTracesCORE.Opera_Glasses_icon-icons.com_54155.ico");
var icon = iconStream != null ? new Icon(iconStream) : SystemIcons.Application;

ConfigClasses.LoadTracemetaConfiguration();

string currentVersion = VersionManager.Instance.GetCurrentVersion();
trayIcon = new NotifyIcon
{
Expand All @@ -57,16 +60,20 @@
contextMenu.Items.Add("-");
contextMenu.Items.Add("Show Status", null, (s, e) =>
{
TimeSpan Total_current_session = WriterManager.active_session;
TimeSpan Total_trace_duration = WriterManager.trace_duration;
Comment on lines +63 to +64

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The local variable names Total_current_session and Total_trace_duration do not follow standard C# naming conventions, which recommend camelCase for local variables (e.g., totalCurrentSession). Adhering to conventions improves code readability and maintainability. Please update these variables and their subsequent usages.

MessageBox.Show(
$"Version: {currentVersion}\n" +
$"Logs Created: {WriterManager.amount_compressed_file}\n" +
$"Logs Uploaded: {ObjectStorageHandler.UploadedFiles}",
$"Computer ID: {PathHasher.deviceId}\n" +
$"Logs Created / Uploaded: {WriterManager.amount_compressed_file} / {ObjectStorageHandler.UploadedFiles}\n" +
$"File events collected: {DisplayHelper.ToPowerOfTen(WriterManager.file_event_counter)}\n\n" +
$"Active session elapsed (HH:MM:SS): {Total_current_session.TotalHours:00}:{Total_current_session.Minutes:00}:{Total_current_session.Seconds:00}\n" +
$"Trace Duration: {Total_trace_duration.TotalDays:00} Days {Total_trace_duration.Hours:00} Hours",
"Status",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
});
contextMenu.Items.Add("-");
contextMenu.Items.Add("Exit", null, OnExitClicked);

Check warning on line 76 in IOTracesCORE/Program.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Nullability of reference types in type of parameter 'sender' of 'void Program.OnExitClicked(object sender, EventArgs e)' doesn't match the target delegate 'EventHandler' (possibly because of nullability attributes).

Check warning on line 76 in IOTracesCORE/Program.cs

View workflow job for this annotation

GitHub Actions / build (win-x64)

Nullability of reference types in type of parameter 'sender' of 'void Program.OnExitClicked(object sender, EventArgs e)' doesn't match the target delegate 'EventHandler' (possibly because of nullability attributes).

trayIcon.ContextMenuStrip = contextMenu;

Expand Down
25 changes: 8 additions & 17 deletions IOTracesCORE/TracerConfigForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Security.Principal;
using System.Text;
using System.Text.Json;
using static IOTracesCORE.utils.ConfigClasses;

namespace IOTracesCORE
{
Expand Down Expand Up @@ -33,7 +34,7 @@ public TracerConfigForm(CancellationToken token)

LoadSavedConfiguration();

if (!File.Exists(ConfigPath))
if (!File.Exists(AppConfigPath))
{
chkEnableUpload.Checked = true;
ChkEnableUpload_CheckedChanged(this, EventArgs.Empty);
Expand Down Expand Up @@ -316,17 +317,6 @@ private void RunTracer(string outputPath, bool anonymous, bool upload, ObjectSto
});
}

private class PersistedConfig
{
public string OutputPath { get; set; } = "";
public bool Anonymous { get; set; }
public bool UploadEnabled { get; set; }
}

private string ConfigPath =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"IOTracer", "config.json.enc");

private void SaveConfiguration()
{
try
Expand All @@ -335,14 +325,14 @@ private void SaveConfiguration()
{
OutputPath = txtOutputPath.Text,
Anonymous = chkAnonymous.Checked,
UploadEnabled = chkEnableUpload.Checked
UploadEnabled = chkEnableUpload.Checked,
};

string json = JsonSerializer.Serialize(cfg, new JsonSerializerOptions { WriteIndented = true });
byte[] encrypted = ProtectedData.Protect(
Encoding.UTF8.GetBytes(json), null, DataProtectionScope.CurrentUser);
Directory.CreateDirectory(Path.GetDirectoryName(ConfigPath)!);
File.WriteAllBytes(ConfigPath, encrypted);
Directory.CreateDirectory(Path.GetDirectoryName(AppConfigPath)!);
File.WriteAllBytes(AppConfigPath, encrypted);
}
catch (Exception ex) { Debug.WriteLine("Save config failed: " + ex.Message); }
}
Expand All @@ -351,9 +341,10 @@ private void LoadSavedConfiguration()
{
try
{
if (!File.Exists(ConfigPath)) return;
Debug.Write($"Receiving config from: {AppConfigPath}");
if (!File.Exists(AppConfigPath)) return;

byte[] encrypted = File.ReadAllBytes(ConfigPath);
byte[] encrypted = File.ReadAllBytes(AppConfigPath);
byte[] decrypted = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser);
string json = Encoding.UTF8.GetString(decrypted);
var cfg = JsonSerializer.Deserialize<PersistedConfig>(json);
Expand Down
45 changes: 39 additions & 6 deletions IOTracesCORE/WriterManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class WriterManager
private bool is_anonymous;
private bool is_upload_automatically;
public static int amount_compressed_file = 0;
public static int disk_event_counter = 0;
public static int file_event_counter = 0;
public static TimeSpan active_session = TimeSpan.FromSeconds(0);
public static TimeSpan trace_duration = TimeSpan.FromSeconds(0);

public WriterManager(string dirpath, bool is_anonymous, bool upload,ObjectStorageHandler obj)
{
Expand All @@ -58,6 +62,8 @@ public WriterManager(string dirpath, bool is_anonymous, bool upload,ObjectStorag
process_snap_filepath = GenerateFilePath("process");
fs_snap_filepath = GenerateFilePath("filesystem_snapshot");
this.is_anonymous = is_anonymous;

StartEventRateDetector();
}

public void InitiateDirectory()
Expand Down Expand Up @@ -95,6 +101,32 @@ public void InitiateDirectory()
Console.WriteLine("File output: {0}", this.dir_path);
}

private void StartEventRateDetector()
{
Debug.WriteLine("Starting event rate detector thread...");
Thread eventRateThread = new(EventRateDetector)
{
IsBackground = true
};
eventRateThread.Start();
}

private void EventRateDetector()
{
while (true)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This while(true) loop runs indefinitely. Although it's on a background thread, it's better practice to make it cancellable for a graceful shutdown. Consider passing a CancellationToken to the WriterManager and checking token.IsCancellationRequested in the loop condition.

{
int initial_count = disk_event_counter;
Thread.Sleep(1000);
int final_count = disk_event_counter;
int events_in_interval = final_count - initial_count;
disk_event_counter = 0;
Comment on lines +118 to +122

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This block of code for measuring the event rate is not thread-safe. disk_event_counter is modified by other threads concurrently. The sequence of reading the counter, sleeping, reading it again, and then resetting it to zero is subject to race conditions that will lead to lost counts and incorrect metrics. To fix this, you should atomically read and reset the counter. For example: int events_in_interval = Interlocked.Exchange(ref disk_event_counter, 0); after the Thread.Sleep(1000);.

//Debug.WriteLine($"Rate: {events_in_interval}");
if (events_in_interval > 100) {
active_session += TimeSpan.FromSeconds(1);
}
}
}

private static string EscapeCsvField(string field)
{
if (string.IsNullOrEmpty(field))
Expand Down Expand Up @@ -145,6 +177,8 @@ public void Write(FilesystemTrace data)
{
return;
}
file_event_counter += 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

file_event_counter is a static field that can be accessed by multiple threads concurrently. The += 1 operation is not atomic and will lead to race conditions and incorrect counts. Use Interlocked.Increment to ensure thread-safe increments.

            Interlocked.Increment(ref file_event_counter);

//event_counter += 1;
fs_sb.Append(data.FormatAsCsv(is_anonymous));
if (IsTimeToFlush(fs_sb))
{
Expand All @@ -165,7 +199,7 @@ public void Write(DiskTrace data)
{
return;
}

disk_event_counter += 1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

disk_event_counter is a static field that can be accessed by multiple threads concurrently. The += 1 operation is not atomic and will lead to race conditions and incorrect counts. Use Interlocked.Increment to ensure thread-safe increments.

            Interlocked.Increment(ref disk_event_counter);

ds_sb.Append(data.FormatAsCsv());

if (IsTimeToFlush(ds_sb))
Expand All @@ -187,7 +221,7 @@ public void Write(NetworkTrace data)
{
return;
}

//event_counter += 1;
nw_sb.Append(data.FormatAsCsv());

if (IsTimeToFlush(nw_sb))
Expand Down Expand Up @@ -279,6 +313,8 @@ public void DirectWrite(string file_out_path ,string input)
writer.Write(input);
}

amount_compressed_file++;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

amount_compressed_file is a static field that can be accessed by multiple threads concurrently. The ++ operation is not atomic and can lead to race conditions and incorrect counts. Use Interlocked.Increment to ensure thread-safe increments.

            Interlocked.Increment(ref amount_compressed_file);


if (is_upload_automatically)
{
obj_storage.QueueFile(out_path);
Expand Down Expand Up @@ -361,10 +397,6 @@ public void CompressRun()
Directory.Delete(dir_path, true);
}

public void FlushSnapper()
{
FlushWrite(fs_snap_sb, fs_snap_filepath, "filesystem_snapshot");
}

public async Task CompressAllAsync()
{
Expand All @@ -380,6 +412,7 @@ public async Task CompressAllAsync()
WriteStatus();

await obj_storage.ClearQueue();
ConfigClasses.SaveTracemetaConfiguration(active_session + trace_duration, file_event_counter);

CompressRun();
}
Expand Down
11 changes: 10 additions & 1 deletion IOTracesCORE/cloudstorage/ObjectStorageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ internal class ObjectStorageHandler
private R2Client r2Client;
private ConcurrentQueue<string> uploadQueue = new();
public static int UploadedFiles = 0;
private TimeSpan LastActiveHours = TimeSpan.FromSeconds(0);
public static int LastFileEvent;


public ObjectStorageHandler()
Expand All @@ -26,9 +28,16 @@ public ObjectStorageHandler()

public async Task UploadFile(string filepath)
{
//double deltaSeconds = WriterManager.active_session.TotalSeconds - LastActiveHours.TotalSeconds;
//int deltaFileEvent = WriterManager.file_event_counter - LastFileEvent;
//Debug.WriteLine($"Uploading file with delta seconds: {deltaSeconds}, delta file events: {deltaFileEvent}");

FileInfo fi = new FileInfo(filepath);
await r2Client.PutObject(fi);
File.Delete(filepath);

LastFileEvent = WriterManager.file_event_counter;
LastActiveHours = WriterManager.active_session;
}

public void QueueFile(string filepath)
Expand All @@ -54,7 +63,7 @@ public async Task ClearQueue()
catch (Exception ex)
{
Debug.WriteLine($"Error uploading {filepath}: {ex}");
QueueFile(filepath);
//QueueFile(filepath);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The logic to re-queue a file for upload upon failure has been commented out. This means that if an upload fails due to a transient issue (e.g., a temporary network error), the file will be lost instead of being retried. This could lead to data loss. If this change is intentional, it should be documented. Otherwise, the retry logic should be restored, perhaps with a limited number of retries and a backoff strategy.

}
}
}
Expand Down
21 changes: 18 additions & 3 deletions IOTracesCORE/cloudstorage/R2Client.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public async Task PutObject(FileInfo file)
{
try
{
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));

var currentVersion = VersionManager.Instance.GetCurrentVersion();
if (
string.IsNullOrWhiteSpace(currentVersion) ||
Expand All @@ -39,7 +41,21 @@ public async Task PutObject(FileInfo file)

string dir_name = file.DirectoryName ?? "unknown_dir";
string trace_type = Path.GetFileName(file.DirectoryName) ?? "unknown_type";
var response = await http.GetAsync($"{EndpointUrl}/windows_trace/{PathHasher.deviceId}/{CurrentDate}/{trace_type}/{file.Name}");

var endpoint = $"{EndpointUrl}/windows_trace/{PathHasher.deviceId}/{CurrentDate}/{trace_type}/{file.Name}";

var request = new HttpRequestMessage(
HttpMethod.Get,
endpoint
);

Debug.WriteLine(endpoint);

//request.Headers.Add("X-Active-Delta-Seconds", deltaSeconds.ToString());
//request.Headers.Add("X-File-Events-Delta-Collected", deltaFileEvent.ToString());
//request.Headers.Add("X-Computer-Id", PathHasher.deviceId);

var response = await http.SendAsync(request);
response.EnsureSuccessStatusCode();

var presignedUrl = await response.Content.ReadAsStringAsync();
Expand All @@ -53,11 +69,10 @@ public async Task PutObject(FileInfo file)
};


var uploadResponse = await http.SendAsync(uploadRequest);
var uploadResponse = await http.SendAsync(uploadRequest, cts.Token);
uploadResponse.EnsureSuccessStatusCode();

Debug.WriteLine($"{file.FullName} successfully uploaded");

}
catch (Exception)
{
Expand Down
83 changes: 83 additions & 0 deletions IOTracesCORE/utils/ConfigClasses.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using IOTracesCORE.cloudstorage;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;

namespace IOTracesCORE.utils
{
internal class ConfigClasses

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This class only contains static members and nested types. It can be declared as a static class to make this explicit and prevent it from being instantiated, which is a good practice for utility classes.

    internal static class ConfigClasses

{
public class PersistedConfig
{
public string OutputPath { get; set; } = "";
public bool Anonymous { get; set; }
public bool UploadEnabled { get; set; }
}

public class TraceMetadata
{
public long TraceDuration { get; set; }
public long FileEventsCount { get; set; }
}

public static string AppConfigPath =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"IOTracer", "config.json.enc");

public static string ConfigTraceMetadataPath =>
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"IOTracer", "trace_metadata.json.enc");

public static void SaveTracemetaConfiguration(TimeSpan trace_active, long file_events)
{
try
{
Debug.WriteLine($"Saving a session of {trace_active.TotalSeconds} seconds");
var cfg = new TraceMetadata
{
TraceDuration = (long)trace_active.TotalSeconds,
FileEventsCount = file_events
};

string json = JsonSerializer.Serialize(cfg, new JsonSerializerOptions { WriteIndented = true });
byte[] encrypted = ProtectedData.Protect(
Encoding.UTF8.GetBytes(json), null, DataProtectionScope.CurrentUser);
Directory.CreateDirectory(Path.GetDirectoryName(ConfigTraceMetadataPath)!);
File.WriteAllBytes(ConfigTraceMetadataPath, encrypted);
}
catch (Exception ex) { Debug.WriteLine("Save config failed: " + ex.Message); }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This catch block is too broad. It catches any Exception and only logs the message, which can hide the root cause of issues like file I/O errors or serialization problems. At a minimum, log the full exception details with ex.ToString() for better diagnostics.

            catch (Exception ex) { Debug.WriteLine("Save config failed: " + ex.ToString()); }

}

public static void LoadTracemetaConfiguration()
{
try
{
Debug.Write($"Receiving config from: {ConfigTraceMetadataPath}");
if (!File.Exists(ConfigTraceMetadataPath)) return;

byte[] encrypted = File.ReadAllBytes(ConfigTraceMetadataPath);
byte[] decrypted = ProtectedData.Unprotect(encrypted, null, DataProtectionScope.CurrentUser);
string json = Encoding.UTF8.GetString(decrypted);
var cfg = JsonSerializer.Deserialize<TraceMetadata>(json);

if (cfg == null) return;

WriterManager.trace_duration = TimeSpan.FromSeconds(cfg.TraceDuration);
WriterManager.file_event_counter += (int)cfg.FileEventsCount;
ObjectStorageHandler.LastFileEvent = (int)cfg.FileEventsCount;
Comment on lines +71 to +72

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Casting cfg.FileEventsCount (a long) to an int can cause an overflow if the value exceeds int.MaxValue (approximately 2.1 billion). This would lead to data corruption for the event count metric. To prevent this, the fields WriterManager.file_event_counter and ObjectStorageHandler.LastFileEvent should be changed to long throughout the codebase, and this explicit cast should be removed.

Debug.WriteLine($"Loaded a session of {cfg.TraceDuration} seconds");
Debug.WriteLine($"Loaded {cfg.FileEventsCount} file events");
}
catch (Exception ex)
{
Debug.WriteLine("Load config failed: " + ex.Message);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This catch block is too broad and only logs the exception message. This can obscure the underlying cause of a failure during configuration loading. For more effective debugging, you should log the full exception details using ex.ToString().

                Debug.WriteLine("Load config failed: " + ex.ToString());


}
}
}
}
Loading
Loading