public void RequestMeshAssetOnCap(Guid capId, Guid assetId, AssetRequest.AssetRequestHandler handler, AssetRequest.AssetErrorHandler errHandler) { handler = handler ?? throw new ArgumentNullException(nameof(handler)); errHandler = errHandler ?? throw new ArgumentNullException(nameof(errHandler)); if (_negativeCache != null) { _negativeCacheLock.EnterReadLock(); var negCacheContainsId = false; try { negCacheContainsId = _negativeCache.Contains(assetId.ToString("N")); } finally { _negativeCacheLock.ExitReadLock(); } if (negCacheContainsId) { errHandler(new AssetError { Error = AssetErrorType.AssetTypeWrong, }); return; } } if (_caps.TryGetValue(capId, out Capability cap)) { cap.RequestAsset( assetId, (asset) => { if (!ConfigSingleton.ValidTypes.Any(type => type == asset.Type)) { if (_negativeCache != null) { _negativeCacheLock.EnterWriteLock(); try { _negativeCache.Set(new System.Runtime.Caching.CacheItem(assetId.ToString("N"), 0), _negativeCachePolicy); } finally { _negativeCacheLock.ExitWriteLock(); } } errHandler(new AssetError { Error = AssetErrorType.AssetTypeWrong, }); } if (asset.Type == 49) { handler(asset); } else { errHandler(new AssetError { Error = AssetErrorType.AssetTypeWrong, }); } }, errHandler ); return; } errHandler(new AssetError { Error = AssetErrorType.CapabilityIdUnknown, }); }
private void HandleRequest(Guid assetId, AssetRequest.AssetRequestHandler handler, AssetRequest.AssetErrorHandler errHandler) { var reader = ConfigSingleton.ChattelReader; if (reader == null) { // There's no Chattel. Fail. errHandler(new AssetError { Error = AssetErrorType.ConfigIncorrect, Message = "Chattel was null!", }); return; } reader.GetAssetAsync(assetId, asset => { if (asset == null) { errHandler(new AssetError { Error = AssetErrorType.AssetIdUnknown, Message = $"Could not find any asset with ID {assetId}", }); return; } if (!ConfigSingleton.ValidTypes.Contains(asset.Type)) { errHandler(new AssetError { Error = AssetErrorType.AssetTypeWrong, }); return; } handler(asset); }); }
public void RequestAsset(Guid assetId, AssetRequest.AssetRequestHandler handler, AssetRequest.AssetErrorHandler errHandler) { if (!IsActive) // Paused { AssetRequest requestToProcess = null; var gotLock = false; try { // Skiplock: Only locking because of possible shutdown action. Monitor.TryEnter(_requests, ref gotLock); if (gotLock) { _requests.Enqueue(new AssetRequest(assetId, handler, errHandler)); if (_requests.Count > MAX_QUEUED_REQUESTS) { _requests.TryDequeue(out requestToProcess); } } } finally { if (gotLock) { Monitor.Exit(_requests); } } var errors = new Queue <Exception>(); // Have to make sure both operations below happen, even if they throw. if (!gotLock) { // It seems that this cap cannot queue the request. Probably shutting down. Tell the client no such luck. try { errHandler(new AssetError { Error = AssetErrorType.QueueFilled, }); } catch (Exception e) { errors.Enqueue(e); } } try { requestToProcess?.Respond(new AssetError { Error = AssetErrorType.QueueFilled, }); } catch (Exception e) { errors.Enqueue(e); } if (errors.Count > 0) { throw new AggregateException(errors); } return; } // Not paused, query Chattel. HandleRequest(assetId, handler, errHandler); }
public F_StopRouter() : base("/CAPS/HTT") { Get["/TEST"] = _ => { LOG.Debug($"Test called by {Request.UserHostAddress}"); return(Response.AsText("OK")); }; Get["/ADDCAP/{adminToken}/{capId:guid}/{bandwidth?}"] = _ => { if (_.bandwidth != null && (int)_.bandwidth < 0) { LOG.Warn($"Invalid bandwidth spec from {Request.UserHostAddress} on cap {_.capId}: bandwidth cannot be negative ({_.bandwidth})"); return(StockReply.BadRequest); } uint bandwidth = 0; if (_.bandwidth != null) { bandwidth = _.bandwidth; } try { var result = _capAdmin.AddCap(_.adminToken, _.capId, bandwidth); return(result ? StockReply.Ok : StockReply.BadRequest); } catch (InvalidAdminTokenException) { LOG.Warn($"Invalid admin token from {Request.UserHostAddress}"); } return(StockReply.BadRequest); }; Get["/REMCAP/{adminToken}/{capId:guid}"] = _ => { try { var result = _capAdmin.RemoveCap(_.adminToken, _.capId); return(result ? StockReply.Ok : StockReply.BadRequest); } catch (InvalidAdminTokenException) { LOG.Warn($"Invalid admin token from {Request.UserHostAddress}"); } return(StockReply.BadRequest); }; Get["/PAUSE/{adminToken}/{capId:guid}"] = _ => { try { var result = _capAdmin.PauseCap(_.adminToken, _.capId); return(result ? StockReply.Ok : StockReply.BadRequest); } catch (InvalidAdminTokenException) { LOG.Warn($"Invalid admin token from {Request.UserHostAddress}"); } return(StockReply.BadRequest); }; Get["/RESUME/{adminToken}/{capId:guid}"] = _ => { try { var result = _capAdmin.ResumeCap(_.adminToken, _.capId); return(result ? StockReply.Ok : StockReply.BadRequest); } catch (InvalidAdminTokenException) { LOG.Warn($"Invalid admin token from {Request.UserHostAddress}"); } return(StockReply.BadRequest); }; Get["/LIMIT/{adminToken}/{capId:guid}/{bandwidth?}"] = _ => { if (_.bandwidth != null && (int)_.bandwidth < 0) { LOG.Warn($"Invalid bandwidth spec from {Request.UserHostAddress} on cap {_.capId}: bandwidth cannot be negative ({_.bandwidth})"); return(StockReply.BadRequest); } uint bandwidth = 0; if (_.bandwidth != null) { bandwidth = _.bandwidth; } try { var result = _capAdmin.LimitCap(_.adminToken, _.capId, bandwidth); return(result ? StockReply.Ok : StockReply.BadRequest); } catch (InvalidAdminTokenException) { LOG.Warn($"Invalid admin token from {Request.UserHostAddress}"); } return(StockReply.BadRequest); }; Get["/{capId:guid}", true] = async(_, ct) => { var textureId = (Guid?)Request.Query["texture_id"]; var meshId = (Guid?)Request.Query["mesh_id"]; if (textureId == null && meshId == null) { LOG.Warn($"Bad request for asset from {Request.UserHostAddress}: mesh_id nor texture_id supplied"); return(StockReply.BadRequest); } if (textureId != null && meshId != null) { // Difference from Aperture: Aperture continues and only uses the texture_id when both are specc'd. LOG.Warn($"Bad request for asset from {Request.UserHostAddress}: both mesh_id and texture_id supplied"); return(StockReply.BadRequest); } if (textureId == Guid.Empty || meshId == Guid.Empty) { var type = textureId != null ? "texture" : "mesh"; LOG.Warn($"Bad request for asset from {Request.UserHostAddress}: requested {type} is a zero guid."); return(StockReply.BadRequest); } var rangeHeaderVals = Request.Headers["Range"]; string rangeHeader = null; if (rangeHeaderVals.Any()) { rangeHeader = rangeHeaderVals.Aggregate((prev, next) => $"{prev},{next}"); // Because Nancy's being too smart. } IEnumerable <Range> ranges = null; // Parse and store the ranges, but only if a byte range. As per RFC7233: "An origin server MUST ignore a Range header field that contains a range unit it does not understand." if (rangeHeader?.StartsWith("bytes=", StringComparison.Ordinal) ?? false) { try { ranges = Range.ParseRanges(rangeHeader); } catch (FormatException) { LOG.Warn($"Bad range header for asset from {Request.UserHostAddress}. Requested header doesn't match RFC7233: {rangeHeader}"); return(StockReply.RangeError); } catch (ArgumentOutOfRangeException e) { LOG.Warn($"Bad range header for asset from {Request.UserHostAddress}: {rangeHeader}", e); return(StockReply.RangeError); } } if ((ranges?.Count() ?? 0) > 5) // 5 is arbitrary. In reality ranges should be few, lots of ranges usually mean an attack. { LOG.Warn($"Too many ranges requested from {Request.UserHostAddress}: {rangeHeader}"); return(StockReply.RangeError); } var completionSource = new System.Threading.Tasks.TaskCompletionSource <Response>(); AssetRequest.AssetErrorHandler errorHandler = error => { switch (error.Error) { case AssetErrorType.CapabilityIdUnknown: LOG.Warn($"Request on nonexistent cap from {Request.UserHostAddress} {error.Message}"); completionSource.SetResult(StockReply.NotFound); break; case AssetErrorType.AssetTypeWrong: LOG.Warn($"Request for wrong kind of asset from {Request.UserHostAddress} {error.Message}"); completionSource.SetResult(StockReply.BadRequest); break; case AssetErrorType.ConfigIncorrect: LOG.Warn($"Configuration incomplete! {error.Message}"); completionSource.SetResult(StockReply.InternalServerError); break; case AssetErrorType.AssetIdUnknown: LOG.Warn($"Request for unknown asset from {Request.UserHostAddress} {error.Message}"); completionSource.SetResult(StockReply.NotFound); break; case AssetErrorType.QueueFilled: LOG.Warn($"Request from {Request.UserHostAddress} had to be dropped because cap {_.capId} is paused and filled. {error.Message}"); completionSource.SetResult(StockReply.NotFound); break; default: LOG.Warn($"Request from {Request.UserHostAddress} had unexpected error! {error.Message}"); completionSource.SetResult(StockReply.InternalServerError); break; } }; try { if (textureId != null) { _capAdmin.RequestTextureAssetOnCap( (Guid)_.capId, (Guid)textureId, asset => { var response = new Response(); PrepareResponse(response, asset, ranges); completionSource.SetResult(response); }, errorHandler ); } else { _capAdmin.RequestMeshAssetOnCap( (Guid)_.capId, (Guid)meshId, asset => { var response = new Response(); PrepareResponse(response, asset, ranges); completionSource.SetResult(response); }, errorHandler ); } } catch (IndexOutOfRangeException e) { LOG.Warn($"Bad range requested from {Request.UserHostAddress} for asset {textureId ?? meshId}: {rangeHeader}", e); return(StockReply.RangeError); } return(await completionSource.Task); }; }