Set autocrlf by default, update affected files (#2261)

This commit is contained in:
George Pollard
2022-08-17 13:07:20 +12:00
committed by GitHub
parent 2382ce5ca5
commit a3f1d59f70
20 changed files with 1545 additions and 1543 deletions

2
.gitattributes vendored Normal file
View File

@ -0,0 +1,2 @@
* text=auto
*.ps1 text=crlf

View File

@ -1,75 +1,75 @@
#!/usr/bin/env python
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
import hmac
import json
import logging
import os
from hashlib import sha512
from typing import Any, Dict
import aiohttp
import azure.functions as func
def code_block(data: str) -> str:
data = data.replace("`", "``")
return "\n```\n%s\n```\n" % data
async def send_message(req: func.HttpRequest) -> bool:
data = req.get_json()
teams_url = os.environ.get("TEAMS_URL")
if teams_url is None:
raise Exception("missing TEAMS_URL")
message: Dict[str, Any] = {
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": data["instance_name"],
"sections": [
{
"facts": [
{"name": "instance", "value": data["instance_name"]},
{"name": "event type", "value": data["event_type"]},
]
},
{"text": code_block(json.dumps(data["event"], sort_keys=True))},
],
}
async with aiohttp.ClientSession() as client:
async with client.post(teams_url, json=message) as response:
return response.ok
def verify(req: func.HttpRequest) -> bool:
request_hmac = req.headers.get("X-Onefuzz-Digest")
if request_hmac is None:
raise Exception("missing X-Onefuzz-Digest")
hmac_token = os.environ.get("HMAC_TOKEN")
if hmac_token is None:
raise Exception("missing HMAC_TOKEN")
digest = hmac.new(
hmac_token.encode(), msg=req.get_body(), digestmod=sha512
).hexdigest()
if digest != request_hmac:
logging.error("invalid hmac")
return False
return True
async def main(req: func.HttpRequest) -> func.HttpResponse:
if not verify(req):
return func.HttpResponse("no thanks")
if await send_message(req):
return func.HttpResponse("unable to send message")
return func.HttpResponse("thanks")
#!/usr/bin/env python
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
#
import hmac
import json
import logging
import os
from hashlib import sha512
from typing import Any, Dict
import aiohttp
import azure.functions as func
def code_block(data: str) -> str:
data = data.replace("`", "``")
return "\n```\n%s\n```\n" % data
async def send_message(req: func.HttpRequest) -> bool:
data = req.get_json()
teams_url = os.environ.get("TEAMS_URL")
if teams_url is None:
raise Exception("missing TEAMS_URL")
message: Dict[str, Any] = {
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"summary": data["instance_name"],
"sections": [
{
"facts": [
{"name": "instance", "value": data["instance_name"]},
{"name": "event type", "value": data["event_type"]},
]
},
{"text": code_block(json.dumps(data["event"], sort_keys=True))},
],
}
async with aiohttp.ClientSession() as client:
async with client.post(teams_url, json=message) as response:
return response.ok
def verify(req: func.HttpRequest) -> bool:
request_hmac = req.headers.get("X-Onefuzz-Digest")
if request_hmac is None:
raise Exception("missing X-Onefuzz-Digest")
hmac_token = os.environ.get("HMAC_TOKEN")
if hmac_token is None:
raise Exception("missing HMAC_TOKEN")
digest = hmac.new(
hmac_token.encode(), msg=req.get_body(), digestmod=sha512
).hexdigest()
if digest != request_hmac:
logging.error("invalid hmac")
return False
return True
async def main(req: func.HttpRequest) -> func.HttpResponse:
if not verify(req):
return func.HttpResponse("no thanks")
if await send_message(req):
return func.HttpResponse("unable to send message")
return func.HttpResponse("thanks")

View File

@ -1,40 +1,40 @@
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service.Functions;
public class QueueProxyHearbeat {
private readonly ILogTracer _log;
private readonly IProxyOperations _proxy;
public QueueProxyHearbeat(ILogTracer log, IProxyOperations proxy) {
_log = log;
_proxy = proxy;
}
//[Function("QueueProxyHearbeat")]
public async Async.Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string msg) {
_log.Info($"heartbeat: {msg}");
var hb = JsonSerializer.Deserialize<ProxyHeartbeat>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}"); ;
var newHb = hb with { TimeStamp = DateTimeOffset.UtcNow };
var proxy = await _proxy.GetByProxyId(newHb.ProxyId);
var log = _log.WithTag("ProxyId", newHb.ProxyId.ToString());
if (proxy == null) {
log.Warning($"invalid proxy id: {newHb.ProxyId}");
return;
}
var newProxy = proxy with { Heartbeat = newHb };
var r = await _proxy.Replace(newProxy);
if (!r.IsOk) {
var (status, reason) = r.ErrorV;
log.Error($"Failed to replace proxy heartbeat record due to [{status}] {reason}");
}
}
}
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service.Functions;
public class QueueProxyHearbeat {
private readonly ILogTracer _log;
private readonly IProxyOperations _proxy;
public QueueProxyHearbeat(ILogTracer log, IProxyOperations proxy) {
_log = log;
_proxy = proxy;
}
//[Function("QueueProxyHearbeat")]
public async Async.Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string msg) {
_log.Info($"heartbeat: {msg}");
var hb = JsonSerializer.Deserialize<ProxyHeartbeat>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}"); ;
var newHb = hb with { TimeStamp = DateTimeOffset.UtcNow };
var proxy = await _proxy.GetByProxyId(newHb.ProxyId);
var log = _log.WithTag("ProxyId", newHb.ProxyId.ToString());
if (proxy == null) {
log.Warning($"invalid proxy id: {newHb.ProxyId}");
return;
}
var newProxy = proxy with { Heartbeat = newHb };
var r = await _proxy.Replace(newProxy);
if (!r.IsOk) {
var (status, reason) = r.ErrorV;
log.Error($"Failed to replace proxy heartbeat record due to [{status}] {reason}");
}
}
}

View File

@ -1,24 +1,24 @@
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service.Functions;
public class QueueWebhooks {
private readonly ILogTracer _log;
private readonly IWebhookMessageLogOperations _webhookMessageLog;
public QueueWebhooks(ILogTracer log, IWebhookMessageLogOperations webhookMessageLog) {
_log = log;
_webhookMessageLog = webhookMessageLog;
}
//[Function("QueueWebhooks")]
public async Async.Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string msg) {
_log.Info($"Webhook Message Queued: {msg}");
var obj = JsonSerializer.Deserialize<WebhookMessageQueueObj>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}");
await _webhookMessageLog.ProcessFromQueue(obj);
}
}
using System.Text.Json;
using Microsoft.Azure.Functions.Worker;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service.Functions;
public class QueueWebhooks {
private readonly ILogTracer _log;
private readonly IWebhookMessageLogOperations _webhookMessageLog;
public QueueWebhooks(ILogTracer log, IWebhookMessageLogOperations webhookMessageLog) {
_log = log;
_webhookMessageLog = webhookMessageLog;
}
//[Function("QueueWebhooks")]
public async Async.Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string msg) {
_log.Info($"Webhook Message Queued: {msg}");
var obj = JsonSerializer.Deserialize<WebhookMessageQueueObj>(msg, EntityConverter.GetJsonSerializerOptions()).EnsureNotNull($"wrong data {msg}");
await _webhookMessageLog.ProcessFromQueue(obj);
}
}

View File

@ -1,328 +1,328 @@
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service;
[SerializeValue]
public enum ErrorCode {
INVALID_REQUEST = 450,
INVALID_PERMISSION = 451,
MISSING_EULA_AGREEMENT = 452,
INVALID_JOB = 453,
INVALID_TASK = INVALID_JOB,
UNABLE_TO_ADD_TASK_TO_JOB = 454,
INVALID_CONTAINER = 455,
UNABLE_TO_RESIZE = 456,
UNAUTHORIZED = 457,
UNABLE_TO_USE_STOPPED_JOB = 458,
UNABLE_TO_CHANGE_JOB_DURATION = 459,
UNABLE_TO_CREATE_NETWORK = 460,
VM_CREATE_FAILED = 461,
MISSING_NOTIFICATION = 462,
INVALID_IMAGE = 463,
UNABLE_TO_CREATE = 464,
UNABLE_TO_PORT_FORWARD = 465,
UNABLE_TO_FIND = 467,
TASK_FAILED = 468,
INVALID_NODE = 469,
NOTIFICATION_FAILURE = 470,
UNABLE_TO_UPDATE = 471,
PROXY_FAILED = 472,
INVALID_CONFIGURATION = 473,
UNABLE_TO_CREATE_CONTAINER = 474,
}
public enum VmState {
Init,
ExtensionsLaunch,
ExtensionsFailed,
VmAllocationFailed,
Running,
Stopping,
Stopped
}
public enum WebhookMessageState {
Queued,
Retrying,
Succeeded,
Failed
}
public enum TaskState {
Init,
Waiting,
Scheduled,
SettingUp,
Running,
Stopping,
Stopped,
WaitJob
}
public enum TaskType {
Coverage,
LibfuzzerFuzz,
LibfuzzerCoverage,
LibfuzzerCrashReport,
LibfuzzerMerge,
LibfuzzerRegression,
GenericAnalysis,
GenericSupervisor,
GenericMerge,
GenericGenerator,
GenericCrashReport,
GenericRegression,
DotnetCoverage,
}
public enum Os {
Windows,
Linux
}
public enum ContainerType {
Analysis,
Coverage,
Crashes,
Inputs,
NoRepro,
ReadonlyInputs,
Reports,
Setup,
Tools,
UniqueInputs,
UniqueReports,
RegressionReports,
Logs
}
[SkipRename]
public enum StatsFormat {
AFL
}
public enum TaskDebugFlag {
KeepNodeOnFailure,
KeepNodeOnCompletion,
}
public enum ScalesetState {
Init,
Setup,
Resize,
Running,
Shutdown,
Halt,
CreationFailed
}
public enum JobState {
Init,
Enabled,
Stopping,
Stopped
}
public static class JobStateHelper {
private static readonly IReadOnlySet<JobState> _shuttingDown = new HashSet<JobState>(new[] { JobState.Stopping, JobState.Stopped });
private static readonly IReadOnlySet<JobState> _avaiable = new HashSet<JobState>(new[] { JobState.Init, JobState.Enabled });
private static readonly IReadOnlySet<JobState> _needsWork = new HashSet<JobState>(new[] { JobState.Init, JobState.Stopping });
public static IReadOnlySet<JobState> Available => _avaiable;
public static IReadOnlySet<JobState> NeedsWork => _needsWork;
public static IReadOnlySet<JobState> ShuttingDown => _shuttingDown;
}
public static class ScalesetStateHelper {
private static readonly IReadOnlySet<ScalesetState> _canUpdate = new HashSet<ScalesetState> { ScalesetState.Init, ScalesetState.Resize };
private static readonly IReadOnlySet<ScalesetState> _needsWork =
new HashSet<ScalesetState>{
ScalesetState.Init,
ScalesetState.Setup,
ScalesetState.Resize,
ScalesetState.Shutdown,
ScalesetState.Halt
};
private static readonly IReadOnlySet<ScalesetState> _available = new HashSet<ScalesetState> { ScalesetState.Resize, ScalesetState.Running };
private static readonly IReadOnlySet<ScalesetState> _resizing = new HashSet<ScalesetState> { ScalesetState.Halt, ScalesetState.Init, ScalesetState.Setup };
/// set of states that indicate the scaleset can be updated
public static IReadOnlySet<ScalesetState> CanUpdate => _canUpdate;
/// set of states that indicate work is needed during eventing
public static IReadOnlySet<ScalesetState> NeedsWork => _needsWork;
/// set of states that indicate if it's available for work
public static IReadOnlySet<ScalesetState> Available => _available;
/// set of states that indicate scaleset is resizing
public static IReadOnlySet<ScalesetState> Resizing => _resizing;
}
public static class VmStateHelper {
private static readonly IReadOnlySet<VmState> _needsWork = new HashSet<VmState> { VmState.Init, VmState.ExtensionsLaunch, VmState.Stopping };
private static readonly IReadOnlySet<VmState> _available = new HashSet<VmState> { VmState.Init, VmState.ExtensionsLaunch, VmState.ExtensionsFailed, VmState.VmAllocationFailed, VmState.Running, };
public static IReadOnlySet<VmState> NeedsWork => _needsWork;
public static IReadOnlySet<VmState> Available => _available;
}
public static class TaskStateHelper {
public static readonly IReadOnlySet<TaskState> AvailableStates =
new HashSet<TaskState> { TaskState.Waiting, TaskState.Scheduled, TaskState.SettingUp, TaskState.Running, TaskState.WaitJob };
public static readonly IReadOnlySet<TaskState> NeedsWorkStates =
new HashSet<TaskState> { TaskState.Init, TaskState.Stopping };
public static readonly IReadOnlySet<TaskState> ShuttingDownStates =
new HashSet<TaskState> { TaskState.Stopping, TaskState.Stopped };
public static readonly IReadOnlySet<TaskState> HasStartedStates =
new HashSet<TaskState> { TaskState.Running, TaskState.Stopping, TaskState.Stopped };
public static bool Available(this TaskState state) => AvailableStates.Contains(state);
public static bool NeedsWork(this TaskState state) => NeedsWorkStates.Contains(state);
public static bool ShuttingDown(this TaskState state) => ShuttingDownStates.Contains(state);
public static bool HasStarted(this TaskState state) => HasStartedStates.Contains(state);
}
public enum PoolState {
Init,
Running,
Shutdown,
Halt
}
public static class PoolStateHelper {
private static readonly IReadOnlySet<PoolState> _needsWork = new HashSet<PoolState> { PoolState.Init, PoolState.Shutdown, PoolState.Halt };
private static readonly IReadOnlySet<PoolState> _available = new HashSet<PoolState> { PoolState.Running };
public static IReadOnlySet<PoolState> NeedsWork => _needsWork;
public static IReadOnlySet<PoolState> Available => _available;
}
[SkipRename]
public enum Architecture {
x86_64
}
public enum TaskFeature {
InputQueueFromContainer,
SupervisorExe,
SupervisorEnv,
SupervisorOptions,
SupervisorInputMarker,
StatsFile,
StatsFormat,
TargetExe,
TargetExeOptional,
TargetEnv,
TargetOptions,
AnalyzerExe,
AnalyzerEnv,
AnalyzerOptions,
RenameOutput,
TargetOptionsMerge,
TargetWorkers,
GeneratorExe,
GeneratorEnv,
GeneratorOptions,
WaitForFiles,
TargetTimeout,
CheckAsanLog,
CheckDebugger,
CheckRetryCount,
EnsembleSyncDelay,
PreserveExistingOutputs,
CheckFuzzerHelp,
ExpectCrashOnFailure,
ReportList,
MinimizedStackDepth,
CoverageFilter,
TargetMustUseInput
}
[Flags]
public enum ContainerPermission {
Read = 1 << 0,
Write = 1 << 1,
List = 1 << 2,
Delete = 1 << 3,
}
public enum Compare {
Equal,
AtLeast,
AtMost
}
public enum AgentMode {
Fuzz,
Repro,
Proxy
}
public enum NodeState {
Init,
Free,
SettingUp,
Rebooting,
Ready,
Busy,
Done,
Shutdown,
Halt,
}
public static class NodeStateHelper {
private static readonly IReadOnlySet<NodeState> _needsWork =
new HashSet<NodeState>(new[] { NodeState.Done, NodeState.Shutdown, NodeState.Halt });
private static readonly IReadOnlySet<NodeState> _readyForReset
= new HashSet<NodeState>(new[] { NodeState.Done, NodeState.Shutdown, NodeState.Halt });
private static readonly IReadOnlySet<NodeState> _canProcessNewWork =
new HashSet<NodeState>(new[] { NodeState.Free });
private static readonly IReadOnlySet<NodeState> _busy =
new HashSet<NodeState>(new[] { NodeState.Busy });
public static IReadOnlySet<NodeState> BusyStates => _busy;
public static IReadOnlySet<NodeState> NeedsWorkStates => _needsWork;
public static bool NeedsWork(this NodeState state) => _needsWork.Contains(state);
///If Node is in one of these states, ignore updates from the agent.
public static bool ReadyForReset(this NodeState state) => _readyForReset.Contains(state);
public static bool CanProcessNewWork(this NodeState state) => _canProcessNewWork.Contains(state);
}
public enum NodeDisposalStrategy {
ScaleIn,
Decomission
}
public enum GithubIssueState {
Open,
Closed
}
public enum GithubIssueSearchMatch {
Title,
Body
}
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service;
[SerializeValue]
public enum ErrorCode {
INVALID_REQUEST = 450,
INVALID_PERMISSION = 451,
MISSING_EULA_AGREEMENT = 452,
INVALID_JOB = 453,
INVALID_TASK = INVALID_JOB,
UNABLE_TO_ADD_TASK_TO_JOB = 454,
INVALID_CONTAINER = 455,
UNABLE_TO_RESIZE = 456,
UNAUTHORIZED = 457,
UNABLE_TO_USE_STOPPED_JOB = 458,
UNABLE_TO_CHANGE_JOB_DURATION = 459,
UNABLE_TO_CREATE_NETWORK = 460,
VM_CREATE_FAILED = 461,
MISSING_NOTIFICATION = 462,
INVALID_IMAGE = 463,
UNABLE_TO_CREATE = 464,
UNABLE_TO_PORT_FORWARD = 465,
UNABLE_TO_FIND = 467,
TASK_FAILED = 468,
INVALID_NODE = 469,
NOTIFICATION_FAILURE = 470,
UNABLE_TO_UPDATE = 471,
PROXY_FAILED = 472,
INVALID_CONFIGURATION = 473,
UNABLE_TO_CREATE_CONTAINER = 474,
}
public enum VmState {
Init,
ExtensionsLaunch,
ExtensionsFailed,
VmAllocationFailed,
Running,
Stopping,
Stopped
}
public enum WebhookMessageState {
Queued,
Retrying,
Succeeded,
Failed
}
public enum TaskState {
Init,
Waiting,
Scheduled,
SettingUp,
Running,
Stopping,
Stopped,
WaitJob
}
public enum TaskType {
Coverage,
LibfuzzerFuzz,
LibfuzzerCoverage,
LibfuzzerCrashReport,
LibfuzzerMerge,
LibfuzzerRegression,
GenericAnalysis,
GenericSupervisor,
GenericMerge,
GenericGenerator,
GenericCrashReport,
GenericRegression,
DotnetCoverage,
}
public enum Os {
Windows,
Linux
}
public enum ContainerType {
Analysis,
Coverage,
Crashes,
Inputs,
NoRepro,
ReadonlyInputs,
Reports,
Setup,
Tools,
UniqueInputs,
UniqueReports,
RegressionReports,
Logs
}
[SkipRename]
public enum StatsFormat {
AFL
}
public enum TaskDebugFlag {
KeepNodeOnFailure,
KeepNodeOnCompletion,
}
public enum ScalesetState {
Init,
Setup,
Resize,
Running,
Shutdown,
Halt,
CreationFailed
}
public enum JobState {
Init,
Enabled,
Stopping,
Stopped
}
public static class JobStateHelper {
private static readonly IReadOnlySet<JobState> _shuttingDown = new HashSet<JobState>(new[] { JobState.Stopping, JobState.Stopped });
private static readonly IReadOnlySet<JobState> _avaiable = new HashSet<JobState>(new[] { JobState.Init, JobState.Enabled });
private static readonly IReadOnlySet<JobState> _needsWork = new HashSet<JobState>(new[] { JobState.Init, JobState.Stopping });
public static IReadOnlySet<JobState> Available => _avaiable;
public static IReadOnlySet<JobState> NeedsWork => _needsWork;
public static IReadOnlySet<JobState> ShuttingDown => _shuttingDown;
}
public static class ScalesetStateHelper {
private static readonly IReadOnlySet<ScalesetState> _canUpdate = new HashSet<ScalesetState> { ScalesetState.Init, ScalesetState.Resize };
private static readonly IReadOnlySet<ScalesetState> _needsWork =
new HashSet<ScalesetState>{
ScalesetState.Init,
ScalesetState.Setup,
ScalesetState.Resize,
ScalesetState.Shutdown,
ScalesetState.Halt
};
private static readonly IReadOnlySet<ScalesetState> _available = new HashSet<ScalesetState> { ScalesetState.Resize, ScalesetState.Running };
private static readonly IReadOnlySet<ScalesetState> _resizing = new HashSet<ScalesetState> { ScalesetState.Halt, ScalesetState.Init, ScalesetState.Setup };
/// set of states that indicate the scaleset can be updated
public static IReadOnlySet<ScalesetState> CanUpdate => _canUpdate;
/// set of states that indicate work is needed during eventing
public static IReadOnlySet<ScalesetState> NeedsWork => _needsWork;
/// set of states that indicate if it's available for work
public static IReadOnlySet<ScalesetState> Available => _available;
/// set of states that indicate scaleset is resizing
public static IReadOnlySet<ScalesetState> Resizing => _resizing;
}
public static class VmStateHelper {
private static readonly IReadOnlySet<VmState> _needsWork = new HashSet<VmState> { VmState.Init, VmState.ExtensionsLaunch, VmState.Stopping };
private static readonly IReadOnlySet<VmState> _available = new HashSet<VmState> { VmState.Init, VmState.ExtensionsLaunch, VmState.ExtensionsFailed, VmState.VmAllocationFailed, VmState.Running, };
public static IReadOnlySet<VmState> NeedsWork => _needsWork;
public static IReadOnlySet<VmState> Available => _available;
}
public static class TaskStateHelper {
public static readonly IReadOnlySet<TaskState> AvailableStates =
new HashSet<TaskState> { TaskState.Waiting, TaskState.Scheduled, TaskState.SettingUp, TaskState.Running, TaskState.WaitJob };
public static readonly IReadOnlySet<TaskState> NeedsWorkStates =
new HashSet<TaskState> { TaskState.Init, TaskState.Stopping };
public static readonly IReadOnlySet<TaskState> ShuttingDownStates =
new HashSet<TaskState> { TaskState.Stopping, TaskState.Stopped };
public static readonly IReadOnlySet<TaskState> HasStartedStates =
new HashSet<TaskState> { TaskState.Running, TaskState.Stopping, TaskState.Stopped };
public static bool Available(this TaskState state) => AvailableStates.Contains(state);
public static bool NeedsWork(this TaskState state) => NeedsWorkStates.Contains(state);
public static bool ShuttingDown(this TaskState state) => ShuttingDownStates.Contains(state);
public static bool HasStarted(this TaskState state) => HasStartedStates.Contains(state);
}
public enum PoolState {
Init,
Running,
Shutdown,
Halt
}
public static class PoolStateHelper {
private static readonly IReadOnlySet<PoolState> _needsWork = new HashSet<PoolState> { PoolState.Init, PoolState.Shutdown, PoolState.Halt };
private static readonly IReadOnlySet<PoolState> _available = new HashSet<PoolState> { PoolState.Running };
public static IReadOnlySet<PoolState> NeedsWork => _needsWork;
public static IReadOnlySet<PoolState> Available => _available;
}
[SkipRename]
public enum Architecture {
x86_64
}
public enum TaskFeature {
InputQueueFromContainer,
SupervisorExe,
SupervisorEnv,
SupervisorOptions,
SupervisorInputMarker,
StatsFile,
StatsFormat,
TargetExe,
TargetExeOptional,
TargetEnv,
TargetOptions,
AnalyzerExe,
AnalyzerEnv,
AnalyzerOptions,
RenameOutput,
TargetOptionsMerge,
TargetWorkers,
GeneratorExe,
GeneratorEnv,
GeneratorOptions,
WaitForFiles,
TargetTimeout,
CheckAsanLog,
CheckDebugger,
CheckRetryCount,
EnsembleSyncDelay,
PreserveExistingOutputs,
CheckFuzzerHelp,
ExpectCrashOnFailure,
ReportList,
MinimizedStackDepth,
CoverageFilter,
TargetMustUseInput
}
[Flags]
public enum ContainerPermission {
Read = 1 << 0,
Write = 1 << 1,
List = 1 << 2,
Delete = 1 << 3,
}
public enum Compare {
Equal,
AtLeast,
AtMost
}
public enum AgentMode {
Fuzz,
Repro,
Proxy
}
public enum NodeState {
Init,
Free,
SettingUp,
Rebooting,
Ready,
Busy,
Done,
Shutdown,
Halt,
}
public static class NodeStateHelper {
private static readonly IReadOnlySet<NodeState> _needsWork =
new HashSet<NodeState>(new[] { NodeState.Done, NodeState.Shutdown, NodeState.Halt });
private static readonly IReadOnlySet<NodeState> _readyForReset
= new HashSet<NodeState>(new[] { NodeState.Done, NodeState.Shutdown, NodeState.Halt });
private static readonly IReadOnlySet<NodeState> _canProcessNewWork =
new HashSet<NodeState>(new[] { NodeState.Free });
private static readonly IReadOnlySet<NodeState> _busy =
new HashSet<NodeState>(new[] { NodeState.Busy });
public static IReadOnlySet<NodeState> BusyStates => _busy;
public static IReadOnlySet<NodeState> NeedsWorkStates => _needsWork;
public static bool NeedsWork(this NodeState state) => _needsWork.Contains(state);
///If Node is in one of these states, ignore updates from the agent.
public static bool ReadyForReset(this NodeState state) => _readyForReset.Contains(state);
public static bool CanProcessNewWork(this NodeState state) => _canProcessNewWork.Contains(state);
}
public enum NodeDisposalStrategy {
ScaleIn,
Decomission
}
public enum GithubIssueState {
Open,
Closed
}
public enum GithubIssueSearchMatch {
Title,
Body
}

View File

@ -1,135 +1,135 @@
using System.Threading.Tasks;
using ApiService.OneFuzzLib.Orm;
using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
public interface IProxyOperations : IStatefulOrm<Proxy, VmState> {
Task<Proxy?> GetByProxyId(Guid proxyId);
Async.Task SetState(Proxy proxy, VmState state);
bool IsAlive(Proxy proxy);
Async.Task SaveProxyConfig(Proxy proxy);
bool IsOutdated(Proxy proxy);
Async.Task<Proxy?> GetOrCreate(string region);
}
public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IProxyOperations {
static TimeSpan PROXY_LIFESPAN = TimeSpan.FromDays(7);
public ProxyOperations(ILogTracer log, IOnefuzzContext context)
: base(log.WithTag("Component", "scaleset-proxy"), context) {
}
public async Task<Proxy?> GetByProxyId(Guid proxyId) {
var data = QueryAsync(filter: $"RowKey eq '{proxyId}'");
return await data.FirstOrDefaultAsync();
}
public async Async.Task<Proxy?> GetOrCreate(string region) {
var proxyList = QueryAsync(filter: $"region eq '{region}' and outdated eq false");
await foreach (var proxy in proxyList) {
if (IsOutdated(proxy)) {
await Replace(proxy with { Outdated = true });
continue;
}
if (!VmStateHelper.Available.Contains(proxy.State)) {
continue;
}
return proxy;
}
_logTracer.Info($"creating proxy: region:{region}");
var newProxy = new Proxy(region, Guid.NewGuid(), DateTimeOffset.UtcNow, VmState.Init, Auth.BuildAuth(), null, null, _context.ServiceConfiguration.OneFuzzVersion, null, false);
await Replace(newProxy);
await _context.Events.SendEvent(new EventProxyCreated(region, newProxy.ProxyId));
return newProxy;
}
public bool IsAlive(Proxy proxy) {
var tenMinutesAgo = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10);
if (proxy.Heartbeat != null && proxy.Heartbeat.TimeStamp < tenMinutesAgo) {
_logTracer.Info($"last heartbeat is more than an 10 minutes old: {proxy.Region} - last heartbeat:{proxy.Heartbeat} compared_to:{tenMinutesAgo}");
return false;
}
if (proxy.Heartbeat != null && proxy.TimeStamp != null && proxy.TimeStamp < tenMinutesAgo) {
_logTracer.Error($"no heartbeat in the last 10 minutes: {proxy.Region} timestamp: {proxy.TimeStamp} compared_to:{tenMinutesAgo}");
return false;
}
return true;
}
public bool IsOutdated(Proxy proxy) {
if (!VmStateHelper.Available.Contains(proxy.State)) {
return false;
}
if (proxy.Version != _context.ServiceConfiguration.OneFuzzVersion) {
_logTracer.Info($"mismatch version: proxy:{proxy.Version} service:{_context.ServiceConfiguration.OneFuzzVersion} state:{proxy.State}");
return true;
}
if (proxy.CreatedTimestamp != null) {
if (proxy.CreatedTimestamp < (DateTimeOffset.UtcNow - PROXY_LIFESPAN)) {
_logTracer.Info($"proxy older than 7 days:proxy-created:{proxy.CreatedTimestamp} state:{proxy.State}");
return true;
}
}
return false;
}
public async Async.Task SaveProxyConfig(Proxy proxy) {
var forwards = await GetForwards(proxy);
var url = (await _context.Containers.GetFileSasUrl(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", StorageType.Config, BlobSasPermissions.Read)).EnsureNotNull("Can't generate file sas");
var queueSas = await _context.Queue.GetQueueSas("proxy", StorageType.Config, QueueSasPermissions.Add).EnsureNotNull("can't generate queue sas") ?? throw new Exception("Queue sas is null");
var proxyConfig = new ProxyConfig(
Url: url,
Notification: queueSas,
Region: proxy.Region,
ProxyId: proxy.ProxyId,
Forwards: forwards,
InstanceTelemetryKey: _context.ServiceConfiguration.ApplicationInsightsInstrumentationKey.EnsureNotNull("missing InstrumentationKey"),
MicrosoftTelemetryKey: _context.ServiceConfiguration.OneFuzzTelemetry.EnsureNotNull("missing Telemetry"),
InstanceId: await _context.Containers.GetInstanceId());
await _context.Containers.SaveBlob(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", _entityConverter.ToJsonString(proxyConfig), StorageType.Config);
}
public async Async.Task SetState(Proxy proxy, VmState state) {
if (proxy.State == state) {
return;
}
await Replace(proxy with { State = state });
await _context.Events.SendEvent(new EventProxyStateUpdated(proxy.Region, proxy.ProxyId, proxy.State));
}
public async Async.Task<List<Forward>> GetForwards(Proxy proxy) {
var forwards = new List<Forward>();
await foreach (var entry in _context.ProxyForwardOperations.SearchForward(region: proxy.Region, proxyId: proxy.ProxyId)) {
if (entry.EndTime < DateTimeOffset.UtcNow) {
await _context.ProxyForwardOperations.Delete(entry);
} else {
forwards.Add(new Forward(entry.Port, entry.DstPort, entry.DstIp));
}
}
return forwards;
}
}
using System.Threading.Tasks;
using ApiService.OneFuzzLib.Orm;
using Azure.Storage.Sas;
namespace Microsoft.OneFuzz.Service;
public interface IProxyOperations : IStatefulOrm<Proxy, VmState> {
Task<Proxy?> GetByProxyId(Guid proxyId);
Async.Task SetState(Proxy proxy, VmState state);
bool IsAlive(Proxy proxy);
Async.Task SaveProxyConfig(Proxy proxy);
bool IsOutdated(Proxy proxy);
Async.Task<Proxy?> GetOrCreate(string region);
}
public class ProxyOperations : StatefulOrm<Proxy, VmState, ProxyOperations>, IProxyOperations {
static TimeSpan PROXY_LIFESPAN = TimeSpan.FromDays(7);
public ProxyOperations(ILogTracer log, IOnefuzzContext context)
: base(log.WithTag("Component", "scaleset-proxy"), context) {
}
public async Task<Proxy?> GetByProxyId(Guid proxyId) {
var data = QueryAsync(filter: $"RowKey eq '{proxyId}'");
return await data.FirstOrDefaultAsync();
}
public async Async.Task<Proxy?> GetOrCreate(string region) {
var proxyList = QueryAsync(filter: $"region eq '{region}' and outdated eq false");
await foreach (var proxy in proxyList) {
if (IsOutdated(proxy)) {
await Replace(proxy with { Outdated = true });
continue;
}
if (!VmStateHelper.Available.Contains(proxy.State)) {
continue;
}
return proxy;
}
_logTracer.Info($"creating proxy: region:{region}");
var newProxy = new Proxy(region, Guid.NewGuid(), DateTimeOffset.UtcNow, VmState.Init, Auth.BuildAuth(), null, null, _context.ServiceConfiguration.OneFuzzVersion, null, false);
await Replace(newProxy);
await _context.Events.SendEvent(new EventProxyCreated(region, newProxy.ProxyId));
return newProxy;
}
public bool IsAlive(Proxy proxy) {
var tenMinutesAgo = DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10);
if (proxy.Heartbeat != null && proxy.Heartbeat.TimeStamp < tenMinutesAgo) {
_logTracer.Info($"last heartbeat is more than an 10 minutes old: {proxy.Region} - last heartbeat:{proxy.Heartbeat} compared_to:{tenMinutesAgo}");
return false;
}
if (proxy.Heartbeat != null && proxy.TimeStamp != null && proxy.TimeStamp < tenMinutesAgo) {
_logTracer.Error($"no heartbeat in the last 10 minutes: {proxy.Region} timestamp: {proxy.TimeStamp} compared_to:{tenMinutesAgo}");
return false;
}
return true;
}
public bool IsOutdated(Proxy proxy) {
if (!VmStateHelper.Available.Contains(proxy.State)) {
return false;
}
if (proxy.Version != _context.ServiceConfiguration.OneFuzzVersion) {
_logTracer.Info($"mismatch version: proxy:{proxy.Version} service:{_context.ServiceConfiguration.OneFuzzVersion} state:{proxy.State}");
return true;
}
if (proxy.CreatedTimestamp != null) {
if (proxy.CreatedTimestamp < (DateTimeOffset.UtcNow - PROXY_LIFESPAN)) {
_logTracer.Info($"proxy older than 7 days:proxy-created:{proxy.CreatedTimestamp} state:{proxy.State}");
return true;
}
}
return false;
}
public async Async.Task SaveProxyConfig(Proxy proxy) {
var forwards = await GetForwards(proxy);
var url = (await _context.Containers.GetFileSasUrl(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", StorageType.Config, BlobSasPermissions.Read)).EnsureNotNull("Can't generate file sas");
var queueSas = await _context.Queue.GetQueueSas("proxy", StorageType.Config, QueueSasPermissions.Add).EnsureNotNull("can't generate queue sas") ?? throw new Exception("Queue sas is null");
var proxyConfig = new ProxyConfig(
Url: url,
Notification: queueSas,
Region: proxy.Region,
ProxyId: proxy.ProxyId,
Forwards: forwards,
InstanceTelemetryKey: _context.ServiceConfiguration.ApplicationInsightsInstrumentationKey.EnsureNotNull("missing InstrumentationKey"),
MicrosoftTelemetryKey: _context.ServiceConfiguration.OneFuzzTelemetry.EnsureNotNull("missing Telemetry"),
InstanceId: await _context.Containers.GetInstanceId());
await _context.Containers.SaveBlob(new Container("proxy-configs"), $"{proxy.Region}/{proxy.ProxyId}/config.json", _entityConverter.ToJsonString(proxyConfig), StorageType.Config);
}
public async Async.Task SetState(Proxy proxy, VmState state) {
if (proxy.State == state) {
return;
}
await Replace(proxy with { State = state });
await _context.Events.SendEvent(new EventProxyStateUpdated(proxy.Region, proxy.ProxyId, proxy.State));
}
public async Async.Task<List<Forward>> GetForwards(Proxy proxy) {
var forwards = new List<Forward>();
await foreach (var entry in _context.ProxyForwardOperations.SearchForward(region: proxy.Region, proxyId: proxy.ProxyId)) {
if (entry.EndTime < DateTimeOffset.UtcNow) {
await _context.ProxyForwardOperations.Delete(entry);
} else {
forwards.Add(new Forward(entry.Port, entry.DstPort, entry.DstIp));
}
}
return forwards;
}
}

View File

@ -1,253 +1,253 @@
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text.Json;
using System.Threading.Tasks;
using ApiService.OneFuzzLib.Orm;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service;
public interface IWebhookOperations : IOrm<Webhook> {
Async.Task SendEvent(EventMessage eventMessage);
Async.Task<Webhook?> GetByWebhookId(Guid webhookId);
Async.Task<bool> Send(WebhookMessageLog messageLog);
Task<EventPing> Ping(Webhook webhook);
}
public class WebhookOperations : Orm<Webhook>, IWebhookOperations {
private readonly IHttpClientFactory _httpFactory;
public WebhookOperations(IHttpClientFactory httpFactory, ILogTracer log, IOnefuzzContext context)
: base(log, context) {
_httpFactory = httpFactory;
}
async public Async.Task SendEvent(EventMessage eventMessage) {
await foreach (var webhook in GetWebhooksCached()) {
if (!webhook.EventTypes.Contains(eventMessage.EventType)) {
continue;
}
await AddEvent(webhook, eventMessage);
}
}
async private Async.Task AddEvent(Webhook webhook, EventMessage eventMessage) {
var message = new WebhookMessageLog(
EventId: eventMessage.EventId,
EventType: eventMessage.EventType,
Event: eventMessage.Event,
InstanceId: eventMessage.InstanceId,
InstanceName: eventMessage.InstanceName,
WebhookId: webhook.WebhookId,
TryCount: 0
);
var r = await _context.WebhookMessageLogOperations.Replace(message);
if (!r.IsOk) {
var (status, reason) = r.ErrorV;
_logTracer.Error($"Failed to replace webhook message log due to [{status}] {reason}");
}
await _context.WebhookMessageLogOperations.QueueWebhook(message);
}
public async Async.Task<bool> Send(WebhookMessageLog messageLog) {
var webhook = await GetByWebhookId(messageLog.WebhookId);
if (webhook == null || webhook.Url == null) {
throw new Exception($"Invalid Webhook. Webhook with WebhookId: {messageLog.WebhookId} Not Found");
}
var (data, digest) = await BuildMessage(webhookId: webhook.WebhookId, eventId: messageLog.EventId, eventType: messageLog.EventType, webhookEvent: messageLog.Event, secretToken: webhook.SecretToken, messageFormat: webhook.MessageFormat);
var headers = new Dictionary<string, string> { { "User-Agent", $"onefuzz-webhook {_context.ServiceConfiguration.OneFuzzVersion}" } };
if (digest != null) {
headers["X-Onefuzz-Digest"] = digest;
}
var client = new Request(_httpFactory.CreateClient());
_logTracer.Info(data);
var response = client.Post(url: webhook.Url, json: data, headers: headers);
var result = response.Result;
if (result.StatusCode == HttpStatusCode.Accepted) {
return true;
}
return false;
}
public async Task<EventPing> Ping(Webhook webhook) {
var ping = new EventPing(Guid.NewGuid());
var instanceId = await _context.Containers.GetInstanceId();
var instanceName = _context.Creds.GetInstanceName();
await AddEvent(webhook, new EventMessage(Guid.NewGuid(), EventType.Ping, ping, instanceId, instanceName));
return ping;
}
// Not converting to bytes, as it's not neccessary in C#. Just keeping as string.
public async Async.Task<Tuple<string, string?>> BuildMessage(Guid webhookId, Guid eventId, EventType eventType, BaseEvent webhookEvent, String? secretToken, WebhookMessageFormat? messageFormat) {
var entityConverter = new EntityConverter();
string data = "";
if (messageFormat != null && messageFormat == WebhookMessageFormat.EventGrid) {
var eventGridMessage = new[] { new WebhookMessageEventGrid(Id: eventId, Data: webhookEvent, DataVersion: "1.0.0", Subject: _context.Creds.GetInstanceName(), EventType: eventType, EventTime: DateTimeOffset.UtcNow) };
data = JsonSerializer.Serialize(eventGridMessage, options: EntityConverter.GetJsonSerializerOptions());
} else {
var instanceId = await _context.Containers.GetInstanceId();
var webhookMessage = new WebhookMessage(WebhookId: webhookId, EventId: eventId, EventType: eventType, Event: webhookEvent, InstanceId: instanceId, InstanceName: _context.Creds.GetInstanceName());
data = JsonSerializer.Serialize(webhookMessage, options: EntityConverter.GetJsonSerializerOptions());
}
string? digest = null;
var hmac = HMAC.Create("HMACSHA512");
if (secretToken != null && hmac != null) {
hmac.Key = System.Text.Encoding.UTF8.GetBytes(secretToken);
digest = Convert.ToHexString(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(data)));
}
return new Tuple<string, string?>(data, digest);
}
public async Async.Task<Webhook?> GetByWebhookId(Guid webhookId) {
var data = QueryAsync(filter: $"PartitionKey eq '{webhookId}'");
return await data.FirstOrDefaultAsync();
}
//todo: caching
public IAsyncEnumerable<Webhook> GetWebhooksCached() {
return QueryAsync();
}
}
public interface IWebhookMessageLogOperations : IOrm<WebhookMessageLog> {
IAsyncEnumerable<WebhookMessageLog> SearchExpired();
public Async.Task ProcessFromQueue(WebhookMessageQueueObj obj);
public Async.Task QueueWebhook(WebhookMessageLog webhookLog);
}
public class WebhookMessageLogOperations : Orm<WebhookMessageLog>, IWebhookMessageLogOperations {
const int EXPIRE_DAYS = 7;
const int MAX_TRIES = 5;
public WebhookMessageLogOperations(IHttpClientFactory httpFactory, ILogTracer log, IOnefuzzContext context)
: base(log, context) {
}
public async Async.Task QueueWebhook(WebhookMessageLog webhookLog) {
var obj = new WebhookMessageQueueObj(webhookLog.WebhookId, webhookLog.EventId);
TimeSpan? visibilityTimeout = webhookLog.State switch {
WebhookMessageState.Queued => TimeSpan.Zero,
WebhookMessageState.Retrying => TimeSpan.FromSeconds(30),
_ => null
};
if (visibilityTimeout == null) {
_logTracer.WithTags(
new[] {
("WebhookId", webhookLog.WebhookId.ToString()),
("EventId", webhookLog.EventId.ToString()) }
).
Error($"invalid WebhookMessage queue state, not queuing. {webhookLog.WebhookId}:{webhookLog.EventId} - {webhookLog.State}");
} else {
await _context.Queue.QueueObject("webhooks", obj, StorageType.Config, visibilityTimeout: visibilityTimeout);
}
}
public async Async.Task ProcessFromQueue(WebhookMessageQueueObj obj) {
var message = await GetWebhookMessageById(obj.WebhookId, obj.EventId);
if (message == null) {
_logTracer.WithTags(
new[] {
("WebhookId", obj.WebhookId.ToString()),
("EventId", obj.EventId.ToString()) }
).
Error($"webhook message log not found for webhookId: {obj.WebhookId} and eventId: {obj.EventId}");
} else {
await Process(message);
}
}
private async Async.Task Process(WebhookMessageLog message) {
if (message.State == WebhookMessageState.Failed || message.State == WebhookMessageState.Succeeded) {
_logTracer.WithTags(
new[] {
("WebhookId", message.WebhookId.ToString()),
("EventId", message.EventId.ToString()) }
).
Error($"webhook message already handled. {message.WebhookId}:{message.EventId}");
return;
}
var newMessage = message with { TryCount = message.TryCount + 1 };
_logTracer.Info($"sending webhook: {message.WebhookId}:{message.EventId}");
var success = await Send(newMessage);
if (success) {
newMessage = newMessage with { State = WebhookMessageState.Succeeded };
await Replace(newMessage);
_logTracer.Info($"sent webhook event {newMessage.WebhookId}:{newMessage.EventId}");
} else if (newMessage.TryCount < MAX_TRIES) {
newMessage = newMessage with { State = WebhookMessageState.Retrying };
await Replace(newMessage);
await QueueWebhook(newMessage);
_logTracer.Warning($"sending webhook event failed, re-queued {newMessage.WebhookId}:{newMessage.EventId}");
} else {
newMessage = newMessage with { State = WebhookMessageState.Failed };
await Replace(newMessage);
_logTracer.Info($"sending webhook: {newMessage.WebhookId} event: {newMessage.EventId} failed {newMessage.TryCount} times.");
}
}
private async Async.Task<bool> Send(WebhookMessageLog message) {
var webhook = await _context.WebhookOperations.GetByWebhookId(message.WebhookId);
if (webhook == null) {
_logTracer.WithTags(
new[] {
("WebhookId", message.WebhookId.ToString()),
}
).
Error($"webhook not found for webhookId: {message.WebhookId}");
return false;
}
try {
return await _context.WebhookOperations.Send(message);
} catch (Exception exc) {
_logTracer.WithTags(
new[] {
("WebhookId", message.WebhookId.ToString())
}
).
Exception(exc);
return false;
}
}
private void QueueObject(string v, WebhookMessageQueueObj obj, StorageType config, int? visibility_timeout) {
throw new NotImplementedException();
}
public IAsyncEnumerable<WebhookMessageLog> SearchExpired() {
var expireTime = (DateTimeOffset.UtcNow - TimeSpan.FromDays(EXPIRE_DAYS)).ToString("o");
var timeFilter = $"Timestamp lt datetime'{expireTime}'";
return QueryAsync(filter: timeFilter);
}
public async Async.Task<WebhookMessageLog?> GetWebhookMessageById(Guid webhookId, Guid eventId) {
var data = QueryAsync(filter: $"PartitionKey eq '{webhookId}' and RowKey eq '{eventId}'");
return await data.FirstOrDefaultAsync();
}
}
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text.Json;
using System.Threading.Tasks;
using ApiService.OneFuzzLib.Orm;
using Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
namespace Microsoft.OneFuzz.Service;
public interface IWebhookOperations : IOrm<Webhook> {
Async.Task SendEvent(EventMessage eventMessage);
Async.Task<Webhook?> GetByWebhookId(Guid webhookId);
Async.Task<bool> Send(WebhookMessageLog messageLog);
Task<EventPing> Ping(Webhook webhook);
}
public class WebhookOperations : Orm<Webhook>, IWebhookOperations {
private readonly IHttpClientFactory _httpFactory;
public WebhookOperations(IHttpClientFactory httpFactory, ILogTracer log, IOnefuzzContext context)
: base(log, context) {
_httpFactory = httpFactory;
}
async public Async.Task SendEvent(EventMessage eventMessage) {
await foreach (var webhook in GetWebhooksCached()) {
if (!webhook.EventTypes.Contains(eventMessage.EventType)) {
continue;
}
await AddEvent(webhook, eventMessage);
}
}
async private Async.Task AddEvent(Webhook webhook, EventMessage eventMessage) {
var message = new WebhookMessageLog(
EventId: eventMessage.EventId,
EventType: eventMessage.EventType,
Event: eventMessage.Event,
InstanceId: eventMessage.InstanceId,
InstanceName: eventMessage.InstanceName,
WebhookId: webhook.WebhookId,
TryCount: 0
);
var r = await _context.WebhookMessageLogOperations.Replace(message);
if (!r.IsOk) {
var (status, reason) = r.ErrorV;
_logTracer.Error($"Failed to replace webhook message log due to [{status}] {reason}");
}
await _context.WebhookMessageLogOperations.QueueWebhook(message);
}
public async Async.Task<bool> Send(WebhookMessageLog messageLog) {
var webhook = await GetByWebhookId(messageLog.WebhookId);
if (webhook == null || webhook.Url == null) {
throw new Exception($"Invalid Webhook. Webhook with WebhookId: {messageLog.WebhookId} Not Found");
}
var (data, digest) = await BuildMessage(webhookId: webhook.WebhookId, eventId: messageLog.EventId, eventType: messageLog.EventType, webhookEvent: messageLog.Event, secretToken: webhook.SecretToken, messageFormat: webhook.MessageFormat);
var headers = new Dictionary<string, string> { { "User-Agent", $"onefuzz-webhook {_context.ServiceConfiguration.OneFuzzVersion}" } };
if (digest != null) {
headers["X-Onefuzz-Digest"] = digest;
}
var client = new Request(_httpFactory.CreateClient());
_logTracer.Info(data);
var response = client.Post(url: webhook.Url, json: data, headers: headers);
var result = response.Result;
if (result.StatusCode == HttpStatusCode.Accepted) {
return true;
}
return false;
}
public async Task<EventPing> Ping(Webhook webhook) {
var ping = new EventPing(Guid.NewGuid());
var instanceId = await _context.Containers.GetInstanceId();
var instanceName = _context.Creds.GetInstanceName();
await AddEvent(webhook, new EventMessage(Guid.NewGuid(), EventType.Ping, ping, instanceId, instanceName));
return ping;
}
// Not converting to bytes, as it's not neccessary in C#. Just keeping as string.
public async Async.Task<Tuple<string, string?>> BuildMessage(Guid webhookId, Guid eventId, EventType eventType, BaseEvent webhookEvent, String? secretToken, WebhookMessageFormat? messageFormat) {
var entityConverter = new EntityConverter();
string data = "";
if (messageFormat != null && messageFormat == WebhookMessageFormat.EventGrid) {
var eventGridMessage = new[] { new WebhookMessageEventGrid(Id: eventId, Data: webhookEvent, DataVersion: "1.0.0", Subject: _context.Creds.GetInstanceName(), EventType: eventType, EventTime: DateTimeOffset.UtcNow) };
data = JsonSerializer.Serialize(eventGridMessage, options: EntityConverter.GetJsonSerializerOptions());
} else {
var instanceId = await _context.Containers.GetInstanceId();
var webhookMessage = new WebhookMessage(WebhookId: webhookId, EventId: eventId, EventType: eventType, Event: webhookEvent, InstanceId: instanceId, InstanceName: _context.Creds.GetInstanceName());
data = JsonSerializer.Serialize(webhookMessage, options: EntityConverter.GetJsonSerializerOptions());
}
string? digest = null;
var hmac = HMAC.Create("HMACSHA512");
if (secretToken != null && hmac != null) {
hmac.Key = System.Text.Encoding.UTF8.GetBytes(secretToken);
digest = Convert.ToHexString(hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(data)));
}
return new Tuple<string, string?>(data, digest);
}
public async Async.Task<Webhook?> GetByWebhookId(Guid webhookId) {
var data = QueryAsync(filter: $"PartitionKey eq '{webhookId}'");
return await data.FirstOrDefaultAsync();
}
//todo: caching
public IAsyncEnumerable<Webhook> GetWebhooksCached() {
return QueryAsync();
}
}
public interface IWebhookMessageLogOperations : IOrm<WebhookMessageLog> {
IAsyncEnumerable<WebhookMessageLog> SearchExpired();
public Async.Task ProcessFromQueue(WebhookMessageQueueObj obj);
public Async.Task QueueWebhook(WebhookMessageLog webhookLog);
}
public class WebhookMessageLogOperations : Orm<WebhookMessageLog>, IWebhookMessageLogOperations {
const int EXPIRE_DAYS = 7;
const int MAX_TRIES = 5;
public WebhookMessageLogOperations(IHttpClientFactory httpFactory, ILogTracer log, IOnefuzzContext context)
: base(log, context) {
}
public async Async.Task QueueWebhook(WebhookMessageLog webhookLog) {
var obj = new WebhookMessageQueueObj(webhookLog.WebhookId, webhookLog.EventId);
TimeSpan? visibilityTimeout = webhookLog.State switch {
WebhookMessageState.Queued => TimeSpan.Zero,
WebhookMessageState.Retrying => TimeSpan.FromSeconds(30),
_ => null
};
if (visibilityTimeout == null) {
_logTracer.WithTags(
new[] {
("WebhookId", webhookLog.WebhookId.ToString()),
("EventId", webhookLog.EventId.ToString()) }
).
Error($"invalid WebhookMessage queue state, not queuing. {webhookLog.WebhookId}:{webhookLog.EventId} - {webhookLog.State}");
} else {
await _context.Queue.QueueObject("webhooks", obj, StorageType.Config, visibilityTimeout: visibilityTimeout);
}
}
public async Async.Task ProcessFromQueue(WebhookMessageQueueObj obj) {
var message = await GetWebhookMessageById(obj.WebhookId, obj.EventId);
if (message == null) {
_logTracer.WithTags(
new[] {
("WebhookId", obj.WebhookId.ToString()),
("EventId", obj.EventId.ToString()) }
).
Error($"webhook message log not found for webhookId: {obj.WebhookId} and eventId: {obj.EventId}");
} else {
await Process(message);
}
}
private async Async.Task Process(WebhookMessageLog message) {
if (message.State == WebhookMessageState.Failed || message.State == WebhookMessageState.Succeeded) {
_logTracer.WithTags(
new[] {
("WebhookId", message.WebhookId.ToString()),
("EventId", message.EventId.ToString()) }
).
Error($"webhook message already handled. {message.WebhookId}:{message.EventId}");
return;
}
var newMessage = message with { TryCount = message.TryCount + 1 };
_logTracer.Info($"sending webhook: {message.WebhookId}:{message.EventId}");
var success = await Send(newMessage);
if (success) {
newMessage = newMessage with { State = WebhookMessageState.Succeeded };
await Replace(newMessage);
_logTracer.Info($"sent webhook event {newMessage.WebhookId}:{newMessage.EventId}");
} else if (newMessage.TryCount < MAX_TRIES) {
newMessage = newMessage with { State = WebhookMessageState.Retrying };
await Replace(newMessage);
await QueueWebhook(newMessage);
_logTracer.Warning($"sending webhook event failed, re-queued {newMessage.WebhookId}:{newMessage.EventId}");
} else {
newMessage = newMessage with { State = WebhookMessageState.Failed };
await Replace(newMessage);
_logTracer.Info($"sending webhook: {newMessage.WebhookId} event: {newMessage.EventId} failed {newMessage.TryCount} times.");
}
}
private async Async.Task<bool> Send(WebhookMessageLog message) {
var webhook = await _context.WebhookOperations.GetByWebhookId(message.WebhookId);
if (webhook == null) {
_logTracer.WithTags(
new[] {
("WebhookId", message.WebhookId.ToString()),
}
).
Error($"webhook not found for webhookId: {message.WebhookId}");
return false;
}
try {
return await _context.WebhookOperations.Send(message);
} catch (Exception exc) {
_logTracer.WithTags(
new[] {
("WebhookId", message.WebhookId.ToString())
}
).
Exception(exc);
return false;
}
}
private void QueueObject(string v, WebhookMessageQueueObj obj, StorageType config, int? visibility_timeout) {
throw new NotImplementedException();
}
public IAsyncEnumerable<WebhookMessageLog> SearchExpired() {
var expireTime = (DateTimeOffset.UtcNow - TimeSpan.FromDays(EXPIRE_DAYS)).ToString("o");
var timeFilter = $"Timestamp lt datetime'{expireTime}'";
return QueryAsync(filter: timeFilter);
}
public async Async.Task<WebhookMessageLog?> GetWebhookMessageById(Guid webhookId, Guid eventId) {
var data = QueryAsync(filter: $"PartitionKey eq '{webhookId}' and RowKey eq '{eventId}'");
return await data.FirstOrDefaultAsync();
}
}

View File

@ -1,325 +1,325 @@
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure;
using Azure.Data.Tables;
namespace Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
public abstract record EntityBase {
[JsonIgnore] public ETag? ETag { get; set; }
public DateTimeOffset? TimeStamp { get; set; }
// https://docs.microsoft.com/en-us/rest/api/storageservices/designing-a-scalable-partitioning-strategy-for-azure-table-storage#yyy
// Produce "good-quality-table-key" based on a DateTimeOffset timestamp
public static string NewSortedKey => $"{DateTimeOffset.MaxValue.Ticks - DateTimeOffset.UtcNow.Ticks}";
}
public abstract record StatefulEntityBase<T>([property: JsonIgnore] T BaseState) : EntityBase() where T : Enum;
/// How the value is populated
public enum InitMethod {
//T() will be used
DefaultConstructor,
}
[AttributeUsage(AttributeTargets.Parameter)]
public class DefaultValueAttribute : Attribute {
public InitMethod InitMethod { get; }
public DefaultValueAttribute(InitMethod initMethod) {
InitMethod = initMethod;
}
}
/// Indicates that the enum cases should no be renamed
[AttributeUsage(AttributeTargets.Enum)]
public class SerializeValueAttribute : Attribute { }
/// Indicates that the enum cases should no be renamed
[AttributeUsage(AttributeTargets.Enum)]
public class SkipRenameAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter)]
public class RowKeyAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter)]
public class PartitionKeyAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Property)]
public class TypeDiscrimnatorAttribute : Attribute {
public string FieldName { get; }
// the type of a function that takes the value of fieldName as an input and return the type
public Type ConverterType { get; }
public TypeDiscrimnatorAttribute(string fieldName, Type converterType) {
if (!converterType.IsAssignableTo(typeof(ITypeProvider))) {
throw new ArgumentException($"the provided type needs to implement ITypeProvider");
}
FieldName = fieldName;
ConverterType = converterType;
}
}
public interface ITypeProvider {
Type GetTypeInfo(object input);
}
public enum EntityPropertyKind {
PartitionKey,
RowKey,
Column
}
public record EntityProperty(
string name,
string columnName,
Type type,
EntityPropertyKind kind,
(TypeDiscrimnatorAttribute, ITypeProvider)? discriminator,
DefaultValueAttribute? defaultValue,
ParameterInfo parameterInfo
);
public record EntityInfo(Type type, ILookup<string, EntityProperty> properties, Func<object?[], object> constructor);
class OnefuzzNamingPolicy : JsonNamingPolicy {
public override string ConvertName(string name) {
return CaseConverter.PascalToSnake(name);
}
}
public class EntityConverter {
private readonly JsonSerializerOptions _options;
private readonly ConcurrentDictionary<Type, EntityInfo> _cache;
public EntityConverter() {
_options = GetJsonSerializerOptions();
_cache = new ConcurrentDictionary<Type, EntityInfo>();
}
public static JsonSerializerOptions GetJsonSerializerOptions() {
var options = new JsonSerializerOptions() {
PropertyNamingPolicy = new OnefuzzNamingPolicy(),
};
options.Converters.Add(new CustomEnumConverterFactory());
options.Converters.Add(new PolymorphicConverterFactory());
return options;
}
internal static Func<object?[], object> BuildConstructerFrom(ConstructorInfo constructorInfo) {
var constructorParameters = Expression.Parameter(typeof(object?[]));
var parameterExpressions =
constructorInfo.GetParameters().Select((parameterInfo, i) => {
var ithIndex = Expression.Constant(i);
var ithParameter = Expression.ArrayIndex(constructorParameters, ithIndex);
var unboxedIthParameter = Expression.Convert(ithParameter, parameterInfo.ParameterType);
return unboxedIthParameter;
}).ToArray();
NewExpression constructorCall = Expression.New(constructorInfo, parameterExpressions);
Func<object?[], object> ctor = Expression.Lambda<Func<object?[], object>>(constructorCall, constructorParameters).Compile();
return ctor;
}
private static IEnumerable<EntityProperty> GetEntityProperties<T>(ParameterInfo parameterInfo) {
var name = parameterInfo.Name.EnsureNotNull($"Invalid paramter {parameterInfo}");
var parameterType = parameterInfo.ParameterType.EnsureNotNull($"Invalid paramter {parameterInfo}");
var isRowkey = parameterInfo.GetCustomAttribute(typeof(RowKeyAttribute)) != null;
var isPartitionkey = parameterInfo.GetCustomAttribute(typeof(PartitionKeyAttribute)) != null;
var discriminatorAttribute = typeof(T).GetProperty(name)?.GetCustomAttribute<TypeDiscrimnatorAttribute>();
var defaultValueAttribute = parameterInfo.GetCustomAttribute<DefaultValueAttribute>();
(TypeDiscrimnatorAttribute, ITypeProvider)? discriminator = null;
if (discriminatorAttribute != null) {
var t = (ITypeProvider)(Activator.CreateInstance(discriminatorAttribute.ConverterType) ?? throw new Exception("unable to retrive the type provider"));
discriminator = (discriminatorAttribute, t);
}
if (isPartitionkey) {
yield return new EntityProperty(name, "PartitionKey", parameterType, EntityPropertyKind.PartitionKey, discriminator, defaultValueAttribute, parameterInfo);
}
if (isRowkey) {
yield return new EntityProperty(name, "RowKey", parameterType, EntityPropertyKind.RowKey, discriminator, defaultValueAttribute, parameterInfo);
}
if (!isPartitionkey && !isRowkey) {
var columnName = typeof(T).GetProperty(name)?.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? CaseConverter.PascalToSnake(name);
yield return new EntityProperty(name, columnName, parameterType, EntityPropertyKind.Column, discriminator, defaultValueAttribute, parameterInfo);
}
}
private EntityInfo GetEntityInfo<T>() {
return _cache.GetOrAdd(typeof(T), type => {
var constructor = type.GetConstructors()[0];
var parameterInfos = constructor.GetParameters();
var parameters =
parameterInfos.SelectMany(GetEntityProperties<T>).ToArray();
return new EntityInfo(typeof(T), parameters.ToLookup(x => x.name), BuildConstructerFrom(constructor));
});
}
public string ToJsonString<T>(T typedEntity) => JsonSerializer.Serialize(typedEntity, _options);
public T? FromJsonString<T>(string value) => JsonSerializer.Deserialize<T>(value, _options);
public TableEntity ToTableEntity<T>(T typedEntity) where T : EntityBase {
if (typedEntity == null) {
throw new ArgumentNullException(nameof(typedEntity));
}
var type = typeof(T);
var entityInfo = GetEntityInfo<T>();
Dictionary<string, object?> columnValues = entityInfo.properties.SelectMany(x => x).Select(prop => {
var value = entityInfo.type.GetProperty(prop.name)?.GetValue(typedEntity);
if (value == null) {
return (prop.columnName, value: (object?)null);
}
if (prop.kind == EntityPropertyKind.PartitionKey || prop.kind == EntityPropertyKind.RowKey) {
return (prop.columnName, value?.ToString());
}
if (prop.type == typeof(Guid) || prop.type == typeof(Guid?) || prop.type == typeof(Uri)) {
return (prop.columnName, value?.ToString());
}
if (prop.type == typeof(bool)
|| prop.type == typeof(bool?)
|| prop.type == typeof(string)
|| prop.type == typeof(DateTime)
|| prop.type == typeof(DateTime?)
|| prop.type == typeof(DateTimeOffset)
|| prop.type == typeof(DateTimeOffset?)
|| prop.type == typeof(int)
|| prop.type == typeof(int?)
|| prop.type == typeof(long)
|| prop.type == typeof(long?)
|| prop.type == typeof(double)
|| prop.type == typeof(double?)
) {
return (prop.columnName, value);
}
var serialized = JsonSerializer.Serialize(value, _options);
return (prop.columnName, serialized.Trim('"'));
}).ToDictionary(x => x.columnName, x => x.value);
var tableEntity = new TableEntity(columnValues);
if (typedEntity.ETag.HasValue) {
tableEntity.ETag = typedEntity.ETag.Value;
}
return tableEntity;
}
private object? GetFieldValue(EntityInfo info, string name, TableEntity entity) {
var ef = info.properties[name].First();
if (ef.kind == EntityPropertyKind.PartitionKey || ef.kind == EntityPropertyKind.RowKey) {
if (ef.type == typeof(string))
return entity.GetString(ef.kind.ToString());
else if (ef.type == typeof(Guid))
return Guid.Parse(entity.GetString(ef.kind.ToString()));
else if (ef.type == typeof(int))
return int.Parse(entity.GetString(ef.kind.ToString()));
else if (ef.type == typeof(long))
return long.Parse(entity.GetString(ef.kind.ToString()));
else if (ef.type.IsClass)
return ef.type.GetConstructor(new[] { typeof(string) })!.Invoke(new[] { entity.GetString(ef.kind.ToString()) });
else {
throw new Exception($"invalid partition or row key type of {info.type} property {name}: {ef.type}");
}
}
var fieldName = ef.columnName;
var obj = entity[fieldName];
if (obj == null) {
if (ef.parameterInfo.HasDefaultValue) {
return ef.parameterInfo.DefaultValue;
}
return ef.defaultValue switch {
DefaultValueAttribute { InitMethod: InitMethod.DefaultConstructor } => Activator.CreateInstance(ef.type),
_ => null,
};
}
try {
if (ef.type == typeof(string)) {
return entity.GetString(fieldName);
} else if (ef.type == typeof(bool) || ef.type == typeof(bool?)) {
return entity.GetBoolean(fieldName);
} else if (ef.type == typeof(DateTimeOffset) || ef.type == typeof(DateTimeOffset?)) {
return entity.GetDateTimeOffset(fieldName);
} else if (ef.type == typeof(DateTime) || ef.type == typeof(DateTime?)) {
return entity.GetDateTime(fieldName);
} else if (ef.type == typeof(double) || ef.type == typeof(double?)) {
return entity.GetDouble(fieldName);
} else if (ef.type == typeof(Guid) || ef.type == typeof(Guid?)) {
return (object?)Guid.Parse(entity.GetString(fieldName));
} else if (ef.type == typeof(int) || ef.type == typeof(short) || ef.type == typeof(int?) || ef.type == typeof(short?)) {
return entity.GetInt32(fieldName);
} else if (ef.type == typeof(long) || ef.type == typeof(long?)) {
return entity.GetInt64(fieldName);
} else {
var outputType = ef.type;
if (ef.discriminator != null) {
var (attr, typeProvider) = ef.discriminator.Value;
var v = GetFieldValue(info, attr.FieldName, entity) ?? throw new Exception($"No value for {attr.FieldName}");
outputType = typeProvider.GetTypeInfo(v);
}
var objType = obj.GetType();
if (objType == typeof(string)) {
var value = entity.GetString(fieldName);
if (value.StartsWith('[') || value.StartsWith('{') || value == "null") {
return JsonSerializer.Deserialize(value, outputType, options: _options);
} else {
return JsonSerializer.Deserialize($"\"{value}\"", outputType, options: _options);
}
} else {
var value = entity.GetString(fieldName);
return JsonSerializer.Deserialize(value, outputType, options: _options);
}
}
} catch (Exception ex) {
throw new InvalidOperationException($"Unable to get value for property '{name}' (entity field '{fieldName}')", ex);
}
}
public T ToRecord<T>(TableEntity entity) where T : EntityBase {
var entityInfo = GetEntityInfo<T>();
object?[] parameters;
try {
parameters = entityInfo.properties.Select(grouping => GetFieldValue(entityInfo, grouping.Key, entity)).ToArray();
} catch (Exception ex) {
throw new InvalidOperationException($"Unable to extract properties from TableEntity for {typeof(T)}", ex);
}
try {
var entityRecord = (T)entityInfo.constructor.Invoke(parameters);
if (entity.ETag != default) {
entityRecord.ETag = entity.ETag;
}
entityRecord.TimeStamp = entity.Timestamp;
return entityRecord;
} catch (Exception ex) {
var stringParam = string.Join(", ", parameters);
throw new InvalidOperationException($"Could not initialize object of type {typeof(T)} with the following parameters: {stringParam} constructor {entityInfo.constructor}", ex);
}
}
}
using System.Collections.Concurrent;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure;
using Azure.Data.Tables;
namespace Microsoft.OneFuzz.Service.OneFuzzLib.Orm;
public abstract record EntityBase {
[JsonIgnore] public ETag? ETag { get; set; }
public DateTimeOffset? TimeStamp { get; set; }
// https://docs.microsoft.com/en-us/rest/api/storageservices/designing-a-scalable-partitioning-strategy-for-azure-table-storage#yyy
// Produce "good-quality-table-key" based on a DateTimeOffset timestamp
public static string NewSortedKey => $"{DateTimeOffset.MaxValue.Ticks - DateTimeOffset.UtcNow.Ticks}";
}
public abstract record StatefulEntityBase<T>([property: JsonIgnore] T BaseState) : EntityBase() where T : Enum;
/// How the value is populated
public enum InitMethod {
//T() will be used
DefaultConstructor,
}
[AttributeUsage(AttributeTargets.Parameter)]
public class DefaultValueAttribute : Attribute {
public InitMethod InitMethod { get; }
public DefaultValueAttribute(InitMethod initMethod) {
InitMethod = initMethod;
}
}
/// Indicates that the enum cases should no be renamed
[AttributeUsage(AttributeTargets.Enum)]
public class SerializeValueAttribute : Attribute { }
/// Indicates that the enum cases should no be renamed
[AttributeUsage(AttributeTargets.Enum)]
public class SkipRenameAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter)]
public class RowKeyAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Parameter)]
public class PartitionKeyAttribute : Attribute { }
[AttributeUsage(AttributeTargets.Property)]
public class TypeDiscrimnatorAttribute : Attribute {
public string FieldName { get; }
// the type of a function that takes the value of fieldName as an input and return the type
public Type ConverterType { get; }
public TypeDiscrimnatorAttribute(string fieldName, Type converterType) {
if (!converterType.IsAssignableTo(typeof(ITypeProvider))) {
throw new ArgumentException($"the provided type needs to implement ITypeProvider");
}
FieldName = fieldName;
ConverterType = converterType;
}
}
public interface ITypeProvider {
Type GetTypeInfo(object input);
}
public enum EntityPropertyKind {
PartitionKey,
RowKey,
Column
}
public record EntityProperty(
string name,
string columnName,
Type type,
EntityPropertyKind kind,
(TypeDiscrimnatorAttribute, ITypeProvider)? discriminator,
DefaultValueAttribute? defaultValue,
ParameterInfo parameterInfo
);
public record EntityInfo(Type type, ILookup<string, EntityProperty> properties, Func<object?[], object> constructor);
class OnefuzzNamingPolicy : JsonNamingPolicy {
public override string ConvertName(string name) {
return CaseConverter.PascalToSnake(name);
}
}
public class EntityConverter {
private readonly JsonSerializerOptions _options;
private readonly ConcurrentDictionary<Type, EntityInfo> _cache;
public EntityConverter() {
_options = GetJsonSerializerOptions();
_cache = new ConcurrentDictionary<Type, EntityInfo>();
}
public static JsonSerializerOptions GetJsonSerializerOptions() {
var options = new JsonSerializerOptions() {
PropertyNamingPolicy = new OnefuzzNamingPolicy(),
};
options.Converters.Add(new CustomEnumConverterFactory());
options.Converters.Add(new PolymorphicConverterFactory());
return options;
}
internal static Func<object?[], object> BuildConstructerFrom(ConstructorInfo constructorInfo) {
var constructorParameters = Expression.Parameter(typeof(object?[]));
var parameterExpressions =
constructorInfo.GetParameters().Select((parameterInfo, i) => {
var ithIndex = Expression.Constant(i);
var ithParameter = Expression.ArrayIndex(constructorParameters, ithIndex);
var unboxedIthParameter = Expression.Convert(ithParameter, parameterInfo.ParameterType);
return unboxedIthParameter;
}).ToArray();
NewExpression constructorCall = Expression.New(constructorInfo, parameterExpressions);
Func<object?[], object> ctor = Expression.Lambda<Func<object?[], object>>(constructorCall, constructorParameters).Compile();
return ctor;
}
private static IEnumerable<EntityProperty> GetEntityProperties<T>(ParameterInfo parameterInfo) {
var name = parameterInfo.Name.EnsureNotNull($"Invalid paramter {parameterInfo}");
var parameterType = parameterInfo.ParameterType.EnsureNotNull($"Invalid paramter {parameterInfo}");
var isRowkey = parameterInfo.GetCustomAttribute(typeof(RowKeyAttribute)) != null;
var isPartitionkey = parameterInfo.GetCustomAttribute(typeof(PartitionKeyAttribute)) != null;
var discriminatorAttribute = typeof(T).GetProperty(name)?.GetCustomAttribute<TypeDiscrimnatorAttribute>();
var defaultValueAttribute = parameterInfo.GetCustomAttribute<DefaultValueAttribute>();
(TypeDiscrimnatorAttribute, ITypeProvider)? discriminator = null;
if (discriminatorAttribute != null) {
var t = (ITypeProvider)(Activator.CreateInstance(discriminatorAttribute.ConverterType) ?? throw new Exception("unable to retrive the type provider"));
discriminator = (discriminatorAttribute, t);
}
if (isPartitionkey) {
yield return new EntityProperty(name, "PartitionKey", parameterType, EntityPropertyKind.PartitionKey, discriminator, defaultValueAttribute, parameterInfo);
}
if (isRowkey) {
yield return new EntityProperty(name, "RowKey", parameterType, EntityPropertyKind.RowKey, discriminator, defaultValueAttribute, parameterInfo);
}
if (!isPartitionkey && !isRowkey) {
var columnName = typeof(T).GetProperty(name)?.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? CaseConverter.PascalToSnake(name);
yield return new EntityProperty(name, columnName, parameterType, EntityPropertyKind.Column, discriminator, defaultValueAttribute, parameterInfo);
}
}
private EntityInfo GetEntityInfo<T>() {
return _cache.GetOrAdd(typeof(T), type => {
var constructor = type.GetConstructors()[0];
var parameterInfos = constructor.GetParameters();
var parameters =
parameterInfos.SelectMany(GetEntityProperties<T>).ToArray();
return new EntityInfo(typeof(T), parameters.ToLookup(x => x.name), BuildConstructerFrom(constructor));
});
}
public string ToJsonString<T>(T typedEntity) => JsonSerializer.Serialize(typedEntity, _options);
public T? FromJsonString<T>(string value) => JsonSerializer.Deserialize<T>(value, _options);
public TableEntity ToTableEntity<T>(T typedEntity) where T : EntityBase {
if (typedEntity == null) {
throw new ArgumentNullException(nameof(typedEntity));
}
var type = typeof(T);
var entityInfo = GetEntityInfo<T>();
Dictionary<string, object?> columnValues = entityInfo.properties.SelectMany(x => x).Select(prop => {
var value = entityInfo.type.GetProperty(prop.name)?.GetValue(typedEntity);
if (value == null) {
return (prop.columnName, value: (object?)null);
}
if (prop.kind == EntityPropertyKind.PartitionKey || prop.kind == EntityPropertyKind.RowKey) {
return (prop.columnName, value?.ToString());
}
if (prop.type == typeof(Guid) || prop.type == typeof(Guid?) || prop.type == typeof(Uri)) {
return (prop.columnName, value?.ToString());
}
if (prop.type == typeof(bool)
|| prop.type == typeof(bool?)
|| prop.type == typeof(string)
|| prop.type == typeof(DateTime)
|| prop.type == typeof(DateTime?)
|| prop.type == typeof(DateTimeOffset)
|| prop.type == typeof(DateTimeOffset?)
|| prop.type == typeof(int)
|| prop.type == typeof(int?)
|| prop.type == typeof(long)
|| prop.type == typeof(long?)
|| prop.type == typeof(double)
|| prop.type == typeof(double?)
) {
return (prop.columnName, value);
}
var serialized = JsonSerializer.Serialize(value, _options);
return (prop.columnName, serialized.Trim('"'));
}).ToDictionary(x => x.columnName, x => x.value);
var tableEntity = new TableEntity(columnValues);
if (typedEntity.ETag.HasValue) {
tableEntity.ETag = typedEntity.ETag.Value;
}
return tableEntity;
}
private object? GetFieldValue(EntityInfo info, string name, TableEntity entity) {
var ef = info.properties[name].First();
if (ef.kind == EntityPropertyKind.PartitionKey || ef.kind == EntityPropertyKind.RowKey) {
if (ef.type == typeof(string))
return entity.GetString(ef.kind.ToString());
else if (ef.type == typeof(Guid))
return Guid.Parse(entity.GetString(ef.kind.ToString()));
else if (ef.type == typeof(int))
return int.Parse(entity.GetString(ef.kind.ToString()));
else if (ef.type == typeof(long))
return long.Parse(entity.GetString(ef.kind.ToString()));
else if (ef.type.IsClass)
return ef.type.GetConstructor(new[] { typeof(string) })!.Invoke(new[] { entity.GetString(ef.kind.ToString()) });
else {
throw new Exception($"invalid partition or row key type of {info.type} property {name}: {ef.type}");
}
}
var fieldName = ef.columnName;
var obj = entity[fieldName];
if (obj == null) {
if (ef.parameterInfo.HasDefaultValue) {
return ef.parameterInfo.DefaultValue;
}
return ef.defaultValue switch {
DefaultValueAttribute { InitMethod: InitMethod.DefaultConstructor } => Activator.CreateInstance(ef.type),
_ => null,
};
}
try {
if (ef.type == typeof(string)) {
return entity.GetString(fieldName);
} else if (ef.type == typeof(bool) || ef.type == typeof(bool?)) {
return entity.GetBoolean(fieldName);
} else if (ef.type == typeof(DateTimeOffset) || ef.type == typeof(DateTimeOffset?)) {
return entity.GetDateTimeOffset(fieldName);
} else if (ef.type == typeof(DateTime) || ef.type == typeof(DateTime?)) {
return entity.GetDateTime(fieldName);
} else if (ef.type == typeof(double) || ef.type == typeof(double?)) {
return entity.GetDouble(fieldName);
} else if (ef.type == typeof(Guid) || ef.type == typeof(Guid?)) {
return (object?)Guid.Parse(entity.GetString(fieldName));
} else if (ef.type == typeof(int) || ef.type == typeof(short) || ef.type == typeof(int?) || ef.type == typeof(short?)) {
return entity.GetInt32(fieldName);
} else if (ef.type == typeof(long) || ef.type == typeof(long?)) {
return entity.GetInt64(fieldName);
} else {
var outputType = ef.type;
if (ef.discriminator != null) {
var (attr, typeProvider) = ef.discriminator.Value;
var v = GetFieldValue(info, attr.FieldName, entity) ?? throw new Exception($"No value for {attr.FieldName}");
outputType = typeProvider.GetTypeInfo(v);
}
var objType = obj.GetType();
if (objType == typeof(string)) {
var value = entity.GetString(fieldName);
if (value.StartsWith('[') || value.StartsWith('{') || value == "null") {
return JsonSerializer.Deserialize(value, outputType, options: _options);
} else {
return JsonSerializer.Deserialize($"\"{value}\"", outputType, options: _options);
}
} else {
var value = entity.GetString(fieldName);
return JsonSerializer.Deserialize(value, outputType, options: _options);
}
}
} catch (Exception ex) {
throw new InvalidOperationException($"Unable to get value for property '{name}' (entity field '{fieldName}')", ex);
}
}
public T ToRecord<T>(TableEntity entity) where T : EntityBase {
var entityInfo = GetEntityInfo<T>();
object?[] parameters;
try {
parameters = entityInfo.properties.Select(grouping => GetFieldValue(entityInfo, grouping.Key, entity)).ToArray();
} catch (Exception ex) {
throw new InvalidOperationException($"Unable to extract properties from TableEntity for {typeof(T)}", ex);
}
try {
var entityRecord = (T)entityInfo.constructor.Invoke(parameters);
if (entity.ETag != default) {
entityRecord.ETag = entity.ETag;
}
entityRecord.TimeStamp = entity.Timestamp;
return entityRecord;
} catch (Exception ex) {
var stringParam = string.Join(", ", parameters);
throw new InvalidOperationException($"Could not initialize object of type {typeof(T)} with the following parameters: {stringParam} constructor {entityInfo.constructor}", ex);
}
}
}

View File

@ -1,42 +1,42 @@
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<coverage line-rate=\"0.40\" branch-rate=\"0\" lines-covered=\"2\" lines-valid=\"5\" branches-covered=\"0\" branches-valid=\"0\" complexity=\"0\" version=\"0.1\" timestamp=\"1648844445\">
<packages>
<package name=\"C:/Users\" line-rate=\"0.50\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file1.txt\" filename=\"C:/Users/file1.txt\" line-rate=\"0.50\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"5\" hits=\"3\" branch=\"false\" />
<line number=\"10\" hits=\"0\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
<package name=\"C:/Users\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file2.txt\" filename=\"C:/Users/file2.txt\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"1\" hits=\"0\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
<package name=\"Invalid file format: C:/Users/file/..\" line-rate=\"1.00\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file/..\" filename=\"C:/Users/file/..\" line-rate=\"1.00\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"1\" hits=\"1\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
<package name=\"Invalid file format: C:/Users/file/..\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file/..\" filename=\"C:/Users/file/..\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"1\" hits=\"0\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
</packages>
<?xml version=\"1.0\" encoding=\"utf-8\"?>
<coverage line-rate=\"0.40\" branch-rate=\"0\" lines-covered=\"2\" lines-valid=\"5\" branches-covered=\"0\" branches-valid=\"0\" complexity=\"0\" version=\"0.1\" timestamp=\"1648844445\">
<packages>
<package name=\"C:/Users\" line-rate=\"0.50\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file1.txt\" filename=\"C:/Users/file1.txt\" line-rate=\"0.50\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"5\" hits=\"3\" branch=\"false\" />
<line number=\"10\" hits=\"0\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
<package name=\"C:/Users\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file2.txt\" filename=\"C:/Users/file2.txt\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"1\" hits=\"0\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
<package name=\"Invalid file format: C:/Users/file/..\" line-rate=\"1.00\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file/..\" filename=\"C:/Users/file/..\" line-rate=\"1.00\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"1\" hits=\"1\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
<package name=\"Invalid file format: C:/Users/file/..\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<classes>
<class name=\"C:/Users/file/..\" filename=\"C:/Users/file/..\" line-rate=\"0.00\" branch-rate=\"0\" complexity=\"0\">
<lines>
<line number=\"1\" hits=\"0\" branch=\"false\" />
</lines>
</class>
</classes>
</package>
</packages>
</coverage>

View File

@ -1,34 +1,34 @@
# srcview
A library for mapping module+offset to source:line. Note that you'll get
significantly better results if you use instruction level coverage as
opposed to branch. Alternatively you're welcome to post-process branch
coverage into instruction, but this project does not assist with that.
## Docs
`cargo doc --open`
## Usage
See [`examples/dump_cobertura.rs`](examples/dump_cobertura.rs).
This can be run with `cargo run --example dump_cobertura res\example.pdb res\example.txt`.
The biggest challenge when making this work is likely getting the absolute PDB
paths to match relative paths inside your repo. To make this a bit easier, you
can dump the PDB paths with: `cargo run --example dump_paths res\example.pdb`
## ADO Integration
See [`azure-pipelines.yml`](azure-pipelines.yml).
## VSCode Integration
Install [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters),
then place a file named `coverage.xml` at a location such that the relative
file paths match your repo. Note that this is a file you should generate
yourself, and that the `coverage.xml` included in this repo will probably not
help you. From there, navigate to any file you expect to see coverage in. If
the paths and coverage file correctly line up, you should see red or green bars
# srcview
A library for mapping module+offset to source:line. Note that you'll get
significantly better results if you use instruction level coverage as
opposed to branch. Alternatively you're welcome to post-process branch
coverage into instruction, but this project does not assist with that.
## Docs
`cargo doc --open`
## Usage
See [`examples/dump_cobertura.rs`](examples/dump_cobertura.rs).
This can be run with `cargo run --example dump_cobertura res\example.pdb res\example.txt`.
The biggest challenge when making this work is likely getting the absolute PDB
paths to match relative paths inside your repo. To make this a bit easier, you
can dump the PDB paths with: `cargo run --example dump_paths res\example.pdb`
## ADO Integration
See [`azure-pipelines.yml`](azure-pipelines.yml).
## VSCode Integration
Install [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=ryanluker.vscode-coverage-gutters),
then place a file named `coverage.xml` at a location such that the relative
file paths match your repo. Note that this is a file you should generate
yourself, and that the `coverage.xml` included in this repo will probably not
help you. From there, navigate to any file you expect to see coverage in. If
the paths and coverage file correctly line up, you should see red or green bars
next to the source lines.

View File

@ -1,6 +1,6 @@
# TODO
- add trace/info/warn
- flesh out modoff parser error
- consider using Cow to reduce memory usage
- consider mixed case module names?
# TODO
- add trace/info/warn
- flesh out modoff parser error
- consider using Cow to reduce memory usage
- consider mixed case module names?
- redo xml generation to not be hand-rolled

View File

@ -1,4 +1,4 @@
.direnv
.python_packages
__pycache__
.direnv
.python_packages
__pycache__
.venv

View File

@ -1,74 +1,74 @@
#!/usr/bin/env python
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import datetime
import logging
import azure.functions as func
from onefuzztypes.enums import JobState, TaskState
from ..onefuzzlib.jobs import Job
from ..onefuzzlib.notifications.main import Notification
from ..onefuzzlib.repro import Repro
from ..onefuzzlib.tasks.main import Task
RETENTION_POLICY = datetime.timedelta(days=(18 * 30))
SEARCH_EXTENT = datetime.timedelta(days=(20 * 30))
def main(mytimer: func.TimerRequest) -> None: # noqa: F841
now = datetime.datetime.now(tz=datetime.timezone.utc)
time_retained_older = now - RETENTION_POLICY
time_retained_newer = now - SEARCH_EXTENT
time_filter = (
f"Timestamp lt datetime'{time_retained_older.isoformat()}' "
f"and Timestamp gt datetime'{time_retained_newer.isoformat()}'"
)
time_filter_newer = f"Timestamp gt datetime'{time_retained_older.isoformat()}'"
# Collecting 'still relevant' task containers.
# NOTE: This must be done before potentially modifying tasks otherwise
# the task timestamps will not be useful.
used_containers = set()
for task in Task.search(raw_unchecked_filter=time_filter_newer):
task_containers = {x.name for x in task.config.containers}
used_containers.update(task_containers)
for notification in Notification.search(raw_unchecked_filter=time_filter):
logging.debug(
"checking expired notification for removal: %s",
notification.notification_id,
)
container = notification.container
if container not in used_containers:
logging.info(
"deleting expired notification: %s", notification.notification_id
)
notification.delete()
for job in Job.search(
query={"state": [JobState.stopped]}, raw_unchecked_filter=time_filter
):
if job.user_info is not None and job.user_info.upn is not None:
logging.info("removing PII from job: %s", job.job_id)
job.user_info.upn = None
job.save()
for task in Task.search(
query={"state": [TaskState.stopped]}, raw_unchecked_filter=time_filter
):
if task.user_info is not None and task.user_info.upn is not None:
logging.info("removing PII from task: %s", task.task_id)
task.user_info.upn = None
task.save()
for repro in Repro.search(raw_unchecked_filter=time_filter):
if repro.user_info is not None and repro.user_info.upn is not None:
logging.info("removing PII from repro: %s", repro.vm_id)
repro.user_info.upn = None
repro.save()
#!/usr/bin/env python
#
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT License.
import datetime
import logging
import azure.functions as func
from onefuzztypes.enums import JobState, TaskState
from ..onefuzzlib.jobs import Job
from ..onefuzzlib.notifications.main import Notification
from ..onefuzzlib.repro import Repro
from ..onefuzzlib.tasks.main import Task
RETENTION_POLICY = datetime.timedelta(days=(18 * 30))
SEARCH_EXTENT = datetime.timedelta(days=(20 * 30))
def main(mytimer: func.TimerRequest) -> None: # noqa: F841
now = datetime.datetime.now(tz=datetime.timezone.utc)
time_retained_older = now - RETENTION_POLICY
time_retained_newer = now - SEARCH_EXTENT
time_filter = (
f"Timestamp lt datetime'{time_retained_older.isoformat()}' "
f"and Timestamp gt datetime'{time_retained_newer.isoformat()}'"
)
time_filter_newer = f"Timestamp gt datetime'{time_retained_older.isoformat()}'"
# Collecting 'still relevant' task containers.
# NOTE: This must be done before potentially modifying tasks otherwise
# the task timestamps will not be useful.
used_containers = set()
for task in Task.search(raw_unchecked_filter=time_filter_newer):
task_containers = {x.name for x in task.config.containers}
used_containers.update(task_containers)
for notification in Notification.search(raw_unchecked_filter=time_filter):
logging.debug(
"checking expired notification for removal: %s",
notification.notification_id,
)
container = notification.container
if container not in used_containers:
logging.info(
"deleting expired notification: %s", notification.notification_id
)
notification.delete()
for job in Job.search(
query={"state": [JobState.stopped]}, raw_unchecked_filter=time_filter
):
if job.user_info is not None and job.user_info.upn is not None:
logging.info("removing PII from job: %s", job.job_id)
job.user_info.upn = None
job.save()
for task in Task.search(
query={"state": [TaskState.stopped]}, raw_unchecked_filter=time_filter
):
if task.user_info is not None and task.user_info.upn is not None:
logging.info("removing PII from task: %s", task.task_id)
task.user_info.upn = None
task.save()
for repro in Repro.search(raw_unchecked_filter=time_filter):
if repro.user_info is not None and repro.user_info.upn is not None:
logging.info("removing PII from repro: %s", repro.vm_id)
repro.user_info.upn = None
repro.save()

View File

@ -1,73 +1,73 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# mypy
.mypy_cache/
test-cov.xml
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
.pytest_cache/
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# pyenv
.python-version
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# mypy
.mypy_cache/
test-cov.xml
test-output.xml

View File

@ -1,6 +1,6 @@
{
"proxy_nsg_config": {
"allowed_ips": ["*"],
"allowed_service_tags": []
}
{
"proxy_nsg_config": {
"allowed_ips": ["*"],
"allowed_service_tags": []
}
}

View File

@ -1,15 +1,15 @@
using System;
namespace Problems {
public static class Problems {
public static void Func(ReadOnlySpan<byte> data) {
var count = 0;
if (data.Length < 4) {
return;
}
if (data[0] == 0) { count++; }
if (data[1] == 1) { count++; }
if (data[2] == 2) { count++; }
if (count >= 3) { throw new Exception("this is bad"); }
}
}
}
using System;
namespace Problems {
public static class Problems {
public static void Func(ReadOnlySpan<byte> data) {
var count = 0;
if (data.Length < 4) {
return;
}
if (data[0] == 0) { count++; }
if (data[1] == 1) { count++; }
if (data[2] == 2) { count++; }
if (count >= 3) { throw new Exception("this is bad"); }
}
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -1,8 +1,8 @@
using SharpFuzz;
namespace Wrapper {
public class Program {
public static void Main(string[] args) {
Fuzzer.LibFuzzer.Run(stream => { Problems.Problems.Func(stream); });
}
}
}
using SharpFuzz;
namespace Wrapper {
public class Program {
public static void Main(string[] args) {
Fuzzer.LibFuzzer.Run(stream => { Problems.Problems.Func(stream); });
}
}
}

View File

@ -1,15 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\problems\problems.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpFuzz" Version="1.6.1" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\problems\problems.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="SharpFuzz" Version="1.6.1" />
</ItemGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
</Project>

View File

@ -1,85 +1,85 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN64
#include <io.h>
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#define STDIN_FILENO _fileno(stdin)
#define read _read
#else
#include <unistd.h>
#endif
#define SIZE 8192
#define BUF_SIZE 32
int check(const char *data, size_t len)
{
char buf[BUF_SIZE];
memset(buf, 0, BUF_SIZE);
strncpy(buf, data, len); // BUG - This incorrectly uses length of src, not dst
// do something to ensure this isn't optimized away
int buflen = strlen(buf);
for (int i = 0; i <= ((buflen % 2 == 1) ? buflen - 1 : buflen) / 2; i++)
{
if (buf[i] != buf[buflen - 1 - i])
{
printf("not palindrome: ");
printf(buf);
printf("\n");
break;
}
}
return 0;
}
int from_stdin()
{
char input[SIZE] = {0};
int size = read(STDIN_FILENO, input, SIZE);
return check(input, size);
}
int from_file(char *filename)
{
FILE *infile;
char *buffer;
long length;
int result;
infile = fopen(filename, "r");
if (infile == NULL)
return 1;
fseek(infile, 0L, SEEK_END);
length = ftell(infile);
fseek(infile, 0L, SEEK_SET);
buffer = calloc(length, sizeof(char));
if (buffer == NULL)
return 1;
length = fread(buffer, sizeof(char), length, infile);
fclose(infile);
result = check(buffer, length);
free(buffer);
return result;
}
int main(int argc, char **argv)
{
if (argc == 1)
{
return from_stdin();
}
else if (argc > 1)
{
return from_file(argv[1]);
}
}
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#ifdef _WIN64
#include <io.h>
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#define STDIN_FILENO _fileno(stdin)
#define read _read
#else
#include <unistd.h>
#endif
#define SIZE 8192
#define BUF_SIZE 32
int check(const char *data, size_t len)
{
char buf[BUF_SIZE];
memset(buf, 0, BUF_SIZE);
strncpy(buf, data, len); // BUG - This incorrectly uses length of src, not dst
// do something to ensure this isn't optimized away
int buflen = strlen(buf);
for (int i = 0; i <= ((buflen % 2 == 1) ? buflen - 1 : buflen) / 2; i++)
{
if (buf[i] != buf[buflen - 1 - i])
{
printf("not palindrome: ");
printf(buf);
printf("\n");
break;
}
}
return 0;
}
int from_stdin()
{
char input[SIZE] = {0};
int size = read(STDIN_FILENO, input, SIZE);
return check(input, size);
}
int from_file(char *filename)
{
FILE *infile;
char *buffer;
long length;
int result;
infile = fopen(filename, "r");
if (infile == NULL)
return 1;
fseek(infile, 0L, SEEK_END);
length = ftell(infile);
fseek(infile, 0L, SEEK_SET);
buffer = calloc(length, sizeof(char));
if (buffer == NULL)
return 1;
length = fread(buffer, sizeof(char), length, infile);
fclose(infile);
result = check(buffer, length);
free(buffer);
return result;
}
int main(int argc, char **argv)
{
if (argc == 1)
{
return from_stdin();
}
else if (argc > 1)
{
return from_file(argv[1]);
}
}