From 6da382215bef129932bc528f4212eca1e7b5f35b Mon Sep 17 00:00:00 2001 From: Aditya Keswani Date: Wed, 8 Apr 2026 15:11:05 -0700 Subject: [PATCH 1/7] Add demo --- BadgeReleaseDemo/Auth/AuthHelper.cs | 253 +++ BadgeReleaseDemo/BadgeReleaseDemo.csproj | 28 + BadgeReleaseDemo/BadgeReleaseDemo.sln | 24 + BadgeReleaseDemo/GraphApi/BadgeManagement.cs | 136 ++ .../GraphApi/PrintJobSubmission.cs | 156 ++ .../GraphApi/PrinterRegistration.cs | 162 ++ BadgeReleaseDemo/GraphApi/PrinterSharing.cs | 123 ++ BadgeReleaseDemo/Helpers/ConsoleHelper.cs | 92 + BadgeReleaseDemo/Helpers/CryptoHelper.cs | 82 + BadgeReleaseDemo/IppLibrary/Constants.cs | 40 + .../IppLibrary/DocumentAttributes.cs | 64 + BadgeReleaseDemo/IppLibrary/Helpers.cs | 861 ++++++++++ .../IppLibrary/IIppRequestFactory.cs | 284 ++++ BadgeReleaseDemo/IppLibrary/IPPAttribute.cs | 1514 +++++++++++++++++ .../IppLibrary/IPPAttributeGroup.cs | 187 ++ BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs | 1183 +++++++++++++ BadgeReleaseDemo/IppLibrary/IPPEncoding.cs | 198 +++ BadgeReleaseDemo/IppLibrary/IPPException.cs | 59 + .../IppLibrary/IPPFactoryHelper.cs | 40 + BadgeReleaseDemo/IppLibrary/IPPInputStream.cs | 183 ++ BadgeReleaseDemo/IppLibrary/IPPJob.cs | 227 +++ .../IppLibrary/IPPMemberAttribute.cs | 154 ++ BadgeReleaseDemo/IppLibrary/IPPRequest.cs | 352 ++++ .../IppLibrary/IPPRequestFactory.cs | 892 ++++++++++ BadgeReleaseDemo/IppLibrary/IPPResponse.cs | 396 +++++ BadgeReleaseDemo/IppLibrary/IPPTemplates.cs | 512 ++++++ BadgeReleaseDemo/IppLibrary/IPPValue.cs | 951 +++++++++++ BadgeReleaseDemo/IppLibrary/JobAttributes.cs | 214 +++ BadgeReleaseDemo/IppLibrary/JobEvents.cs | 17 + .../IppLibrary/NotifyAttributes.cs | 44 + .../IppLibrary/OperationAttributes.cs | 60 + .../IppLibrary/PrinterAttributes.cs | 217 +++ BadgeReleaseDemo/IppLibrary/PrinterEvents.cs | 18 + .../IppLibrary/RawIppEncodingBase.cs | 81 + .../IppLibrary/RequestedAttributes.cs | 16 + .../IppLibrary/RequiredAttributes.cs | 15 + .../IppLibrary/ZeroByteReadException.cs | 21 + .../IppOperations/PrinterIppClient.cs | 315 ++++ BadgeReleaseDemo/Program.cs | 386 +++++ BadgeReleaseDemo/README.md | 219 +++ BadgeReleaseDemo/Resources/SampleDocument.pdf | Bin 0 -> 688 bytes BadgeReleaseDemo/appsettings.json | 10 + 42 files changed, 10786 insertions(+) create mode 100644 BadgeReleaseDemo/Auth/AuthHelper.cs create mode 100644 BadgeReleaseDemo/BadgeReleaseDemo.csproj create mode 100644 BadgeReleaseDemo/BadgeReleaseDemo.sln create mode 100644 BadgeReleaseDemo/GraphApi/BadgeManagement.cs create mode 100644 BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs create mode 100644 BadgeReleaseDemo/GraphApi/PrinterRegistration.cs create mode 100644 BadgeReleaseDemo/GraphApi/PrinterSharing.cs create mode 100644 BadgeReleaseDemo/Helpers/ConsoleHelper.cs create mode 100644 BadgeReleaseDemo/Helpers/CryptoHelper.cs create mode 100644 BadgeReleaseDemo/IppLibrary/Constants.cs create mode 100644 BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/Helpers.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPAttribute.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPEncoding.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPException.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPInputStream.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPJob.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPRequest.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPResponse.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPTemplates.cs create mode 100644 BadgeReleaseDemo/IppLibrary/IPPValue.cs create mode 100644 BadgeReleaseDemo/IppLibrary/JobAttributes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/JobEvents.cs create mode 100644 BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/OperationAttributes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/PrinterEvents.cs create mode 100644 BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs create mode 100644 BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs create mode 100644 BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs create mode 100644 BadgeReleaseDemo/IppOperations/PrinterIppClient.cs create mode 100644 BadgeReleaseDemo/Program.cs create mode 100644 BadgeReleaseDemo/README.md create mode 100644 BadgeReleaseDemo/Resources/SampleDocument.pdf create mode 100644 BadgeReleaseDemo/appsettings.json diff --git a/BadgeReleaseDemo/Auth/AuthHelper.cs b/BadgeReleaseDemo/Auth/AuthHelper.cs new file mode 100644 index 0000000..0a0e722 --- /dev/null +++ b/BadgeReleaseDemo/Auth/AuthHelper.cs @@ -0,0 +1,253 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.IdentityModel.Tokens.Jwt; +using System.Security.Cryptography; +using System.Text.Json; +using BadgeReleaseDemo.GraphApi; +using BadgeReleaseDemo.Helpers; +using Microsoft.Identity.Client; +using Microsoft.IdentityModel.Tokens; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; + +namespace BadgeReleaseDemo.Auth; + +/// +/// Handles authentication for both user (Printer Admin) and printer device flows. +/// Printer certificate and keys are kept in memory only. +/// +public class AuthHelper +{ + private readonly string appId; + private readonly string tenantId; + private readonly string graphBaseUrl; + private IPublicClientApplication? publicClient; + private AuthenticationResult? userAuthResult; + private AuthenticationResult? graphAuthResult; + + // Printer identity (in memory only) + private PrinterRegistrationResult? registrationResult; + private AsymmetricCipherKeyPair? printerKeyPair; + private string? printerToken; + + public string UserUpn => userAuthResult?.Account?.Username ?? "unknown"; + + public string UserAccessToken => userAuthResult?.AccessToken ?? throw new InvalidOperationException("User not signed in"); + + public string PrinterToken => printerToken ?? throw new InvalidOperationException("Printer token not acquired"); + + public AuthHelper(string appId, string tenantId, string graphBaseUrl) + { + this.appId = appId; + this.tenantId = tenantId; + this.graphBaseUrl = graphBaseUrl; + } + + /// + /// Signs in the user interactively using device code flow. + /// + public async Task SignInUserAsync() + { + var builder = PublicClientApplicationBuilder + .Create(appId) + .WithRedirectUri("http://localhost"); + + if (!string.IsNullOrEmpty(tenantId)) + { + builder = builder.WithAuthority($"https://login.microsoftonline.com/{tenantId}"); + } + + publicClient = builder.Build(); + + var scopes = new[] { "https://print.print.microsoft.com/.default" }; + + userAuthResult = await publicClient.AcquireTokenInteractive(scopes) + .WithPrompt(Prompt.SelectAccount) + .ExecuteAsync(); + + return UserUpn; + } + + /// + /// Gets a fresh user access token, refreshing if needed. + /// + public async Task GetUserTokenAsync() + { + if (publicClient == null || userAuthResult == null) + { + throw new InvalidOperationException("User not signed in. Call SignInUserAsync first."); + } + + try + { + var accounts = await publicClient.GetAccountsAsync(); + var scopes = new[] { "https://print.print.microsoft.com/.default" }; + userAuthResult = await publicClient.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) + .ExecuteAsync(); + } + catch (MsalUiRequiredException) + { + var scopes = new[] { "https://print.print.microsoft.com/.default" }; + userAuthResult = await publicClient.AcquireTokenInteractive(scopes) + .ExecuteAsync(); + } + + return UserAccessToken; + } + + /// + /// Gets a Microsoft Graph token for Graph API calls (sharing, badges, jobs). + /// Uses silent acquisition if possible, otherwise prompts interactively. + /// + public async Task GetGraphTokenAsync() + { + if (publicClient == null) + { + throw new InvalidOperationException("User not signed in. Call SignInUserAsync first."); + } + + var scopes = new[] { "https://graph.microsoft.com/.default" }; + + try + { + var accounts = await publicClient.GetAccountsAsync(); + graphAuthResult = await publicClient.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) + .ExecuteAsync(); + } + catch (MsalUiRequiredException) + { + graphAuthResult = await publicClient.AcquireTokenInteractive(scopes) + .ExecuteAsync(); + } + + return graphAuthResult.AccessToken; + } + + /// + /// Stores the printer registration result and keypair for device token acquisition. + /// Certificate and keys are kept in memory only — not saved to disk. + /// + public void SetPrinterCredentials(PrinterRegistrationResult result, AsymmetricCipherKeyPair keyPair) + { + registrationResult = result; + printerKeyPair = keyPair; + } + + /// + /// Acquires a device token for the printer using the JWT-bearer flow: + /// 1. POST grant_type=srv_challenge to get a nonce + /// 2. Create a JWT signed with the printer's private key + /// 3. POST grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer with the JWT + /// + public async Task GetPrinterTokenAsync() + { + if (registrationResult == null || printerKeyPair == null) + { + throw new InvalidOperationException("Printer credentials not set. Register printer first."); + } + + using var httpClient = new HttpClient(); + var tokenUrl = registrationResult.DeviceTokenUrl; + + // Step 1: Request a nonce (srv_challenge) + var challengeBody = new FormUrlEncodedContent(new Dictionary + { + ["grant_type"] = "srv_challenge", + ["windows_api_version"] = "2.0", + }); + + var challengeResp = await httpClient.PostAsync(tokenUrl, challengeBody); + var challengeContent = await challengeResp.Content.ReadAsStringAsync(); + + if (!challengeResp.IsSuccessStatusCode) + { + throw new HttpRequestException($"Nonce request failed: {challengeResp.StatusCode} - {challengeContent}"); + } + + var challengeDoc = JsonSerializer.Deserialize(challengeContent); + var nonce = challengeDoc.GetProperty("Nonce").GetString() + ?? throw new InvalidOperationException("No Nonce in srv_challenge response."); + + // Step 2: Create JWT signed with printer's private key + var jwt = CreateDeviceJwt(nonce); + + // Step 3: Exchange JWT for access token + var tokenBody = new FormUrlEncodedContent(new Dictionary + { + ["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer", + ["request"] = jwt, + }); + + var tokenResp = await httpClient.PostAsync(tokenUrl, tokenBody); + var tokenContent = await tokenResp.Content.ReadAsStringAsync(); + + if (!tokenResp.IsSuccessStatusCode) + { + throw new HttpRequestException($"Device token request failed: {tokenResp.StatusCode} - {tokenContent}"); + } + + var tokenDoc = JsonSerializer.Deserialize(tokenContent); + printerToken = tokenDoc.GetProperty("access_token").GetString() + ?? throw new InvalidOperationException("No access_token in device token response."); + + return printerToken; + } + + /// + /// Refreshes the printer device token if needed. + /// + public async Task RefreshPrinterTokenAsync() + { + return await GetPrinterTokenAsync(); + } + + /// + /// Creates a JWT signed with the printer's private key for the device token flow. + /// + private string CreateDeviceJwt(string nonce) + { + var privateKeyParams = (RsaPrivateCrtKeyParameters)printerKeyPair!.Private; + var rsaParams = DotNetUtilities.ToRSAParameters(privateKeyParams); + var securityKey = new RsaSecurityKey(rsaParams); + var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256); + + // x5c must be the base64 DER of the public cert (no PEM headers) + var certBase64 = registrationResult!.CertificatePem + .Replace("-----BEGIN CERTIFICATE-----", string.Empty) + .Replace("-----END CERTIFICATE-----", string.Empty) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty) + .Trim(); + + var header = new JwtHeader(signingCredentials) + { + { "x5c", certBase64 }, + }; + + // Use the app's client_id and the standard native client redirect URI + var clientId = string.IsNullOrEmpty(registrationResult!.PrinterClientId) + ? appId + : registrationResult.PrinterClientId; + var redirectUri = string.IsNullOrEmpty(registrationResult.PrinterRedirectUri) + ? "https://login.microsoftonline.com/common/oauth2/nativeclient" + : registrationResult.PrinterRedirectUri; + + var claims = new[] + { + new System.Security.Claims.Claim("request_nonce", nonce), + new System.Security.Claims.Claim("grant_type", "device_token"), + new System.Security.Claims.Claim("resource", registrationResult.PrintServiceResourceId), + new System.Security.Claims.Claim("client_id", clientId), + new System.Security.Claims.Claim("redirect_uri", redirectUri), + new System.Security.Claims.Claim("iss", registrationResult.PrinterId), + }; + + var payload = new JwtPayload(claims); + var jwt = new JwtSecurityToken(header, payload); + var handler = new JwtSecurityTokenHandler(); + return handler.WriteToken(jwt); + } +} diff --git a/BadgeReleaseDemo/BadgeReleaseDemo.csproj b/BadgeReleaseDemo/BadgeReleaseDemo.csproj new file mode 100644 index 0000000..c0982d0 --- /dev/null +++ b/BadgeReleaseDemo/BadgeReleaseDemo.csproj @@ -0,0 +1,28 @@ + + + + Exe + net8.0 + BadgeReleaseDemo + BadgeReleaseDemo + enable + enable + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/BadgeReleaseDemo/BadgeReleaseDemo.sln b/BadgeReleaseDemo/BadgeReleaseDemo.sln new file mode 100644 index 0000000..f64a8e0 --- /dev/null +++ b/BadgeReleaseDemo/BadgeReleaseDemo.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BadgeReleaseDemo", "BadgeReleaseDemo.csproj", "{C8E0786E-1129-36F5-7092-E300E7CF2EA2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C8E0786E-1129-36F5-7092-E300E7CF2EA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8E0786E-1129-36F5-7092-E300E7CF2EA2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8E0786E-1129-36F5-7092-E300E7CF2EA2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8E0786E-1129-36F5-7092-E300E7CF2EA2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {EDDF716F-FB42-40B6-9578-912FDE8C5DB8} + EndGlobalSection +EndGlobal diff --git a/BadgeReleaseDemo/GraphApi/BadgeManagement.cs b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs new file mode 100644 index 0000000..4594c6a --- /dev/null +++ b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs @@ -0,0 +1,136 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using BadgeReleaseDemo.Helpers; + +namespace BadgeReleaseDemo.GraphApi; + +/// +/// Handles badge collection and badge CRUD via MS Graph API. +/// +public class BadgeManagement +{ + private const string SingleCollectionId = "0"; + private readonly string graphBaseUrl; + private readonly HttpClient httpClient; + + public BadgeManagement(string graphBaseUrl) + { + this.graphBaseUrl = graphBaseUrl; + httpClient = new HttpClient(); + } + + /// + /// Creates a badge collection. Handles 409 Conflict if it already exists. + /// Returns the collection ID (always "0"). + /// + public async Task CreateBadgeCollectionAsync(string accessToken) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var content = new StringContent("{}", Encoding.UTF8, "application/json"); + var response = await httpClient.PostAsync($"{graphBaseUrl}/print/badgeCollections", content); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (response.StatusCode == HttpStatusCode.Conflict) + { + ConsoleHelper.WriteInfo("Badge collection already exists (this is OK)."); + return SingleCollectionId; + } + + if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Accepted) + { + throw new HttpRequestException($"Failed to create badge collection: {response.StatusCode} - {responseBody}"); + } + + ConsoleHelper.WriteInfo("Badge collection creation initiated."); + + // Poll the operation if it's 202 Accepted + if (response.StatusCode == HttpStatusCode.Accepted) + { + ConsoleHelper.WriteProgress("Waiting for badge collection to be provisioned..."); + await Task.Delay(3000); + } + + return SingleCollectionId; + } + + /// + /// Adds a badge to the collection with the given badge ID and user UPN. + /// + public async Task AddBadgeAsync(string accessToken, string badgeId, string upn) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var requestBody = new + { + id = badgeId, + upn + }; + + var json = JsonSerializer.Serialize(requestBody); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync( + $"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}/badges", + content); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (response.StatusCode == HttpStatusCode.Conflict) + { + ConsoleHelper.WriteInfo($"Badge '{badgeId}' already exists. Updating..."); + await UpdateBadgeAsync(accessToken, badgeId, upn); + return; + } + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to add badge: {response.StatusCode} - {responseBody}"); + } + } + + /// + /// Updates an existing badge's UPN. + /// + private async Task UpdateBadgeAsync(string accessToken, string badgeId, string upn) + { + var requestBody = new { upn }; + var json = JsonSerializer.Serialize(requestBody); + var request = new HttpRequestMessage(HttpMethod.Patch, + $"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}/badges/{badgeId}") + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to update badge: {response.StatusCode} - {responseBody}"); + } + } + + /// + /// Deletes a badge from the collection. + /// + public async Task DeleteBadgeAsync(string accessToken, string badgeId) + { + using var request = new HttpRequestMessage(HttpMethod.Delete, + $"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}/badges/{badgeId}"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.SendAsync(request); + if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.NotFound) + { + var body = await response.Content.ReadAsStringAsync(); + ConsoleHelper.WriteWarning($"Failed to delete badge: {response.StatusCode} - {body}"); + } + } +} diff --git a/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs b/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs new file mode 100644 index 0000000..5326fd7 --- /dev/null +++ b/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs @@ -0,0 +1,156 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using BadgeReleaseDemo.Helpers; + +namespace BadgeReleaseDemo.GraphApi; + +/// +/// Handles print job creation, document upload, and job start via MS Graph API. +/// +public class PrintJobSubmission +{ + private readonly string graphBaseUrl; + private readonly HttpClient httpClient; + + public PrintJobSubmission(string graphBaseUrl) + { + this.graphBaseUrl = graphBaseUrl; + httpClient = new HttpClient(); + } + + /// + /// Creates a print job on a printer share. + /// Matches the functional test pattern: configuration only, no documents in initial creation. + /// Returns (jobId, documentId). + /// + public async Task<(string JobId, string DocumentId)> CreateJobAsync( + string accessToken, string shareId, string displayName) + { + using var request = new HttpRequestMessage(HttpMethod.Post, + $"{graphBaseUrl}/print/shares/{shareId}/jobs"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + // Match functional test pattern: configuration with copies + dpi, no documents + var requestBody = new Dictionary + { + ["configuration"] = new Dictionary + { + ["copies"] = 1, + ["dpi"] = 600, + }, + }; + + var json = JsonSerializer.Serialize(requestBody, new JsonSerializerOptions + { + DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull, + }); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + ConsoleHelper.WriteInfo($"POST {graphBaseUrl}/print/shares/{shareId}/jobs"); + ConsoleHelper.WriteInfo($"Body: {json}"); + + var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to create print job: {response.StatusCode} - {responseBody}"); + } + + var jobDoc = JsonSerializer.Deserialize(responseBody); + var jobId = jobDoc.GetProperty("id").GetString()!; + + // Extract document ID from the first document in the response + var documents = jobDoc.GetProperty("documents"); + var documentId = documents[0].GetProperty("id").GetString()!; + + return (jobId, documentId); + } + + /// + /// Creates an upload session for a document. + /// Returns the upload URL. + /// + public async Task CreateUploadSessionAsync( + string accessToken, string shareId, string jobId, string documentId, long documentSize) + { + using var request = new HttpRequestMessage(HttpMethod.Post, + $"{graphBaseUrl}/print/shares/{shareId}/jobs/{jobId}/documents/{documentId}/createUploadSession"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var requestBody = new Dictionary + { + ["properties"] = new Dictionary + { + ["documentName"] = "SampleDocument.pdf", + ["contentType"] = "application/pdf", + ["size"] = documentSize, + }, + }; + + var json = JsonSerializer.Serialize(requestBody); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to create upload session: {response.StatusCode} - {responseBody}"); + } + + var sessionDoc = JsonSerializer.Deserialize(responseBody); + return sessionDoc.GetProperty("uploadUrl").GetString() + ?? throw new InvalidOperationException("No uploadUrl in response."); + } + + /// + /// Uploads a PDF document to the upload session. + /// + public async Task UploadDocumentAsync(string accessToken, string uploadUrl, byte[] pdfData) + { + using var request = new HttpRequestMessage(HttpMethod.Put, uploadUrl); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + request.Content = new ByteArrayContent(pdfData); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); + request.Content.Headers.ContentLength = pdfData.Length; + request.Content.Headers.Add("Content-Range", $"bytes 0-{pdfData.Length - 1}/{pdfData.Length}"); + + var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to upload document: {response.StatusCode} - {responseBody}"); + } + + ConsoleHelper.WriteInfo($"Uploaded {pdfData.Length} bytes."); + } + + /// + /// Starts the print job after document upload. + /// + public async Task StartJobAsync(string accessToken, string shareId, string jobId) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var content = new StringContent("{}", Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync( + $"{graphBaseUrl}/print/shares/{shareId}/jobs/{jobId}/start", content); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to start print job: {response.StatusCode} - {responseBody}"); + } + + ConsoleHelper.WriteInfo("Print job started."); + } +} diff --git a/BadgeReleaseDemo/GraphApi/PrinterRegistration.cs b/BadgeReleaseDemo/GraphApi/PrinterRegistration.cs new file mode 100644 index 0000000..589a585 --- /dev/null +++ b/BadgeReleaseDemo/GraphApi/PrinterRegistration.cs @@ -0,0 +1,162 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using BadgeReleaseDemo.Helpers; + +namespace BadgeReleaseDemo.GraphApi; + +/// +/// Handles printer registration via the Universal Print Registration Service +/// (https://register.print.microsoft.com). +/// +public class PrinterRegistration +{ + private readonly string registrationBaseUrl; + private readonly HttpClient httpClient; + + public PrinterRegistration(string registrationBaseUrl) + { + this.registrationBaseUrl = registrationBaseUrl.TrimEnd('/'); + httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(2) }; + } + + /// + /// Registers a new printer with Universal Print via the registration service. + /// Returns a record with all fields needed for device token acquisition. + /// + public async Task RegisterPrinterAsync( + string accessToken, + string displayName, + string csrContent, + string transportKey) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + // Step 1: Initiate registration via POST /api/v1.0/register + // Registration API uses snake_case field names + var requestBody = new Dictionary + { + ["name"] = displayName, + ["manufacturer"] = "Badge Release Demo", + ["model"] = "Virtual Printer", + ["device_type"] = "printer", + ["device_capabilities"] = new[] { "print" }, + ["preferred_lang"] = "en-us", + ["certificate_request"] = new Dictionary + { + ["type"] = "pkcs10", + ["data"] = csrContent, + ["transport_key"] = transportKey, + }, + ["has_physical_device"] = false, + }; + + var json = JsonSerializer.Serialize(requestBody); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var regUrl = $"{registrationBaseUrl}/api/v1.0/register"; + var response = await httpClient.PostAsync(regUrl, content); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to initiate printer registration: {response.StatusCode} - {responseBody}"); + } + + // Parse the registration ID from the response + var regResp = JsonSerializer.Deserialize(responseBody); + var registrationId = regResp.GetProperty("registration_id").GetString() + ?? throw new InvalidOperationException("No registration_id in registration response."); + + ConsoleHelper.WriteInfo($"Registration initiated (ID: {registrationId})"); + + // Step 2: Poll GET /api/v1.0/register?registration_id={id} until 200 OK + ConsoleHelper.WriteProgress("Polling registration status..."); + var statusUrl = $"{registrationBaseUrl}/api/v1.0/register?registration_id={registrationId}"; + + var startTime = DateTime.UtcNow; + var maxWait = TimeSpan.FromMinutes(5); + + while (DateTime.UtcNow - startTime < maxWait) + { + var statusResp = await httpClient.GetAsync(statusUrl); + var statusBody = await statusResp.Content.ReadAsStringAsync(); + + if (statusResp.StatusCode == HttpStatusCode.OK) + { + // Registration succeeded — parse printer info + var printerInfo = JsonSerializer.Deserialize(statusBody); + var printerId = printerInfo.GetProperty("cloud_device_id").GetString()!; + + var certPem = string.Empty; + if (printerInfo.TryGetProperty("certificate", out var certProp)) + { + certPem = certProp.GetString() ?? string.Empty; + } + + var deviceTokenUrl = string.Empty; + if (printerInfo.TryGetProperty("device_token_url", out var tokenUrlProp)) + { + deviceTokenUrl = tokenUrlProp.GetString() ?? string.Empty; + } + + var printerClientId = string.Empty; + if (printerInfo.TryGetProperty("printer_client_id", out var clientIdProp)) + { + printerClientId = clientIdProp.GetString() ?? string.Empty; + } + + var printerRedirectUri = string.Empty; + if (printerInfo.TryGetProperty("printer_redirect_uri", out var redirectProp)) + { + printerRedirectUri = redirectProp.GetString() ?? string.Empty; + } + + var PrintServiceResourceId = string.Empty; + if (printerInfo.TryGetProperty("mcp_svc_resource_id", out var resourceProp)) + { + PrintServiceResourceId = resourceProp.GetString() ?? string.Empty; + } + + return new PrinterRegistrationResult( + printerId, certPem, deviceTokenUrl, + printerClientId, printerRedirectUri, PrintServiceResourceId); + } + else if (statusResp.StatusCode == HttpStatusCode.Accepted) + { + // Still processing — wait for the interval specified in the response + var statusDoc = JsonSerializer.Deserialize(statusBody); + int interval = 3; + if (statusDoc.TryGetProperty("interval", out var intervalProp)) + { + interval = intervalProp.GetInt32(); + } + + ConsoleHelper.WriteInfo($"Registration in progress, retrying in {interval}s..."); + await Task.Delay(interval * 1000); + } + else + { + throw new HttpRequestException($"Registration polling failed: {statusResp.StatusCode} - {statusBody}"); + } + } + + throw new TimeoutException("Printer registration timed out after 5 minutes."); + } +} + +/// +/// Contains all fields returned from printer registration needed for device token acquisition. +/// +public record PrinterRegistrationResult( + string PrinterId, + string CertificatePem, + string DeviceTokenUrl, + string PrinterClientId, + string PrinterRedirectUri, + string PrintServiceResourceId); diff --git a/BadgeReleaseDemo/GraphApi/PrinterSharing.cs b/BadgeReleaseDemo/GraphApi/PrinterSharing.cs new file mode 100644 index 0000000..486a30e --- /dev/null +++ b/BadgeReleaseDemo/GraphApi/PrinterSharing.cs @@ -0,0 +1,123 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using BadgeReleaseDemo.Helpers; + +namespace BadgeReleaseDemo.GraphApi; + +/// +/// Handles printer sharing via MS Graph API. +/// +public class PrinterSharing +{ + private readonly string graphBaseUrl; + private readonly HttpClient httpClient; + + public PrinterSharing(string graphBaseUrl) + { + this.graphBaseUrl = graphBaseUrl; + httpClient = new HttpClient(); + } + + /// + /// Creates a printer share with allowAllUsers=true. + /// Returns the share ID. + /// + public async Task CreateShareAsync(string accessToken, string printerId, string displayName) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var requestBody = new Dictionary + { + ["displayName"] = displayName, + ["allowAllUsers"] = true, + ["printer@odata.bind"] = $"{graphBaseUrl}/print/printers/{printerId}", + }; + + var json = JsonSerializer.Serialize(requestBody); + var content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await httpClient.PostAsync($"{graphBaseUrl}/print/shares", content); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to create printer share: {response.StatusCode} - {responseBody}"); + } + + var shareDoc = JsonSerializer.Deserialize(responseBody); + return shareDoc.GetProperty("id").GetString() + ?? throw new InvalidOperationException("No share ID in response."); + } + + /// + /// Enables badge release on the printer via Graph PATCH /print/printers/{id}. + /// Sets releaseMechanisms to qrCode which enables pull-print/badge release. + /// + public async Task EnableBadgeReleaseAsync(string graphToken, string printerId) + { + var patchBody = new + { + releaseMechanisms = new[] + { + new + { + releaseType = "qrCode", + }, + }, + }; + + var json = JsonSerializer.Serialize(patchBody); + using var request = new HttpRequestMessage(HttpMethod.Patch, + $"{graphBaseUrl}/print/printers/{printerId}"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", graphToken); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + + var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException( + $"Failed to enable badge release: {response.StatusCode} - {responseBody}"); + } + } + + /// + /// Deletes a printer share. + /// + public async Task DeleteShareAsync(string accessToken, string shareId) + { + using var request = new HttpRequestMessage(HttpMethod.Delete, + $"{graphBaseUrl}/print/shares/{shareId}"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.SendAsync(request); + if (!response.IsSuccessStatusCode && response.StatusCode != System.Net.HttpStatusCode.NotFound) + { + var body = await response.Content.ReadAsStringAsync(); + ConsoleHelper.WriteWarning($"Failed to delete share: {response.StatusCode} - {body}"); + } + } + + /// + /// Deletes a printer. + /// + public async Task DeletePrinterAsync(string accessToken, string printerId) + { + using var request = new HttpRequestMessage(HttpMethod.Delete, + $"{graphBaseUrl}/print/printers/{printerId}"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.SendAsync(request); + if (!response.IsSuccessStatusCode && response.StatusCode != System.Net.HttpStatusCode.NotFound) + { + var body = await response.Content.ReadAsStringAsync(); + ConsoleHelper.WriteWarning($"Failed to delete printer: {response.StatusCode} - {body}"); + } + } +} diff --git a/BadgeReleaseDemo/Helpers/ConsoleHelper.cs b/BadgeReleaseDemo/Helpers/ConsoleHelper.cs new file mode 100644 index 0000000..1d44b93 --- /dev/null +++ b/BadgeReleaseDemo/Helpers/ConsoleHelper.cs @@ -0,0 +1,92 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace BadgeReleaseDemo.Helpers; + +/// +/// Lightweight console helpers with Unicode indicators and colored text. +/// +public static class ConsoleHelper +{ + public static void WriteHeader(string title) + { + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine(); + Console.WriteLine(new string('═', 60)); + Console.WriteLine($" {title}"); + Console.WriteLine(new string('═', 60)); + Console.ResetColor(); + } + + public static void WriteStep(string emoji, string description) + { + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(); + Console.Write($" {emoji} "); + Console.ForegroundColor = ConsoleColor.Cyan; + Console.WriteLine(description); + Console.ResetColor(); + } + + public static void WriteSuccess(string message) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($" ✅ {message}"); + Console.ResetColor(); + } + + public static void WriteError(string message) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($" ❌ {message}"); + Console.ResetColor(); + } + + public static void WriteInfo(string message) + { + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.WriteLine($" {message}"); + Console.ResetColor(); + } + + public static void WriteWarning(string message) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($" ⚠️ {message}"); + Console.ResetColor(); + } + + public static void WriteProgress(string message) + { + Console.ForegroundColor = ConsoleColor.DarkYellow; + Console.WriteLine($" ⏳ {message}"); + Console.ResetColor(); + } + + public static string Prompt(string message) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write($" ➤ {message}: "); + Console.ResetColor(); + return Console.ReadLine()?.Trim() ?? string.Empty; + } + + public static bool PromptYesNo(string message) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.Write($" ➤ {message} (y/n): "); + Console.ResetColor(); + var input = Console.ReadLine()?.Trim().ToLowerInvariant(); + return input == "y" || input == "yes"; + } + + public static void WriteKeyValue(string key, string? value) + { + Console.ForegroundColor = ConsoleColor.DarkGray; + Console.Write($" {key}: "); + Console.ForegroundColor = ConsoleColor.White; + Console.WriteLine(value ?? "(null)"); + Console.ResetColor(); + } +} diff --git a/BadgeReleaseDemo/Helpers/CryptoHelper.cs b/BadgeReleaseDemo/Helpers/CryptoHelper.cs new file mode 100644 index 0000000..d43ae3a --- /dev/null +++ b/BadgeReleaseDemo/Helpers/CryptoHelper.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using Org.BouncyCastle.Asn1.X509; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Generators; +using Org.BouncyCastle.Crypto.Operators; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Pkcs; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.X509; + +namespace BadgeReleaseDemo.Helpers; + +/// +/// Generates CSR and keypair for printer registration. +/// Certificate and keys are kept in memory only. +/// +public static class CryptoHelper +{ + private const int KeySize = 2048; + + public static AsymmetricCipherKeyPair GenerateKeyPair() + { + var generator = new RsaKeyPairGenerator(); + generator.Init(new KeyGenerationParameters(new SecureRandom(), KeySize)); + return generator.GenerateKeyPair(); + } + + public static string GenerateCsr(AsymmetricCipherKeyPair keyPair) + { + var subject = new X509Name("CN=Universal Print Badge Release Demo"); + var csr = new Pkcs10CertificationRequest( + new Asn1SignatureFactory("SHA256WithRSA", keyPair.Private), + subject, + keyPair.Public, + null); + + using var writer = new StringWriter(); + var pemWriter = new PemWriter(writer); + pemWriter.WriteObject(csr); + pemWriter.Writer.Flush(); + + // API expects base64-encoded DER without PEM headers + var pem = writer.ToString(); + return pem + .Replace("-----BEGIN CERTIFICATE REQUEST-----", string.Empty) + .Replace("-----END CERTIFICATE REQUEST-----", string.Empty) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty) + .Trim(); + } + + public static string GetTransportKey(AsymmetricCipherKeyPair keyPair) + { + using var writer = new StringWriter(); + var pemWriter = new PemWriter(writer); + pemWriter.WriteObject(keyPair.Public); + pemWriter.Writer.Flush(); + + var pem = writer.ToString(); + // Remove PEM header/footer for the transport key + return pem + .Replace("-----BEGIN PUBLIC KEY-----", string.Empty) + .Replace("-----END PUBLIC KEY-----", string.Empty) + .Replace("\r", string.Empty) + .Replace("\n", string.Empty) + .Trim(); + } + + /// + /// Parses a PEM certificate string into an X509Certificate. + /// Used to extract the printer certificate from the registration response. + /// + public static X509Certificate ParseCertificate(string pemCertificate) + { + using var reader = new StringReader(pemCertificate); + var pemReader = new PemReader(reader); + return (X509Certificate)pemReader.ReadObject(); + } +} diff --git a/BadgeReleaseDemo/IppLibrary/Constants.cs b/BadgeReleaseDemo/IppLibrary/Constants.cs new file mode 100644 index 0000000..732f765 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/Constants.cs @@ -0,0 +1,40 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + public static class Constants + { + public const string IppV10 = "1.0"; + public const string IppV11 = "1.1"; + public const string IppV20 = "2.0"; + public const string MopriaDiscoveryV10 = "1.0"; + public const string ConfigurationRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CloudPrint\EnterpriseCloudPrintService"; + public const string LocalSpoolerLayer = "localspl"; + public const string CharSet = "utf-8"; + public const string DocumentFormatPdf = "application/pdf"; + public const string DocumentFormatPwgRaster = "image/pwg-raster"; + public const string DocumentFormatPclm = "application/PCLm"; + public const string DocumentFormatOxps = "application/oxps"; + public const string DocumentFormatUrf = "image/urf"; + public const string DocumentFormatOctetStream = "application/octet-stream"; + public const string UnsupportedAttribute = "unsupported"; + public const string CompressionNone = "none"; + public const string JobsCompleted = "completed"; // Value for WhichJobs attribute. + public const string JobsNotCompleted = "not-completed"; // Value for WhichJobs attribute. + public const string JobFetchable = "fetchable"; // Value for WhichJobs attribute (IPP-INFRA). + public const string Attempted = "attempted"; + public const string NotAttempted = "not-attempted"; + public const int ShortStringLength = 127; // 127 octets maximum for text (127), name (127) attributes (RFC 2911 4.1.1) + public const int MaxTextLength = 1023; // 1023 octets maximum for text (MAX) attributes (RFC 2911 4.1.1) + public const int MaxNameLength = 255; // 255 octets maximum for name (MAX) attributes (RFC 2911 4.1.2) + public const int MaxUriLength = 1023; // 1023 octets maximum for uri attributes (RFC 2911 4.1.5) + public const int MaxKeywordLength = 255; // 255 characters max for keywords (RFC 2911 4.1.3) + public const int MaxMimeTypeLength = 255; // 255 characters max for mime types (RFC 2911 4.1.9) + public const int PrintSchemaVersion = 1; // PrintSchema version + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs b/BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs new file mode 100644 index 0000000..f3c54a7 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs @@ -0,0 +1,64 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + public static class DocumentAttributes + { + public const string Copies = "copies"; + public const string CoverBack = "cover-back"; + public const string CoverFront = "cover-front"; + public const string DateTimeAtCompleted = "date-time-at-completed"; + public const string DocumentJobId = "document-job-id"; + public const string DocumentNaturalLanguage = "document-natural-language"; + public const string DocumentPrinterUri = "document-printer-uri"; + public const string DocumentState = "document-state"; + public const string DocumentStateMessage = "document-state-message"; + public const string DocumentStateReasons = "document-state-reasons"; + public const string DocumentUuid = "document-uuid"; + public const string FeedOrientation = "feed-orientation"; + public const string Finishings = "finishings"; + public const string FinishingsCol = "finishings-col"; + public const string ImpressionsCompleted = "impressions-completed"; + public const string LastDocument = "last-document"; + public const string Media = "media"; + public const string MediaBackCoating = "media-back-coating"; + public const string MediaBottomMargin = "media-bottom-margin"; + public const string MediaCol = "media-col"; + public const string MediaColor = "media-color"; + public const string MediaGrain = "media-grain"; + public const string MediaFrontCoating = "media-front-coating"; + public const string MediaHoleCount = "media-hole-count"; + public const string MediaInfo = "media-info"; + public const string MediaKey = "media-key"; + public const string MediaLeftMargin = "media-left-margin"; + public const string MediaOrderCount = "media-order-count"; + public const string MediaPreprinted = "media-pre-printed"; + public const string MediaRecycled = "media-recycled"; + public const string MediaRightMargin = "media-right-margin"; + public const string MediaSize = "media-size"; + public const string MediaSizeName = "media-size-name"; + public const string MediaSource = "media-source"; + public const string MediaThickness = "media-thickness"; + public const string MediaTooth = "media-tooth"; + public const string MediaTopMargin = "media-top-margin"; + public const string MediaType = "media-type"; + public const string MediaWeightMetric = "media-weight-metric"; + public const string OrientationRequested = "orientation-requested"; + public const string OutputBin = "output-bin"; + public const string Overrides = "overrides"; + public const string PageRanges = "page-ranges"; + public const string PrintColorMode = "print-color-mode"; + public const string PrintContentOptimize = "print-content-optimize"; + public const string PrintRenderingIntent = "print-rendering-intent"; + public const string PrintQuality = "print-quality"; + public const string PrinterResolution = "printer-resolution"; + public const string Sides = "sides"; + public const string XDimension = "x-dimension"; + public const string YDimension = "y-dimension"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/Helpers.cs b/BadgeReleaseDemo/IppLibrary/Helpers.cs new file mode 100644 index 0000000..7c271d2 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/Helpers.cs @@ -0,0 +1,861 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using BadgeReleaseDemo.IppLibrary.Common; + + public static class Helpers + { + /// + /// Used in the job-uri path. + /// Example: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 + /// + private const string JobUriSegment = "jobs"; + + /// + /// Used in the printer-uri and job-uri path. + /// Example: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 + /// + private const string PrinterSegment = "printers"; + + /// + /// URN URI Scheme + /// + private const string UrnScheme = "urn"; + + /// + /// Prefix for UUID URIs + /// + private const string UuidPrefix = "uuid:"; + + public static void WriteNetworkShort(Stream s, short shortValue) + { + s.WriteByte((byte)(shortValue >> 8)); + s.WriteByte((byte)shortValue); + } + + public static void WriteNetworkInteger(Stream s, int intValue) + { + s.WriteByte((byte)(intValue >> 24)); + s.WriteByte((byte)((intValue & 0x00ff0000) >> 16)); + s.WriteByte((byte)((intValue & 0x0000ff00) >> 8)); + s.WriteByte((byte)intValue); + } + + public static void WriteAsciiString(Stream s, string stringValue) + { + byte[] arr = Encoding.ASCII.GetBytes(stringValue); + MemoryStream ms = new MemoryStream(arr); + ms.CopyTo(s); + } + + public static byte[] StringToUTF8ByteArray(string stringValue) + { + if (stringValue == null) + { + stringValue = string.Empty; + } + + return Encoding.UTF8.GetBytes(stringValue); + } + + public static string UTF8ByteArrayToString(byte[] byteArray) + { + return Encoding.UTF8.GetString(byteArray); + } + + public static string UTF8ByteArrayToString(byte[] byteArray, int offset, int length) + { + if (offset < 0 || length < 0 || offset + length > byteArray.Length) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Buffer overflow reading a string."); + } + + return Encoding.UTF8.GetString(byteArray, offset, length); + } + + /// + /// Unlike the above, this is more or less correct, though we may want to check for non-ASCII chars in the incoming string. + /// + /// The string to convert to byte array. + /// Byte array representation of the string. + public static byte[] StringToASCIIByteArray(string stringValue) + { + return Encoding.ASCII.GetBytes(stringValue); + } + + /// + /// See comments above + /// + /// The byte array. + /// String of byte array. + public static string ASCIIByteArrayToString(byte[] byteArray) + { + return Encoding.ASCII.GetString(byteArray); + } + + /// + /// Recover a string from a byte array. + /// + /// The byte array. + /// Offset to the beginning of the string. + /// Length of string. + /// ASCII string of the byte array. + public static string ASCIIByteArrayToString(byte[] byteArray, int offset, int length) + { + if (offset < 0 || length < 0 || offset + length > byteArray.Length) + { + throw new IPPException(StatusCode.ClientErrorNotPossible, "Buffer overflow reading a string."); + } + + var sb = new StringBuilder(); + for (var i = 0; i < length; i++) + { + sb.Append((char)byteArray[i + offset]); + } + + return sb.ToString(); + } + + public static bool ByteArrayToBool(byte[] b) + { + return b[0] != 0; + } + + public static short ByteArrayToShort(byte[] b) + { + return (short)(ushort)((b[0] << 8) | b[1]); + } + + public static byte[] ShortToByteArray(short x) + { + var b = new byte[2]; + b[0] = (byte)(x >> 8); + b[1] = (byte)x; + return b; + } + + public static int ByteArrayToInteger(byte[] byteArray) + { + return (byteArray[0] << 24) | (byteArray[1] << 16) | (byteArray[2] << 8) | byteArray[3]; + } + + public static Tuple ByteArrayToIntegerRange(byte[] byteArray) + { + var lower = (byteArray[0] << 24) | (byteArray[1] << 16) | (byteArray[2] << 8) | byteArray[3]; + var upper = (byteArray[4] << 24) | (byteArray[5] << 16) | (byteArray[6] << 8) | byteArray[7]; + return new Tuple(lower, upper); + } + + /// + /// Converts an integer to a byte array + /// + /// The integer value. + /// Byte array representation of an integer. + public static byte[] IntegerToByteArray(int intValue) + { + byte[] b = new byte[4]; + b[0] = (byte)(intValue >> 24); + b[1] = (byte)((intValue & 0x00ff0000) >> 16); + b[2] = (byte)((intValue & 0x0000ff00) >> 8); + b[3] = (byte)intValue; + + return b; + } + + /// + /// Converts a range of integer to a byte array. + /// + /// Lower bound of the integer values. + /// Upper bound of the integer values. + /// Byte array representation of an integer range. + public static byte[] IntegerRangeToByteArray(int lower, int upper) + { + if (lower > upper) + { + throw new IPPException(StatusCode.ClientErrorNotPossible, "Incorrect integer range."); + } + + var lowerBound = IntegerToByteArray(lower); + var upperBound = IntegerToByteArray(upper); + return lowerBound.Concat(upperBound).ToArray(); + } + + /// + /// Converts .Net DateTime to a byte array according to https://tools.ietf.org/html/rfc2579 page 18. + /// + /// DateTime value, always in UTC. + /// Byte array representation of a date time object. + public static byte[] DateTimeToByteArray(DateTime dateTime) + { + var byteArray = new[] + { + (byte)(dateTime.Year >> 8), + (byte)dateTime.Year, + (byte)dateTime.Month, + (byte)dateTime.Day, + (byte)dateTime.Hour, + (byte)dateTime.Minute, + (byte)dateTime.Second, + (byte)(dateTime.Millisecond / 100), + (byte)'+', + (byte)0, + (byte)0 + }; + + return byteArray; + } + + /// + /// Converts IPP resolution (https://tools.ietf.org/html/rfc8011#section-5.1.16) to byte array. + /// + /// The x dimension. + /// The y dimension. + /// The unit of the dimensions. + /// Byte array representation of resolution dimensions. + public static byte[] ResolutionToByteArray(int x, int y, sbyte units) + { + var xStream = IntegerToByteArray(x); + var yStream = IntegerToByteArray(y); + var unitStream = new byte[] { (byte)units }; + return xStream.Concat(yStream).Concat(unitStream).ToArray(); + } + + /// + /// Converts a byte array IPP resolution (https://tools.ietf.org/html/rfc8011#section-5.1.16). + /// + /// IPP resolution. + /// Resolution dimension of the byte array. + public static Tuple ByteArrayToResolution(byte[] b) + { + var x = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; + var y = (b[4] << 24) | (b[5] << 16) | (b[6] << 8) | b[7]; + var units = (sbyte)b[8]; + return new Tuple(x, y, units); + } + + /// + /// Converts a string to IPP string with natural language (e.g., TextWithNaturalLanguage and NameWithNaturalLanguage). + /// + /// The plain string. + /// The natural language + /// Byte array of the string. + public static byte[] StringWithNaturalLanguageToByteArray(string theString, string naturalLanguage) + { + // Note: from the RFC: + // a. a SIGNED-SHORT which is the number of + // octets in the following field + // b. a value of type natural-language, + // c. a SIGNED-SHORT which is the number of + // octets in the following field, + // d. a value of type textWithoutLanguage. + var langLength = ShortToByteArray((short)naturalLanguage.Length); + var langValue = StringToASCIIByteArray(naturalLanguage); + var stringValue = StringToUTF8ByteArray(theString); + /* + * https://tools.ietf.org/html/rfc8010#section-3 + * 3.9. (Attribute) "value" + +----------------------+--------------------------------------------+ + | Syntax of Attribute | Encoding | + | Value | | + +----------------------+--------------------------------------------+ + | textWithoutLanguage, | LOCALIZED-STRING | + | nameWithoutLanguage | | + +----------------------+--------------------------------------------+ + | textWithLanguage | OCTET-STRING consisting of four fields: a | + | | SIGNED-SHORT, which is the number of | + | | octets in the following field; a value of | + | | type natural-language; a SIGNED-SHORT, | + | | which is the number of octets in the | + | | following field; and a value of type | + | | textWithoutLanguage. The length of a | + | | textWithLanguage value MUST be 4 + the | + | | value of field a + the value of field c. | + +----------------------+--------------------------------------------+ + */ + + // NOTE: the SIGNED-SHORT of field c is the number of octets which is LOCALIZED-STRING + var stringLength = ShortToByteArray((short)stringValue.Length); + return langLength.Concat(langValue).Concat(stringLength).Concat(stringValue).ToArray(); + } + + /// + /// Converts a byte array of IPP string with natural language to string and the natural language parts. + /// + /// The IPP stream. + /// String of the byte array. + public static Tuple ByteArrayToStringWithNaturalLanguage(byte[] b) + { + // Note: from the RFC: + // a. a SIGNED-SHORT which is the number of + // octets in the following field + // b. a value of type natural-language, + // c. a SIGNED-SHORT which is the number of + // octets in the following field, + // d. a value of type textWithoutLanguage. + var langLength = (short)(ushort)((b[0] << 8) | b[1]); + var langValue = ASCIIByteArrayToString(b, 2, langLength); + + var languageByteCount = 2 + langLength; + var stringLength = (short)(ushort)(b[languageByteCount] << 8 | b[languageByteCount + 1]); + var stringValue = UTF8ByteArrayToString(b, languageByteCount + 2, stringLength); + + return new Tuple(stringValue, langValue); + } + + /// + /// Converts a byte array to .Net DateTime according to https://tools.ietf.org/html/rfc2579 page 18. + /// + /// IPP DateTime array. + /// Date time object from a byte array. + public static DateTime ByteArrayToDateTime(byte[] dateTimeArray) + { + var year = (dateTimeArray[0] << 8) | dateTimeArray[1]; + var month = dateTimeArray[2]; + var day = dateTimeArray[3]; + var hour = dateTimeArray[4]; + var minute = dateTimeArray[5]; + var second = dateTimeArray[6]; + + // Before version 4.0.1, the 8th octet (dateTimeArray[7]) was used to store centisecond instead of deci second. + // To support the clients sending centiseconds in place of deciseconds, a modulo 1000 is added. + var milliSecond = (dateTimeArray[7] * 100) % 1000; + + return new System.DateTime(year, month, day, hour, minute, second, milliSecond, DateTimeKind.Local); + } + + /// + /// Converts a boolValue to a byte array + /// + /// The boolean value. + /// Byte array of a boolean. + public static byte[] BoolToByteArray(bool boolValue) + { + byte[] b = new byte[1]; + b[0] = (byte)(boolValue ? 1 : 0); + + return b; + } + + /// + /// Example jobUri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 + /// Returned value: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b + /// + /// The uri of a job. + /// The uri of the printer. + public static Uri GetPrinterUriFromJobUri(Uri jobUri) + { + // Example: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 + // Authority: "localhost:44336" + // Segments: [0] "/" + // [1] "printers/" + // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" + // [3] "jobs/" + // [4] "388" + var printerUri = Helpers.CreateUri(jobUri.GetLeftPart(UriPartial.Authority)); + printerUri = Helpers.CreateUri(printerUri, jobUri.Segments[1] + jobUri.Segments[2]); + return printerUri; + } + + /// + /// Example: + /// baseUrl: https://print.print.microsoft.com + /// printerId: 810a8958-13f4-4044-a09b-a372e8990a6b. + /// jobId: 388 + /// Returned value: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b/Jobs/388 + /// + /// The base url. + /// The printer id. + /// The job id. + /// A uri of a job. + public static Uri CreateJobUri(Uri baseUrl, string printerId, int jobId) + { + // Job uri must be "ipps". + var jobUriBuilder = new UriBuilder(baseUrl) { Scheme = "ipps" }; + jobUriBuilder.Path += PrinterSegment + '/' + printerId + '/' + JobUriSegment + '/' + jobId.ToString(CultureInfo.InvariantCulture); + return jobUriBuilder.Uri; + } + + /// + /// Example: + /// baseUrl: https://print.print.microsoft.com + /// printerId: 810a8958-13f4-4044-a09b-a372e8990a6b + /// Returned value: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b + /// + /// The base url. + /// The printer id. + /// Uri of the printer. + public static Uri CreatePrinterUri(Uri baseUrl, string printerId) + { + // Printer uri must be "ipps". + var printerUriBuilder = new UriBuilder(baseUrl) { Scheme = "ipps" }; + printerUriBuilder.Path += PrinterSegment + '/' + printerId; + return printerUriBuilder.Uri; + } + + /// + /// Returns the Printer UUID as a URN URI string given the printerId. + /// For example, urn:uuid:810a8958-13f4-4044-a09b-a372e8990a6b + /// + /// The printer id. + /// UUID URN URI string + public static string CreatePrinterUuidUri(string printerId) + { + return $"{UrnScheme}:{UuidPrefix}{printerId}"; + } + + /// + /// Example: + /// jobUri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 + /// returned value: 388 + /// To avoid returning an invalid job ID (e.g., -1), caller must have jobUri to call this function. + /// + /// Uri of a job. + /// Job id + public static int GetJobIdFromJobUri(Uri jobUri) + { + // Example: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b/jobs/388 + // Segments: [0] "/" + // [1] "printers/" + // [2] "8b518e16-2d2b-11e8-b467-0ed5f89f718b" + // [3] "jobs/" + // [4] "388" + var jobId = int.Parse(jobUri.Segments[4].TrimEnd('/'), CultureInfo.CurrentCulture); + return jobId; + } + + /// + /// Return the output-device-uuid from its uri format. + /// Example: + /// outputDeviceUuidUri: urn:uuid:01234567-89AB-CDEF-FEDC-BA9876543210 + /// Return: 01234567-89AB-CDEF-FEDC-BA9876543210 + /// + /// The uuid URN. + /// The uuid string. + public static string GetOutputDeviceUuidFromUri(string outputDeviceUuidUri) + { + Uri uuidUri; + + try + { + uuidUri = new Uri(outputDeviceUuidUri); + } + catch + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "The output-device-uuid must be a uri."); + } + + if (string.Compare(uuidUri.Scheme, UrnScheme, StringComparison.OrdinalIgnoreCase) != 0) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "The output-device-uuid must be of URN scheme."); + } + + if (!CultureInfo.InvariantCulture.CompareInfo.IsPrefix(uuidUri.LocalPath, UuidPrefix, CompareOptions.IgnoreCase)) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "The output-device-uuid must be of format urn:uuid:."); + } + + return uuidUri.LocalPath.Substring(UuidPrefix.Length); + } + + /// + /// Example printer URL: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b + /// This function returns: "8b518e16-2d2b-11e8-b467-0ed5f89f718b". + /// + /// Printer URL. This is typically obtained from printer-uri attribute. + /// The printer id that is extracted from printerUrl. + public static string GetPrinterIdFromPrinterUri(Uri printerUri) + { + // Example: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b + // Segments: [0] "/" + // [1] "printers/" + // [2] "8b518e16-2d2b-11e8-b467-0ed5f89f718b" + // printer ID is in segment 2 and may have trailing '/'. + if (printerUri == null) + { + return string.Empty; + } + + Helpers.IsValidPrinterUri(printerUri); + return printerUri.Segments[2].TrimEnd('/'); + } + + /// + /// Example of job uri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388. + /// This function returns: 810a8958-13f4-4044-a09b-a372e8990a6b + /// + /// Valid job uri. This is typically obtained from job-uri attribute. + /// The printer id that is extracted from the jobUri. + public static string GetPrinterIdFromJobUri(Uri jobUri) + { + // Example: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 + // Segments: [0] "/" + // [1] "printers" + // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" + // [3] "jobs/" + // [4] "388" + // Printer id is segment 2 and may have trailing '/'. + if (jobUri == null) + { + return string.Empty; + } + + Helpers.IsValidJobUri(jobUri); + return jobUri.Segments[2].TrimEnd('/'); + } + + /// + /// Retrieve the system uptime. RFC2911 4.3.14.4 specifies job-printer-up-time as an integer (giving us 68 years of resolution) + /// + /// System up time in integer. + public static int GetSystemUptime(DateTime printerCreationTimeUtc) + { + var nowTime = DateTime.UtcNow; + var upTime = nowTime - printerCreationTimeUtc; + return (int)upTime.TotalSeconds; // double to int, max printer life time is 68 years. + } + + /// + /// Retrieve the culture installed on the print server. This returns GetSystemDefaultUILanguage() + /// + /// Natural language string. + public static string GetNaturalLanguage() + { + return CultureInfo.InstalledUICulture.Name.ToLower(CultureInfo.CurrentCulture); + } + + /// + /// Copy attribute value from either group 1, 2 or 3 to the target attribute group. + /// + /// The name of the attribute. + /// The destination to copy the attribute to. + /// Attribute group 1. + /// Attribute group 2. + /// Attribute group 3. + /// True if the attribute already exists in or is inserted to destinationGroup. + public static bool CopyAttribute( + string attributeName, + IppAttributeGroup destinationGroup, + IppAttributeGroup group1, + IppAttributeGroup group2 = null, + IppAttributeGroup group3 = null) + { + // No duplicate needed. + if (destinationGroup.Attributes.ContainsKey(attributeName)) + { + return true; + } + + if (group1 != null && group1.Attributes.ContainsKey(attributeName)) + { + destinationGroup.AddAttribute(group1.Attributes[attributeName]); + return true; + } + else if (group2 != null && group2.Attributes.ContainsKey(attributeName)) + { + destinationGroup.AddAttribute(group2.Attributes[attributeName]); + return true; + } + else if (group3 != null && group3.Attributes.ContainsKey(attributeName)) + { + destinationGroup.AddAttribute(group3.Attributes[attributeName]); + return true; + } + + return false; + } + + /// + /// Returns the attribute value from either group 1, 2 or 3 to the target attribute group. + /// + /// The name of the attribute. + /// Attribute group 1. + /// Attribute group 2. + /// Attribute group 3. + /// Returns the attribute value if found, null otherwise. + public static IppAttribute GetAttribute( + string attributeName, + IppAttributeGroup group1, + IppAttributeGroup group2 = null, + IppAttributeGroup group3 = null) + { + if (group1 != null && group1.Attributes.ContainsKey(attributeName)) + { + return group1.Attributes[attributeName]; + } + else if (group2 != null && group2.Attributes.ContainsKey(attributeName)) + { + return group2.Attributes[attributeName]; + } + else if (group3 != null && group3.Attributes.ContainsKey(attributeName)) + { + return group3.Attributes[attributeName]; + } + + return null; + } + + /// + /// Return true if date time is default value. + /// + public static bool IsDefaultDateTime(DateTime dateTime) + { + var isDefault = dateTime == DateTime.MinValue.ToUniversalTime(); + return isDefault; + } + + /// + /// Default date time. + /// + public static DateTime GetDefaultDateTime() + { + var defaultTime = DateTime.MinValue.ToUniversalTime(); + return defaultTime; + } + + public static string GetDefaultDateTimeString() + { + var defaultTime = DateTime.MinValue.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture); + return defaultTime; + } + + /// + /// Return current time in UTC. + /// + public static DateTime GetCurrentTimeUtc() + { + return DateTime.UtcNow; + } + + /// + /// Return current time in UTC. + /// + public static string GetCurrentTimeUtcString() + { + return DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture); + } + + /// + /// Parse dateTimeString to DateTime. Return default time if unsuccessfull (e.g., for older data). + /// + public static DateTime ParseDateTime(string dateTimeString) + { + // See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings?view=netframework-4.7.2 for the "o" format. + // Example dateTimeString: "2018-12-10T06:58:21.0075545Z". + if (!DateTime.TryParse(dateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime dateTimeToReturn)) + { + dateTimeToReturn = GetDefaultDateTime(); + } + + return dateTimeToReturn; + } + + /// + /// Calculate the numbers of seconds from time1 to time2. + /// + public static int CalculateSecondsSince(DateTime time1, DateTime time2) + { + var timeSpan = time2 - time1; + return (int)timeSpan.TotalSeconds; + } + + /// + /// Returns true if tag is for an attribute group. + /// + public static bool IsBeginAttributeGroupTag(Tag tag) + { + return (tag < Tag.Unsupported && tag >= Tag.Reserved) && tag != Tag.EndOfAttributes; + } + + /// + /// Returns true if tag is for an attribute. + /// + public static bool IsValueTag(Tag tag) + { + return tag >= Tag.Unsupported; + } + + /// + /// Returns true if tag is for a begin collection attribute tag. + /// https://tools.ietf.org/html/rfc8010#section-3.1.6 + /// + public static bool IsBeginCollectionValueTag(Tag tag) + { + return tag == Tag.BegCollection; + } + + /// + /// Returns true if tag is for member attribute tag. + /// + public static bool IsMemberAttrNameTag(Tag tag) + { + return tag == Tag.MemberAttrName; + } + + /// + /// Create a new uri object. + /// + /// The string representation of the uri. + /// Newly created uri object. + public static Uri CreateUri(string uriString) + { + try + { + return new Uri(uriString); + } + catch + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid uri: " + uriString); + } + } + + /// + /// Create a new uri object. + /// + /// Base uri. + /// Additional segments. + /// Newly created uri object. + public static Uri CreateUri(Uri uri, string segments) + { + try + { + return new Uri(uri, segments); + } + catch + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid uri: " + uri.ToString() + ". segments: " + segments); + } + } + + /// + /// Throw if the printerUri is invalid. + /// + /// Printer uri, usually from printer-uri attribute. + public static void IsValidPrinterUri(Uri printerUri) + { + const string printersSegment = "printers/"; + + // Example of correct printer-uri: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b. + // Segments: [0] "/" + // [1] "printers/" + // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" + if (printerUri.Segments.Length != 3 || + string.Compare(printerUri.Segments[1], printersSegment, StringComparison.OrdinalIgnoreCase) != 0) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid printer-uri: " + printerUri.ToString()); + } + } + + /// + /// Throw if jobUri is not valid. + /// + /// The uri of a job. + public static void IsValidJobUri(Uri jobUri) + { + const string printersSegment = "printers/"; + const string jobUriSegment = "jobs/"; + + // Example of correct job-uri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 + // Segments: [0] "/" + // [1] "printers/" + // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" + // [3] "jobs/" + // [4] "388" + if (jobUri.Segments.Length != 5 || + string.Compare(jobUri.Segments[1], printersSegment, StringComparison.OrdinalIgnoreCase) != 0 || + string.Compare(jobUri.Segments[3], jobUriSegment, StringComparison.OrdinalIgnoreCase) != 0) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid job-uri: " + jobUri.ToString()); + } + } + + /// + /// See comments above + /// + /// array + /// string representation of b + public static string ByteArrayToAsciiString(byte[] array) + { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < array.Length; i++) + { + sb.Append((char)array[i]); + } + + return sb.ToString(); + } + + /// + /// Retrieve the system uptime. RFC2911 4.3.14.4 specifies job-printer-up-time as an integer (giving us 68 years of resolution) + /// + /// time as integer + public static int GetSystemUptime() + { + return System.Environment.TickCount; + } + + public static IppAttributeGroup CreateOperationAttributes() + { + var operationAttributes = new IppAttributeGroup(Tag.OperationAttributes); + + // Add required operation attributes + operationAttributes.AddAttribute(new IppAttribute(OperationAttributes.AttributesCharset, IppValue.CreateCharsetValue(IppEncoding.CharSet))); + operationAttributes.AddAttribute(new IppAttribute(OperationAttributes.AttributesNaturalLanguage, IppValue.CreateNaturalLanguageValue(Helpers.GetNaturalLanguage()))); + return operationAttributes; + } + + public static IppAttributeGroup CreateSubscriptionAttributes() + { + var subscriptionAttributes = new IppAttributeGroup(Tag.SubscriptionAttributes); + + // Add required operation attributes + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyCharset, IppValue.CreateCharsetValue(IppEncoding.CharSet))); + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyNaturalLanguage, IppValue.CreateNaturalLanguageValue(Helpers.GetNaturalLanguage()))); + return subscriptionAttributes; + } + + public static IppAttributeGroup CreateEventAttributes(int subscriptionId, string notifySubscribedEvent, int sequenceNumber, string notifyText) + { + var subscriptionAttributes = new IppAttributeGroup(Tag.EventNotificationAttributes); + + // Add required operation attributes + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyCharset, IppValue.CreateCharsetValue(IppEncoding.CharSet))); + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyNaturalLanguage, IppValue.CreateNaturalLanguageValue(Helpers.GetNaturalLanguage()))); + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifySubscriptionId, IppValue.CreateIntegerValue(subscriptionId))); + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifySubscribedEvent, IppValue.CreateKeywordValue(notifySubscribedEvent))); + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifySequenceNumber, IppValue.CreateIntegerValue(sequenceNumber))); + subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyText, IppValue.CreateNameWithoutLanguageValue(notifyText))); + return subscriptionAttributes; + } + + // See https://ftp.pwg.org/pub/pwg/ipp/registrations/apple-printer-firmware-20190724.txt + public static byte[] FirmwareStringVersionToFirmwareVersion(string version) + { + var versionBytes = new List() { 0 }; + foreach (var c in version) + { + if (byte.TryParse(c.ToString(), out var b)) + { + versionBytes.Add(b); + } + else + { + versionBytes.Add(0); + } + } + + return versionBytes.ToArray(); + } + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs b/BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs new file mode 100644 index 0000000..698d034 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs @@ -0,0 +1,284 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + + public interface IIppRequestFactory + { + /// + /// Create Cancel-Job request. + /// + /// + /// + /// + /// + /// + /// + Task CreateCancelJobRequestAsync(int requestId, string requestingUserName, string requestingUserUri, + int jobId, string jobUri); + + /// + /// Create Get-Printer-Attributes request. + /// + /// + /// + /// + /// + /// + /// + Task CreateGetPrinterAttributesRequestAsync( + int requestId, string requestingUserName, string requestingUserUri, List extraOperationAttributes, List requestedAttributes); + + /// + /// Create Get-Output-Device-Attributes request. + /// + /// + /// + /// + /// + /// + /// + Task CreateGetOutputDeviceAttributesRequestAsync( + int requestId, string requestingUserName, string requestingUserUri, List extraOperationAttributes, List requestedAttributes); + + /// + /// Create Get-Printer-Attributes request. + /// + /// + /// + /// + /// + /// + Task CreateGetSupportedPrinterAttributesRequestAsync( + int requestId, string requestingUserName, string requestingUserUri, List requestedAttributes); + + /// + /// Create Get-Printer-Attributes request. + /// + /// + /// + /// + /// + /// + Task CreateSetPrinterAttributesRequestAsync( + int requestId, + string requestingUserName, + string requestingUserUri, + IEnumerable attributesToSet); + + /// + /// Create Get-Printer-Device-Capabilities request. + /// + /// + /// + /// + Task CreateGetPrinterDeviceCapabilitiesRequestAsync(int requestId, string requestingUserName, string requestingUserUri); + + /// + /// Create Validate-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.3). + /// + /// Request id of the IPP packet. + /// + /// + /// + /// + Task CreateValidateJobRequestAsync( + int requestId, + string jobName, + string documentName, + IppAttributeGroup extraOperationAttributes = null, + IppAttributeGroup jobTemplateAttributes = null); + + /// + /// Create Print-Job request. + /// + /// requestId + /// jobName + /// documentName + /// documentPayload + /// Attributes to be included in the operation attribute group. + /// The job template attributes to be included in the request. + /// representation + Task CreatePrintJobRequestAsync(int requestId, string jobName, string documentName, + Stream documentPayload, IppAttributeGroup extraOperationAttributes, IppAttributeGroup jobTemplateAttributes); + + /// + /// Create Create-Job request. + /// + /// requestId + /// jobName + /// The job template attributes to be included in the request. + /// representation + Task CreateCreateJobRequestAsync(int requestId, string jobName = null, IppAttributeGroup jobTemplateAttributes = null); + + /// + /// Create Send-Document request. + /// + /// requestId + /// lastDocument + /// jobUri + /// jobId + /// documentPayload + /// Attributes to be included in the operation attribute group. + /// The job template attributes to be included in the request. + /// representation + Task CreateSendDocumentRequestAsync( + int requestId, + bool lastDocument, + string jobUri = null, + int jobId = 0, + Stream documentPayload = null, + IppAttributeGroup extraOperationAttributes = null, + IppAttributeGroup jobTemplateAttributes = null); + + /// + /// Create Close-Job request. + /// + /// requestId + /// jobUri + /// jobId + /// representation + Task CreateCloseJobRequestAsync(int requestId, string jobUri = null, int jobId = 0); + + /// + /// Create Get-Job-Attributes request. + /// + /// requestId + /// jobUri + /// requestedAttributes + /// representation + Task CreateGetJobAttributesRequestAsync(int requestId, string jobUri, + List requestedAttributes); + + /// + /// Create Get-Jobs request. + /// + /// requestId + /// jobType + /// requestingUserName + /// requestingUserUri + /// printerUri + /// outputDeviceUuid + /// requestedAttributes + /// representation + Task CreateGetJobsRequestAsync(int requestId, + string jobType, + string requestingUserName, + string requestingUserUri, + string printerUri, + string outputDeviceUuid, + List requestedAttributes); + + /// + /// Create Acknowledge-Job request. + /// + /// requestId + /// outputDeviceUuid + /// jobId + /// fetchStatusCode + /// fetchStatusMessage + /// representation + Task CreateAcknowledgeJobRequestAsync(int requestId, string outputDeviceUuid, int jobId, + StatusCode fetchStatusCode, string fetchStatusMessage); + + /// + /// Create IPP-INFRA Acknowledge-Document request. + /// + /// requestId + /// outputDeviceUuid + /// jobId + /// documentNumber + /// fetchStatusCode + /// fetchStatusMessage + /// representation + Task CreateAcknowledgeDocumentRequestAsync(int requestId, string outputDeviceUuid, int jobId, + int documentNumber, StatusCode fetchStatusCode, string fetchStatusMessage); + + /// + /// Create Fetch-Job request. + /// + /// requestId + /// outputDeviceUuid + /// jobId + /// representation + Task CreateFetchJobRequestAsync(int requestId, string outputDeviceUuid, int jobId); + + /// + /// Create Fetch-Document request. + /// + /// requestId + /// outputDeviceUuid + /// jobId + /// documentNumber + /// representation + Task CreateFetchDocumentRequestAsync(int requestId, string outputDeviceUuid, int jobId, + int documentNumber); + + /// + /// Create Update-Output-Device-Attribute request. + /// + /// requestId + /// outputDeviceUuid + /// optionalPrinterAttributes + /// representation + Task CreateUpdateOutputDeviceAttributesRequestAsync(int requestId, string outputDeviceUuid, + IppAttributeGroup optionalPrinterAttributes); + + /// + /// Create Update-Active-Jobs request. + /// + /// requestId + /// outputDeviceUuid + /// optionalOperationAttributes + /// representation + Task CreateUpdateActiveJobsRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalOperationAttributes); + + /// + /// Create Update-Document-Status request. + /// + /// representation + IppRequest CreateUpdateDocumentStatusRequest(); + + /// + /// Create Update-Job-Status request + /// + /// requestId + /// outputDeviceUuid + /// optionalJobAttributes + /// representation + Task CreateUpdateJobStatusRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalJobAttributes); + + /// + /// Creates Microsoft's custom Set-Print-Device-Capabilities request. + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The PDC stream. + /// representation + Task CreateSendPdcRequestAsync(int requestId, string outputDeviceUuid, Stream pdcData); + + /// + /// Creates Microsoft's custom Set-Print-Capabilities request. + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The PC stream. + /// representation + Task CreateSendPcRequestAsync(int requestId, string outputDeviceUuid, Stream pcData); + + /// + /// Creates a Subscriptions related request + /// + /// Request id of the IPP packet. + /// additional attributes + /// representation + Task CreateSubscriptionRelatedRequestAsync(Operation operation, int requestId, List optionalSubscriptionAttributeGroups); + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPAttribute.cs b/BadgeReleaseDemo/IppLibrary/IPPAttribute.cs new file mode 100644 index 0000000..c86aa04 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPAttribute.cs @@ -0,0 +1,1514 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using BadgeReleaseDemo.IppLibrary.Common; + + /// + /// Represents an IPP attribute (encoding documentation: https://tools.ietf.org/html/rfc8010#section-3.1.3). + /// + /// IppAttribute ::= AttributeName, Value { Value } + /// Value ::= SimpleIppValue + /// | CollectionIppAttributeValue + /// SimpleIppValue ::= Type, Value + /// CollectionIppAttributeValues ::= MemberAttribute { MemberAttribute } + /// MemberAttribute ::= AttributeName, Value { Value } + /// + /// + /// Each IppAttribute has the following members: + /// 1. attribute name. + /// 2. List of IppValues (One or more). + /// + /// An IppValue can be of two types: A simple IppAttribute Value, A CollectionIppAttributeValue. + /// + /// Simple IppAttribute value has the following members: + /// 1. Type + /// 2. Binary value. + /// + /// CollectionIppAtrrributeValue has the following members: + /// 1. List of member attributes. + /// + /// If an IppAttribute has simple values, it is considered a simple IppAttribute + /// If an IppAttribute has collectionIppAttributeValues, it is considered a Collection IppAttribute + /// + /// MemberAttribute: Each member attribute is similar to an IppAttribute. It can again be a simple IppAttribute or a CollectionIppAttribute. + /// Member attribute can have multiple values as well. + /// + /// Simple IPP attribute + /// | + /// + Attribute name + /// + Values[] + /// | + /// + type1 - value + /// + type2 - value + /// . + /// . + /// + /// Collection IPP attribute + /// | + /// | Attribute name + /// + Values[] + /// | + /// + -- Member attribute + /// | + member attribute value 1 + /// | + member attribute value 2 + /// | -- Member attribute + /// | + /// + -- Member attribute + /// | + member attribute value 1 + /// | + member attribute value 2 + /// | -- Member attribute + /// | + /// + -- Member attribute + /// | + member attribute value 1 + /// | + member attribute value 2 + /// | -- Member attribute + /// + /// + public class IppAttribute + { + /// + /// The values of the attribute. + /// + private readonly List values; + + /// + /// Initializes a new instance of the class with no values. + /// + public IppAttribute(string attributeName) + { + this.ValueName = attributeName; + this.values = new List(); + this.IsCollectionAttribute = false; + this.IsPii = this.IsPiiAttribute(); + } + + /// + /// Initializes a new instance of the class with a value. + /// + public IppAttribute(string attributeName, IppValue value) + : this(attributeName, new List { value }) + { + } + + /// + /// Initializes a new instance of the class with a value. + /// + public IppAttribute(string attributeName, List values) + : this(attributeName) + { + this.values = new List(values); + + // The values must be either all collection attributes or all regular values (thus a normal attribute). + int collectionValueCount = 0; + + foreach (var value in values) + { + if (value.IsCollectionAttributeValue()) + { + collectionValueCount++; + } + } + + if (collectionValueCount != 0 && collectionValueCount != values.Count) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"An IPP attribute values must be either all collection attributes or regular attributes.")); + } + + if (collectionValueCount != 0) + { + this.IsCollectionAttribute = true; + } + } + + /// + /// Gets or sets a value indicating whether this is a collection attribute. + /// + public bool IsCollectionAttribute { get; set; } = false; + + /// + /// Gets the name of the value, i.e., attribute name. + /// + public string ValueName { get; } + + /// + /// Gets the the first value. + /// + public IppValue FirstValue => this.values.Count != 0 ? this.values[0] : null; + + /// + /// Gets a value indicating whether there are additional values. + /// + public bool HasAdditionalValues => this.values.Count > 1; + + /// + /// Gets the values of the attribute. + /// + public IReadOnlyList Values => this.values.AsReadOnly(); + + /// + /// Gets a value indicating whether this is an empty attribute with no values and no collection attribute. + /// + private bool IsEmptyAttribute => this.values.Count == 0; + + /// + /// Gets a value indicating whether this attribute is considered a Pii attribute. + /// + private bool IsPii { get; } + + /// + /// Deserialize an IppAttribute. + /// + public static async Task DeserializeIppAttributeAsync(IPPInputStream input, CancellationToken cancellationToken) + { + var returnedTuple = await IppAttribute.DeserializeIppAttributeAsync(input, cancellationToken, Tag.Reserved, string.Empty); + return returnedTuple.Item1; + } + + /// + /// Deserialize an IppAttribute. + /// Return one attribute and the next attribute tag and name. + /// See: https://tools.ietf.org/html/rfc8010#section-3.1.6. + /// + public static async Task> DeserializeIppAttributeAsync( + IPPInputStream input, + CancellationToken cancellationToken, + Tag nextTag = Tag.Reserved, + string nextAttributeName = null) + { + Tag tag = nextTag == Tag.Reserved ? await input.ReadTagAsync(cancellationToken) : nextTag; + + string attributeName; + IppAttribute newAttribute = null; + + // Is there an attribute here? + if (!Helpers.IsValueTag(tag)) + { + // Next object is not an attribute. + return new Tuple(null, Tag.Reserved, string.Empty); + } + + // Parse the attribute name if nextAttributeName is not given. + if (string.IsNullOrEmpty(nextAttributeName)) + { + var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); + if (attributeNameLength <= 0) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, $"DeserializeIppAttributeAsync got name-length of {attributeNameLength} for first value of an attribute."); + } + + attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); + } + else + { + attributeName = nextAttributeName; + } + + // Handle collection attribute. + if (Helpers.IsBeginCollectionValueTag(tag)) + { + while (Helpers.IsBeginCollectionValueTag(tag)) + { + if (newAttribute == null) + { + // 2 bytes for value length. + var valueLength = await input.ReadNetworkShortAsync(cancellationToken); + + // The value-length of a collection attribute must be zero. + if (valueLength != 0) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"The value-length of a collection attribute must be zero, instead of {valueLength}")); + } + + var ippValue = await DeserializeCollectionAttributeValueAsync(input, attributeName, cancellationToken); + newAttribute = new IppAttribute(attributeName, ippValue); + } + else + { + // Expect either an additional value (the attribute name length is 0) or a new attribute (a new attribute name is parsed). + // If a new attribute name is parsed, quit the loop. Otherwise add the new value. + var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); + + if (attributeNameLength != 0) + { + attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); + + // Quit the loop, one full attribute is parsed. + break; + } + + // 2 bytes for value length. + var valueLength = await input.ReadNetworkShortAsync(cancellationToken); + + // The value-length of a collection attribute must be zero. + if (valueLength != 0) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"The value-length of a collection attribute must be zero, instead of {valueLength}")); + } + + // This is the case for 1setof CollectionAttribute. (Multi-valued collection attribute) + var additionalCollectionAttributeValue = await DeserializeCollectionAttributeValueAsync(input, string.Empty, cancellationToken); + newAttribute.AddAdditionalValue(additionalCollectionAttributeValue); + } + + attributeName = string.Empty; + tag = await input.ReadTagAsync(cancellationToken); + } + } + else + { + // Loop until 1 complete IppAttribute object is parsed. + while (Helpers.IsValueTag(tag)) + { + if (newAttribute == null) + { + // Parse attribute name. + if (string.IsNullOrEmpty(attributeName)) + { + var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); + if (attributeNameLength == 0) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "DeserializeIppAttributeAsync got name-length of 0 for first value of an attribute."); + } + + attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); + } + + newAttribute = new IppAttribute(attributeName); + attributeName = string.Empty; + } + else + { + // Expect either an additional value (the attribute name length is 0) or a new attribute (a new attribute name is parsed). + // If a new attribute name is parsed, quit the loop. Otherwise add the new value. + var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); + + if (attributeNameLength != 0) + { + attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); + + // Quit the loop, one full attribute is parsed. + break; + } + } + + // 2 bytes for value length. + var valueLength = await input.ReadNetworkShortAsync(cancellationToken); + + // Parse either attribute with one value or with additional value. + // https://tools.ietf.org/html/rfc8010#section-3.1.4. + var value = await input.ReadAsync(valueLength, cancellationToken); + + if (tag == Tag.TypeExtension) + { + // For extended types, the first 4 bytes of the value indicate the extended type as a big-endian signed integer. + // Ensure we read at least 4 bytes into the value. + if (valueLength < 4) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Expected at least 4 bytes in value data for extended type instead of {valueLength}.")); + } + + var extendedType = Helpers.ByteArrayToInteger(value); + tag = (Tag)extendedType; + } + + // An additional value for the attribue. + newAttribute.AddAdditionalValue(new IppValue(tag, value)); + + // Read the next delimiter. + tag = await input.ReadTagAsync(cancellationToken); + } + } + + // An attribute is formed. + var returnValue = new Tuple(newAttribute, tag, attributeName); + return returnValue; + } + + /// + /// Add additional value to the attribute. + /// + /// The value to add. + public void AddAdditionalValue(IppValue newValue) + { + // If first value, just add it. And determine whether this is a collection attribute. + if (this.IsEmptyAttribute) + { + this.values.Add(newValue); + + // If the value is a collection attribute value, then this is a collection attribute. + this.IsCollectionAttribute = newValue.IsCollectionAttributeValue(); + } + else + { + // Not first value. + // If adding a collection attribute value, this attribute must be a collection attribute. + // If adding a normal value, this attribute must not be a collection attribute. + if (newValue.IsCollectionAttributeValue() && !this.IsCollectionAttribute) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Collection attribute value cannot be added to a non collecton attribute {this.ValueName}.")); + } + else if (!newValue.IsCollectionAttributeValue() && this.IsCollectionAttribute) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Only collection attribute values can be added to a collecton attribute {this.ValueName}.")); + } + + this.values.Add(newValue); + } + } + + /// + /// Serialize this attribute. + /// + /// The output stream. + public void Serialize(Stream output) + { + // We write a single "attribute-with-one-value" field + // and then the "additional-value" fields if there are any + for (var i = 0; i < this.values.Count; i++) + { + if (i == 0) + { + this.values[i].Serialize(output, this.ValueName); + } + else + { + this.values[i].Serialize(output); + } + } + } + + /// + /// Serialize the attribute to string, value is masked if it's a Pii attribute. + /// + /// String representation of the attribute. + public override string ToString() + { + return this.ToString(false); + } + + /// + /// Non override version that allows Pii attributes to be included. + /// See IsPiiAttribute() for a list of attributes that are considered Pii. + /// + public string ToString(bool includePiiAttributes) + { + const string PiiMask = "*** PII masked ***"; + if (this.IsPii && !includePiiAttributes) + { + // Mask the value of Pii attributes. + return string.Format(CultureInfo.InvariantCulture, "Attribute {0}: {1}", this.ValueName, PiiMask); + } + + if (this.HasAdditionalValues) + { + var sb = new StringBuilder(); + + sb.AppendFormat(CultureInfo.InvariantCulture, "Attribute {0} - Multiple Values:\n", this.ValueName); + + foreach (var value in this.values) + { + sb.Append(value.ToString()); + } + + return sb.ToString(); + } + else + { + return string.Format(CultureInfo.InvariantCulture, "Attribute {0}: {1}", this.ValueName, this.FirstValue != null ? this.FirstValue.ToString() : string.Empty); + } + } + + /// + /// Determines if this attribute is handled by UP. Will be false for custom attributes. + /// + public bool IsUPHandledUpdateOutputDeviceAttribute() + { + switch (this.ValueName) + { + case PrinterAttributes.CharsetConfigured: + case PrinterAttributes.CharsetSupported: + case PrinterAttributes.ColorModeDefault: + case PrinterAttributes.ColorModeSupported: + case PrinterAttributes.ColorSupported: + case PrinterAttributes.CompressionSupported: + case PrinterAttributes.CopiesDefault: + case PrinterAttributes.CopiesSupported: + case PrinterAttributes.DocumentAccessSupported: + case PrinterAttributes.DocumentFormatDefault: + case PrinterAttributes.DocumentFormatDetailsSupported: + case PrinterAttributes.DocumentFormatPreferred: + case PrinterAttributes.DocumentFormatSupported: + case PrinterAttributes.DocumentPasswordSupported: + case PrinterAttributes.FeedOrientationDefault: + case PrinterAttributes.FeedOrientationSupported: + case PrinterAttributes.FinishingsColDatabase: + case PrinterAttributes.FinishingsColDefault: + case PrinterAttributes.FinishingsColReady: + case PrinterAttributes.FinishingsColSupported: + case PrinterAttributes.FinishingsDefault: + case PrinterAttributes.FinishingsSupported: + case PrinterAttributes.GeneratedNaturalLanguageSupported: + case PrinterAttributes.IppFeaturesSupported: + case PrinterAttributes.IppVersionsSupported: + case PrinterAttributes.JobConstraintsSupported: + case PrinterAttributes.JobCreationAttributesSupported: + case PrinterAttributes.JobIdsSupported: + case PrinterAttributes.JobResolversSupported: + case PrinterAttributes.JobAccountIdDefault: + case PrinterAttributes.JobAccountIdSupported: + case PrinterAttributes.JobAccountingUserIdDefault: + case PrinterAttributes.JobAccountingUserIdSupported: + case PrinterAttributes.JobMandatoryAttributesSupported: + case PrinterAttributes.JobPasswordSupported: + case PrinterAttributes.JobPasswordEncryptionSupported: + case PrinterAttributes.JobPasswordLengthSupported: + case PrinterAttributes.JobReleaseActionDefault: + case PrinterAttributes.JobReleaseActionSupported: + case PrinterAttributes.JobSheetsDefault: + case PrinterAttributes.JobSheetsSupported: + case PrinterAttributes.LandscapeOrientationRequestedPreferred: + case PrinterAttributes.MarginsPreAppliedDefault: + case PrinterAttributes.MarginsPreAppliedSupported: + case PrinterAttributes.MediaBottomMarginSupported: + case PrinterAttributes.MediaLeftMarginSupported: + case PrinterAttributes.MediaRightMarginSupported: + case PrinterAttributes.MediaTopMarginSupported: + case PrinterAttributes.MediaColDatabase: + case PrinterAttributes.MediaColDefault: + case PrinterAttributes.MediaColReady: + case PrinterAttributes.MediaColSupported: + case PrinterAttributes.MediaDefault: + case PrinterAttributes.MediaReady: + case PrinterAttributes.MediaSizeSupported: + case PrinterAttributes.MediaSourceSupported: + case PrinterAttributes.MediaSupported: + case PrinterAttributes.MediaTypeSupported: + case PrinterAttributes.MediaColorSupported: + case PrinterAttributes.MicrosoftPageOrderDefault: + case PrinterAttributes.MicrosoftPageOrderSupported: + case PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion: + case PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem: + case PrinterAttributes.MicrosoftUniversalPrintConnectorId: + case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: + case PrinterAttributes.MicrosoftUniversalPrinterDriverName: + case PrinterAttributes.MicrosoftUniversalPrinterDriverVersion: + case PrinterAttributes.MopriaCertified: + case PrinterAttributes.MultipleDocumentHandlingDefault: + case PrinterAttributes.MultipleDocumentHandlingSupported: + case PrinterAttributes.MultipleDocumentJobsSupported: + case PrinterAttributes.MultipleOperationTimeout: + case PrinterAttributes.MultipleOperationTimeoutAction: + case PrinterAttributes.NaturalLanguageConfigured: + case PrinterAttributes.NumberUpDefault: + case PrinterAttributes.NumberUpSupported: + case PrinterAttributes.OperationsSupported: + case PrinterAttributes.OrientationRequestedDefault: + case PrinterAttributes.OrientationRequestedSupported: + case PrinterAttributes.OutputBinDefault: + case PrinterAttributes.OutputBinSupported: + case PrinterAttributes.OverridesSupported: + case PrinterAttributes.PageRangesSupported: + case PrinterAttributes.PagesPerMinute: + case PrinterAttributes.PagesPerMinuteColor: + case PrinterAttributes.PclmRasterBackSide: + case PrinterAttributes.PclmSourceResolutionSupported: + case PrinterAttributes.PclmStripHeightPreferred: + case PrinterAttributes.PclmStripHeightSupported: + case PrinterAttributes.PdlOverrideSupported: + case PrinterAttributes.PdfFitToPageDefault: + case PrinterAttributes.PdfFitToPageSupported: + case PrinterAttributes.PdfSizeConstraints: + case PrinterAttributes.PdfKOctetsSupported: + case PrinterAttributes.PdfVersionsSupported: + case PrinterAttributes.PrintContentOptimizeDefault: + case PrinterAttributes.PrintContentOptimizeSupported: + case PrinterAttributes.PresentationDirectionNumberUpDefault: + case PrinterAttributes.PresentationDirectionNumberUpSupported: + case PrinterAttributes.PrintDeviceCapabilities: + case PrinterAttributes.PrintQualityDefault: + case PrinterAttributes.PrintQualitySupported: + case PrinterAttributes.PrintRenderingIntentDefault: + case PrinterAttributes.PrintRenderingIntentSupported: + case PrinterAttributes.PrintScalingDefault: + case PrinterAttributes.PrintScalingSupported: + case PrinterAttributes.PrintWFDS: + case PrinterAttributes.PrinterAlert: + case PrinterAttributes.PrinterAlertDescription: + case PrinterAttributes.PrinterConfigChangeDateTime: + case PrinterAttributes.PrinterConfigChangeTime: + case PrinterAttributes.PrinterCurrentTime: + case PrinterAttributes.PrinterDeviceId: + case PrinterAttributes.PrinterFirmwareName: + case PrinterAttributes.PrinterFirmwarePatches: + case PrinterAttributes.PrinterFirmwareStringVersion: + case PrinterAttributes.PrinterFirmwareVersion: + case PrinterAttributes.PrinterGeoLocation: + case PrinterAttributes.PrinterGetAttributesSupported: + case PrinterAttributes.PrinterIccProfiles: + case PrinterAttributes.PrinterIcons: + case PrinterAttributes.PrinterInfo: + case PrinterAttributes.PrinterInputTray: + case PrinterAttributes.PrinterIsAcceptingJobs: + case PrinterAttributes.PrinterKind: + case PrinterAttributes.PrinterLocation: + case PrinterAttributes.PrinterMakeAndModel: + case PrinterAttributes.PrinterMoreInfo: + case PrinterAttributes.PrinterMoreInfoManufacturer: + case PrinterAttributes.PrinterName: + case PrinterAttributes.PrinterOrganization: + case PrinterAttributes.PrinterOrganizationalUnit: + case PrinterAttributes.PrinterOutputTray: + case PrinterAttributes.PrinterResolutionDefault: + case PrinterAttributes.PrinterResolutionSupported: + case PrinterAttributes.PrinterStaticResourceDirectoryUri: + case PrinterAttributes.PrinterStaticResourceKOctetsFree: + case PrinterAttributes.PrinterStaticResourceKOctetsSupported: + case PrinterAttributes.PrinterState: + case PrinterAttributes.PrinterStateMessage: + case PrinterAttributes.PrinterStateReasons: + case PrinterAttributes.PrinterStateChangeDateTime: + case PrinterAttributes.PrinterStateChangeTime: + case PrinterAttributes.PrinterSupply: + case PrinterAttributes.PrinterSupplyDescription: + case PrinterAttributes.PrinterSupplyInfoUri: + case PrinterAttributes.PrinterUpTime: + case PrinterAttributes.PrinterUriSupported: + case PrinterAttributes.PrinterUuid: + case PrinterAttributes.PwgRasterDocumentResolutionSupported: + case PrinterAttributes.PwgRasterDocumentSheetBack: + case PrinterAttributes.PwgRasterDocumentTypeSupported: + case PrinterAttributes.QueuedJobCount: + case PrinterAttributes.SidesDefault: + case PrinterAttributes.SidesSupported: + case PrinterAttributes.UrfSupported: + case PrinterAttributes.UriAuthenticationSupported: + case PrinterAttributes.UriSecuritySupported: + case PrinterAttributes.WhichJobsSupported: + case PrinterAttributes.PullPrintEnabledWithOEMJobRelease: + return true; + + // CUPS IPP Attributes (For Mopria 2.0) + case PrinterAttributes.MarkerColors: + case PrinterAttributes.MarkerHighLevels: + case PrinterAttributes.MarkerLevels: + case PrinterAttributes.MarkerLowLevels: + case PrinterAttributes.MarkerNames: + case PrinterAttributes.MarkerTypes: + return true; + default: + return false; + } + } + + /// + /// Validates the syntax of each attribute value, as discussed in RFC 3196 Section 3.1.2.1.5. + /// + /// + /// The length of each value is correct for the client-supplied syntax tag. + /// The syntax tag of each value is correct for the attribute. + /// The value is in the range specified for the attribute. + /// Multiple values are present only if the attribute supports multiple values. + /// + /// true if valid, false otherwise + public bool IsValidAttribute() + { + IList validTags = this.GetAttributeTags(this.ValueName); + foreach (var value in this.Values) + { + if (!validTags.Contains(value.ValueType)) + { + return false; + } + } + + if (!this.SupportsMultipleValues() && this.HasAdditionalValues) + { + return false; + } + + return true; + } + + /// + /// Filters this attribute's values to only those that the IPP Service supports. + /// + /// + /// For example, if the attribute name is 'printer-state-reasons' and the values are + /// 'none-report' and 'UNSUPPORTED-REPORT', then only 'none-report' is returned. + /// + /// supported values + public List GetSupportedValues() + { + return this.Values.Where(v => this.SupportsValue(v)).ToList(); + } + + /// + /// Filters this attribute's values to only those that the IPP Service does not support. + /// + /// + /// For example, if the attribute name is 'printer-state-reasons' and the values are + /// 'none-report' and 'UNSUPPORTED-REPORT', then only 'UNSUPPORTED-REPORT' is returned. + /// + /// unsupported values + public List GetUnsupportedValues() + { + return this.Values.Where(v => !this.SupportsValue(v)).ToList(); + } + + /// + /// Compare two attributes. + /// + public override bool Equals(object obj) + { + if (obj is IppAttribute other) + { + var isAttributeNameEqual = this.ValueName.Equals(other.ValueName, StringComparison.Ordinal); + var areValuesEqual = this.values.SequenceEqual(other.values); + return isAttributeNameEqual && areValuesEqual; + } + + return false; + } + + /// + /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. + /// Attribute comparison is used by test code. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + /// Deserialize the input stream to a collection attribute. + /// + private static async Task DeserializeCollectionAttributeValueAsync(IPPInputStream input, string attributeName, CancellationToken cancellationToken, int collectionDepth = 0) + { + const int MaxCollectionDepthAllowed = 5; + + // Limit how many layers of collection of collection are allowed. + if (collectionDepth++ > MaxCollectionDepthAllowed) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Max collection depth of {MaxCollectionDepthAllowed} reached.")); + } + + var ippValue = IppValue.CreateCollectionAttributeValue(); + + IppMemberAttribute lastMemberAttribute = null; + + // Deserialize the member attributes for this collection attribute. + var tag = await input.ReadTagAsync(cancellationToken); + while (Helpers.IsMemberAttrNameTag(tag)) + { + // Parse 1 or more member attributes. https://tools.ietf.org/html/rfc8010#section-3.1.7 + // 2 bytes for name-length and it must be zero to signify that this is a member attribute. + var length = await input.ReadNetworkShortAsync(cancellationToken); + if (length != 0) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"The name-length of the member attribute must be zero, instead of {length}")); + } + + // 2 bytes for the value-length of the member-name. + length = await input.ReadNetworkShortAsync(cancellationToken); + var memberAttributeName = await input.ReadStringAsync(length, cancellationToken); + + // 1 byte for member-value tag. + var memberValueTag = await input.ReadTagAsync(cancellationToken); + + // 2 bytes for second name-length, value must be zero to signify it is a "member-attribute" contained in the collection. + length = await input.ReadNetworkShortAsync(cancellationToken); + if (length != 0) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"The second name-length of the member attribute must be zero, instead of {length}")); + } + + // 2 bytes for member-value-length, followed by the member-value. + length = await input.ReadNetworkShortAsync(cancellationToken); + IppValue memberIppValue = null; + + if (memberValueTag == Tag.BegCollection) + { + // A collection attribute. + if (length != 0) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"A member attribute that is a collection must have zero member-value-length instead of {length}.")); + } + + memberIppValue = await DeserializeCollectionAttributeValueAsync(input, memberAttributeName, cancellationToken, collectionDepth); + } + else + { + // A member attribute. Read the value and add it to the attribute. + var memberValue = await input.ReadAsync(length, cancellationToken); + memberIppValue = new IppValue(memberValueTag, memberValue); + } + + // If there is no member attribute name, then it is an additional value for a member attribute. + // This is the case for a member attribute with multiple values. + if (string.IsNullOrEmpty(memberAttributeName)) + { + if (lastMemberAttribute == null) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "A member attribute value should have a name or should be an additional value."); + } + + // Unnamed member attribute, hence adding this value to the last named member attribute. + lastMemberAttribute.AddAdditionalValue(memberIppValue); + } + else + { + // Named attribute value. Hence a new member attribute is being created. + lastMemberAttribute = new IppMemberAttribute(memberAttributeName, memberIppValue); + ippValue.AddMemberAttribute(lastMemberAttribute); + } + + // Read the next tag. + tag = await input.ReadTagAsync(cancellationToken); + } + + // Following the member attributes is the end collection tag. + if (tag != Tag.EndCollection) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Expect end-value-tag of a collection attribute. Received: {tag}")); + } + + // Followed by end-name-length field (2 bytes) and must be zero. + var endNameLength = await input.ReadNetworkShortAsync(cancellationToken); + if (endNameLength != 0) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Invalid value: {endNameLength}. The end-name-length of a collection attribute must be zero.")); + } + + // Followed by end-value-length field (2 bytes) and must be zero. + var endValueLength = await input.ReadNetworkShortAsync(cancellationToken); + if (endValueLength != 0) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Invalid value: {endValueLength}. The end-value-length of a collection attribute must be zero.")); + } + + return ippValue; + } + + /// + /// Returns the supported attribute syntax tags for the given attribute. + /// + /// The attribute + /// Returns supported atttribute syntax tags, or none if the attribute is unknown. + private List GetAttributeTags(string attributeName) + { + switch (attributeName) + { + // text + case PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion: + case PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem: + case PrinterAttributes.MicrosoftUniversalPrintConnectorId: + case PrinterAttributes.MicrosoftUniversalPrinterDriverName: + case PrinterAttributes.MicrosoftUniversalPrinterDriverVersion: + case PrinterAttributes.MopriaCertified: + case PrinterAttributes.PrinterAlertDescription: + case PrinterAttributes.PrinterInfo: + case PrinterAttributes.PrinterLocation: + case PrinterAttributes.PrinterMakeAndModel: + case PrinterAttributes.PrinterDeviceId: + case PrinterAttributes.PrinterStateMessage: + case PrinterAttributes.PrintDeviceCapabilities: + case PrinterAttributes.PrinterFirmwarePatches: + case PrinterAttributes.PrinterFirmwareStringVersion: + case PrinterAttributes.PrinterOrganization: + case PrinterAttributes.PrinterOrganizationalUnit: + case PrinterAttributes.PrinterSupplyDescription: + case JobAttributes.DetailedStatusMessage: + case JobAttributes.DocumentAccessError: + case JobAttributes.DocumentFormatVersionSupplied: + case JobAttributes.JobMessageFromOperator: + case JobAttributes.JobReleaseActionId: + case JobAttributes.JobStateMessage: + case JobAttributes.MediaInfo: + case JobAttributes.MicrosoftOutputDeviceJobStateMessage: + case JobAttributes.MicrosoftPrintTicketGenerationMappings: + case JobAttributes.OutputDeviceAssigned: + case JobAttributes.OutputDeviceJobStateMessage: + return new List { Tag.TextWithoutLanguage, Tag.TextWithLanguage }; + + // text without language + case PrinterAttributes.PrintWFDS: + return new List { Tag.TextWithoutLanguage }; + + // name + case PrinterAttributes.MarkerColors: + case PrinterAttributes.MarkerNames: + case PrinterAttributes.JobAccountIdDefault: + case PrinterAttributes.JobAccountingUserIdDefault: + case PrinterAttributes.PrinterFirmwareName: + case PrinterAttributes.PrinterName: + case JobAttributes.DocumentNameSupplied: + case JobAttributes.JobAccountId: + case JobAttributes.JobAccountingUserId: + case JobAttributes.JobName: + case JobAttributes.JobOriginatingUserName: + return new List { Tag.NameWithoutLanguage, Tag.NameWithLanguage }; + + // keyword + case PrinterAttributes.ColorModeDefault: + case PrinterAttributes.ColorModeSupported: + case PrinterAttributes.CompressionSupported: + case PrinterAttributes.DocumentAccessSupported: + case PrinterAttributes.DocumentFormatDetailsSupported: + case PrinterAttributes.FeedOrientationDefault: + case PrinterAttributes.FeedOrientationSupported: + case PrinterAttributes.FinishingsColSupported: + case PrinterAttributes.IppFeaturesSupported: + case PrinterAttributes.IppVersionsSupported: + case PrinterAttributes.JobCreationAttributesSupported: + case PrinterAttributes.MarkerTypes: + case PrinterAttributes.MediaColSupported: + case PrinterAttributes.MicrosoftPageOrderDefault: + case PrinterAttributes.MicrosoftPageOrderSupported: + case PrinterAttributes.MultipleDocumentHandlingDefault: + case PrinterAttributes.MultipleDocumentHandlingSupported: + case PrinterAttributes.MultipleOperationTimeoutAction: + case PrinterAttributes.OutputBinDefault: + case PrinterAttributes.OutputBinSupported: + case PrinterAttributes.OverridesSupported: + case PrinterAttributes.PclmRasterBackSide: + case PrinterAttributes.PdlOverrideSupported: + case PrinterAttributes.PdfVersionsSupported: + case PrinterAttributes.PresentationDirectionNumberUpDefault: + case PrinterAttributes.PresentationDirectionNumberUpSupported: + case PrinterAttributes.PrintScalingDefault: + case PrinterAttributes.PrintScalingSupported: + case PrinterAttributes.PrintContentOptimizeDefault: + case PrinterAttributes.PrintContentOptimizeSupported: + case PrinterAttributes.PrintRenderingIntentDefault: + case PrinterAttributes.PrintRenderingIntentSupported: + case PrinterAttributes.PrinterGetAttributesSupported: + case PrinterAttributes.PrinterKind: + case PrinterAttributes.PrinterStateReasons: + case PrinterAttributes.PwgRasterDocumentSheetBack: + case PrinterAttributes.PwgRasterDocumentTypeSupported: + case PrinterAttributes.JobReleaseActionDefault: + case PrinterAttributes.JobReleaseActionSupported: + case PrinterAttributes.SidesSupported: + case PrinterAttributes.SidesDefault: + case PrinterAttributes.UrfSupported: + case PrinterAttributes.UriSecuritySupported: + case PrinterAttributes.UriAuthenticationSupported: + case PrinterAttributes.WhichJobsSupported: + case JobAttributes.CompressionSupplied: + case JobAttributes.CoverType: + case JobAttributes.FeedOrientation: + case JobAttributes.JobMandatoryAttributes: + case JobAttributes.JobReleaseAction: + case JobAttributes.JobReleaseActionActual: + case JobAttributes.JobStateReasons: + case JobAttributes.MediaType: + case JobAttributes.OrientationRequested: + case JobAttributes.OutputBin: + case JobAttributes.OutputDeviceJobStateReasons: + case JobAttributes.PageOrderReceived: + case JobAttributes.PresentationDirectionNumberUp: + case JobAttributes.PrintColorMode: + case JobAttributes.PrintColorModeActual: + case JobAttributes.PrintScaling: + case JobAttributes.PrintContentOptimize: + case JobAttributes.PrintRenderingIntent: + case JobAttributes.Sides: + case JobAttributes.SidesActual: + return new List { Tag.Keyword }; + + // enum + case PrinterAttributes.FinishingsDefault: + case PrinterAttributes.FinishingsSupported: + case PrinterAttributes.LandscapeOrientationRequestedPreferred: + case PrinterAttributes.OperationsSupported: + case PrinterAttributes.OrientationRequestedSupported: + case PrinterAttributes.PrintQualityDefault: + case PrinterAttributes.PrintQualitySupported: + case PrinterAttributes.PrinterState: + case JobAttributes.Finishings: + case JobAttributes.JobState: + case JobAttributes.OutputDeviceJobState: + case JobAttributes.OutputDeviceJobStates: + case JobAttributes.PrinterQuality: + case JobAttributes.PrintQuality: + return new List { Tag.Enum }; + + // uri + case PrinterAttributes.PrinterIcons: + case PrinterAttributes.PrinterMoreInfo: + case PrinterAttributes.PrinterMoreInfoManufacturer: + case PrinterAttributes.PrinterStaticResourceDirectoryUri: + case PrinterAttributes.PrinterSupplyInfoUri: + case PrinterAttributes.PrinterUriSupported: + case PrinterAttributes.PrinterUuid: + case JobAttributes.JobMoreInfo: + case JobAttributes.JobOriginatingUserUri: + case JobAttributes.JobPrinterUri: + case JobAttributes.JobUuid: + case JobAttributes.JobUri: + return new List { Tag.Uri }; + + // charset + case PrinterAttributes.CharsetConfigured: + case PrinterAttributes.CharsetSupported: + return new List { Tag.Charset }; + + // naturalLanguage + case PrinterAttributes.GeneratedNaturalLanguageSupported: + case PrinterAttributes.NaturalLanguageConfigured: + return new List { Tag.NaturalLanguage }; + + // mimeMediaType + case PrinterAttributes.DocumentFormatDefault: + case PrinterAttributes.DocumentFormatSupported: + case PrinterAttributes.DocumentFormatPreferred: + case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: + case JobAttributes.DocumentFormatSupplied: + return new List { Tag.MimeMediaType }; + + // octetString + case PrinterAttributes.PrinterAlert: + case PrinterAttributes.PrinterFirmwareVersion: + case PrinterAttributes.PrinterInputTray: + case PrinterAttributes.PrinterOutputTray: + case PrinterAttributes.PrinterSupply: + return new List { Tag.OctetString }; + + // boolean + case PrinterAttributes.ColorSupported: + case PrinterAttributes.JobIdsSupported: + case PrinterAttributes.JobAccountIdSupported: + case PrinterAttributes.JobAccountingUserIdSupported: + case PrinterAttributes.JobMandatoryAttributesSupported: + case PrinterAttributes.MarginsPreAppliedDefault: + case PrinterAttributes.MarginsPreAppliedSupported: + case PrinterAttributes.MultipleDocumentJobsSupported: + case PrinterAttributes.PageRangesSupported: + case PrinterAttributes.PdfFitToPageDefault: + case PrinterAttributes.PdfFitToPageSupported: + case PrinterAttributes.PrinterIsAcceptingJobs: + case JobAttributes.IppAttributeFidelity: + case PrinterAttributes.PullPrintEnabledWithOEMJobRelease: + return new List { Tag.Boolean }; + + // integer + case PrinterAttributes.CopiesDefault: + case PrinterAttributes.DocumentPasswordSupported: + case PrinterAttributes.IppgetEventLife: + case PrinterAttributes.MediaBottomMarginSupported: + case PrinterAttributes.MediaLeftMarginSupported: + case PrinterAttributes.MediaRightMarginSupported: + case PrinterAttributes.MediaTopMarginSupported: + case PrinterAttributes.MultipleOperationTimeout: + case PrinterAttributes.JobPasswordSupported: + case PrinterAttributes.MarkerHighLevels: + case PrinterAttributes.MarkerLevels: + case PrinterAttributes.MarkerLowLevels: + case PrinterAttributes.NumberUpDefault: + case PrinterAttributes.PagesPerMinute: + case PrinterAttributes.PagesPerMinuteColor: + case PrinterAttributes.PclmStripHeightPreferred: + case PrinterAttributes.PclmStripHeightSupported: + case PrinterAttributes.PdfSizeConstraints: + case PrinterAttributes.PrinterConfigChangeTime: + case PrinterAttributes.PrinterStateChangeTime: + case PrinterAttributes.PrinterStaticResourceKOctetsFree: + case PrinterAttributes.PrinterStaticResourceKOctetsSupported: + case PrinterAttributes.PrinterUpTime: + case PrinterAttributes.QueuedJobCount: + case JobAttributes.Copies: + case JobAttributes.CopiesActual: + case JobAttributes.JobId: + case JobAttributes.JobImpressions: + case JobAttributes.JobImpressionsCompleted: + case JobAttributes.JobKOctets: + case JobAttributes.JobKOctetsCompleted: + case JobAttributes.JobKOctetsProcessed: + case JobAttributes.JobMediaSheets: + case JobAttributes.JobMediaSheetsCompleted: + case JobAttributes.JobPriority: + case JobAttributes.JobPrinterUpTime: + case JobAttributes.MediaBottomMargin: + case JobAttributes.MediaHoleCount: + case JobAttributes.MediaLeftMargin: + case JobAttributes.MediaOrderCount: + case JobAttributes.MediaThickness: + case JobAttributes.MediaTopMargin: + case JobAttributes.MediaWeightMetric: + case JobAttributes.MicrosoftJobFetchedTimeInSeconds: + case JobAttributes.MicrosoftJobPrintedTimeInSeconds: + case JobAttributes.MicrosoftJobProcessedTimeInSeconds: + case JobAttributes.MicrosoftJobSpoolerTimeInSeconds: + case JobAttributes.MicrosoftJobPrinterTimeInSeconds: + case JobAttributes.MicrosoftPdfToXpsJobConversionTimeInMilliseconds: + case JobAttributes.MicrosoftPrintTicketGenerationTimeInMilliseconds: + case JobAttributes.NumberOfDocuments: + case JobAttributes.NumberOfInterveningJobs: + case JobAttributes.NumberUp: + case JobAttributes.JobPagesCompleted: + case JobAttributes.TimeAtCreation: + case JobAttributes.XDimension: + case JobAttributes.YDimension: + return new List { Tag.Integer }; + + // integer | no-value + case JobAttributes.TimeAtCompleted: + case JobAttributes.TimeAtProcessing: + return new List { Tag.Integer, Tag.NoValue }; + + // rangeOfInteger + case PrinterAttributes.CopiesSupported: + case PrinterAttributes.PdfKOctetsSupported: + case PrinterAttributes.JobPasswordLengthSupported: + case JobAttributes.PageRanges: + return new List { Tag.RangeOfInteger }; + + // resolution + case PrinterAttributes.PclmSourceResolutionSupported: + case PrinterAttributes.PrinterResolutionDefault: + case PrinterAttributes.PrinterResolutionSupported: + case PrinterAttributes.PwgRasterDocumentResolutionSupported: + case JobAttributes.PrinterResolution: + return new List { Tag.Resolution }; + + // collection + case PrinterAttributes.FinishingsColDatabase: + case PrinterAttributes.FinishingsColReady: + case PrinterAttributes.JobConstraintsSupported: + case PrinterAttributes.JobResolversSupported: + case PrinterAttributes.MediaColDatabase: + case PrinterAttributes.MediaColDefault: + case PrinterAttributes.MediaSizeSupported: + case PrinterAttributes.PrinterIccProfiles: + case JobAttributes.MediaCol: + case JobAttributes.Overrides: + return new List { Tag.BegCollection, Tag.EndCollection }; + + // datetime + case PrinterAttributes.PrinterConfigChangeDateTime: + case PrinterAttributes.PrinterStateChangeDateTime: + case JobAttributes.DateTimeAtCreation: + return new List { Tag.DateTime }; + + // datetime | no-value + case JobAttributes.DateTimeAtCompleted: + case JobAttributes.DateTimeAtProcessing: + return new List { Tag.DateTime, Tag.NoValue }; + + // datetime | unknown + case PrinterAttributes.PrinterCurrentTime: + return new List { Tag.DateTime, Tag.Unknown }; + + // collection | no-value + case PrinterAttributes.FinishingsColDefault: + case JobAttributes.FinishingsCol: + case PrinterAttributes.MediaColReady: + return new List { Tag.BegCollection, Tag.EndCollection, Tag.NoValue }; + + // collection | integer + case JobAttributes.MediaSize: + return new List { Tag.BegCollection, Tag.EndCollection, Tag.Integer }; + + // no-value | enum + case PrinterAttributes.OrientationRequestedDefault: + return new List { Tag.NoValue, Tag.Enum }; + + // keyword | name + case PrinterAttributes.JobPasswordEncryptionSupported: + case PrinterAttributes.JobSheetsDefault: + case PrinterAttributes.JobSheetsSupported: + case PrinterAttributes.MediaSupported: + case PrinterAttributes.MediaSourceSupported: + case PrinterAttributes.MediaTypeSupported: + case PrinterAttributes.MediaColorSupported: + case JobAttributes.JobHoldUntil: + case JobAttributes.JobSheets: + case JobAttributes.Media: + case JobAttributes.MediaBackCoating: + case JobAttributes.MediaColor: + case JobAttributes.MediaGrain: + case JobAttributes.MediaFrontCoating: + case JobAttributes.MediaKey: + case JobAttributes.MediaPreprinted: + case JobAttributes.MediaRecycled: + case JobAttributes.MediaRightMargin: + case JobAttributes.MediaSizeName: + case JobAttributes.MediaSource: + case JobAttributes.MediaTooth: + case JobAttributes.MultipleDocumentHandling: + return new List { Tag.Keyword, Tag.NameWithoutLanguage, Tag.NameWithLanguage }; + + // integer | rangeOfInteger + case PrinterAttributes.NumberUpSupported: + return new List { Tag.Integer, Tag.RangeOfInteger }; + + // no-value | keyword | name + case PrinterAttributes.MediaDefault: + case PrinterAttributes.MediaReady: + return new List { Tag.NoValue, Tag.Keyword, Tag.NameWithoutLanguage, Tag.NameWithLanguage }; + + // uri | unknown + case PrinterAttributes.PrinterGeoLocation: + return new List { Tag.Uri, Tag.Unknown }; + + default: + return new List { }; + } + } + + private bool SupportsMultipleValues() + { + switch (this.ValueName) + { + case PrinterAttributes.CompressionSupported: + case PrinterAttributes.ColorModeSupported: + case PrinterAttributes.PrinterStateReasons: + case PrinterAttributes.CharsetSupported: + case PrinterAttributes.DocumentAccessSupported: + case PrinterAttributes.DocumentFormatDetailsSupported: + case PrinterAttributes.DocumentFormatSupported: + case PrinterAttributes.FeedOrientationSupported: + case PrinterAttributes.FinishingsColDatabase: + case PrinterAttributes.FinishingsColDefault: + case PrinterAttributes.FinishingsColReady: + case PrinterAttributes.FinishingsColSupported: + case PrinterAttributes.FinishingsDefault: + case PrinterAttributes.FinishingsSupported: + case PrinterAttributes.GeneratedNaturalLanguageSupported: + case PrinterAttributes.IppFeaturesSupported: + case PrinterAttributes.IppVersionsSupported: + case PrinterAttributes.JobConstraintsSupported: + case PrinterAttributes.JobCreationAttributesSupported: + case PrinterAttributes.JobResolversSupported: + case PrinterAttributes.JobPasswordEncryptionSupported: + case PrinterAttributes.JobReleaseActionSupported: + case PrinterAttributes.JobSheetsSupported: + case PrinterAttributes.MarkerColors: + case PrinterAttributes.MarkerHighLevels: + case PrinterAttributes.MarkerLevels: + case PrinterAttributes.MarkerLowLevels: + case PrinterAttributes.MarkerNames: + case PrinterAttributes.MarkerTypes: + case PrinterAttributes.MediaColDatabase: + case PrinterAttributes.MediaColReady: + case PrinterAttributes.MediaColSupported: + case PrinterAttributes.MediaColorSupported: + case PrinterAttributes.MediaBottomMarginSupported: + case PrinterAttributes.MediaLeftMarginSupported: + case PrinterAttributes.MediaRightMarginSupported: + case PrinterAttributes.MediaTopMarginSupported: + case PrinterAttributes.MediaReady: + case PrinterAttributes.MediaSupported: + case PrinterAttributes.MediaSizeSupported: + case PrinterAttributes.MediaSourceSupported: + case PrinterAttributes.MediaTypeSupported: + case PrinterAttributes.MicrosoftPageOrderSupported: + case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: + case PrinterAttributes.MultipleDocumentHandlingSupported: + case PrinterAttributes.NumberUpSupported: + case PrinterAttributes.OperationsSupported: + case PrinterAttributes.OrientationRequestedSupported: + case PrinterAttributes.OutputBinSupported: + case PrinterAttributes.OverridesSupported: + case PrinterAttributes.PclmSourceResolutionSupported: + case PrinterAttributes.PclmStripHeightSupported: + case PrinterAttributes.PclmStripHeightPreferred: + case PrinterAttributes.PresentationDirectionNumberUpSupported: + case PrinterAttributes.PdfVersionsSupported: + case PrinterAttributes.PrintContentOptimizeSupported: + case PrinterAttributes.PrintQualitySupported: + case PrinterAttributes.PrintRenderingIntentSupported: + case PrinterAttributes.PrintScalingSupported: + case PrinterAttributes.PrinterAlert: + case PrinterAttributes.PrinterAlertDescription: + case PrinterAttributes.PrinterFirmwareName: + case PrinterAttributes.PrinterFirmwareStringVersion: + case PrinterAttributes.PrinterFirmwarePatches: + case PrinterAttributes.PrinterFirmwareVersion: + case PrinterAttributes.PrinterGetAttributesSupported: + case PrinterAttributes.PrinterIccProfiles: + case PrinterAttributes.PrinterIcons: + case PrinterAttributes.PrinterInputTray: + case PrinterAttributes.PrinterKind: + case PrinterAttributes.PrinterOrganization: + case PrinterAttributes.PrinterOrganizationalUnit: + case PrinterAttributes.PrinterOutputTray: + case PrinterAttributes.PrinterResolutionSupported: + case PrinterAttributes.PrinterSupply: + case PrinterAttributes.PrinterSupplyDescription: + case PrinterAttributes.PrinterUriSupported: + case PrinterAttributes.PwgRasterDocumentResolutionSupported: + case PrinterAttributes.PwgRasterDocumentTypeSupported: + case PrinterAttributes.SidesSupported: + case PrinterAttributes.UrfSupported: + case PrinterAttributes.UriAuthenticationSupported: + case PrinterAttributes.UriSecuritySupported: + case PrinterAttributes.WhichJobsSupported: + case JobAttributes.Finishings: + case JobAttributes.FinishingsCol: + case JobAttributes.JobStateReasons: + case JobAttributes.MicrosoftPrintTicketGenerationMappings: + case JobAttributes.OutputDeviceJobStateReasons: + case JobAttributes.OutputDeviceJobStates: + case JobAttributes.Overrides: + case JobAttributes.PageRanges: + return true; + + default: + return false; + } + } + + private bool SupportsValue(IppValue value) + { + switch (this.ValueName) + { + case PrinterAttributes.PrinterState: + { + var printerState = (PrinterState)value.GetNativeValue(); + return Enum.IsDefined(typeof(PrinterState), printerState); + } + + case PrinterAttributes.PrinterStateReasons: + case PrinterAttributes.PrinterMakeAndModel: + case PrinterAttributes.PrinterMoreInfoManufacturer: + { + var textValue = value.GetNativeValue(); + return !string.IsNullOrEmpty(textValue); + } + + case PrinterAttributes.IppgetEventLife: + { + var intValue = value.GetNativeValue(); + return intValue >= 15; + } + + case PrinterAttributes.CharsetConfigured: + case PrinterAttributes.CharsetSupported: + case PrinterAttributes.ColorModeDefault: + case PrinterAttributes.ColorModeSupported: + case PrinterAttributes.ColorSupported: + case PrinterAttributes.CompressionSupported: + case PrinterAttributes.CopiesDefault: + case PrinterAttributes.CopiesSupported: + case PrinterAttributes.DocumentAccessSupported: + case PrinterAttributes.DocumentFormatDefault: + case PrinterAttributes.DocumentFormatDetailsSupported: + case PrinterAttributes.DocumentFormatPreferred: + case PrinterAttributes.DocumentFormatSupported: + case PrinterAttributes.DocumentPasswordSupported: + case PrinterAttributes.FeedOrientationDefault: + case PrinterAttributes.FeedOrientationSupported: + case PrinterAttributes.FinishingsColDatabase: + case PrinterAttributes.FinishingsColDefault: + case PrinterAttributes.FinishingsColReady: + case PrinterAttributes.FinishingsColSupported: + case PrinterAttributes.FinishingsDefault: + case PrinterAttributes.FinishingsSupported: + case PrinterAttributes.GeneratedNaturalLanguageSupported: + case PrinterAttributes.IppFeaturesSupported: + case PrinterAttributes.IppVersionsSupported: + case PrinterAttributes.JobConstraintsSupported: + case PrinterAttributes.JobCreationAttributesSupported: + case PrinterAttributes.JobIdsSupported: + case PrinterAttributes.JobResolversSupported: + case PrinterAttributes.JobAccountIdDefault: + case PrinterAttributes.JobAccountIdSupported: + case PrinterAttributes.JobAccountingUserIdDefault: + case PrinterAttributes.JobAccountingUserIdSupported: + case PrinterAttributes.JobMandatoryAttributesSupported: + case PrinterAttributes.JobPasswordSupported: + case PrinterAttributes.JobPasswordEncryptionSupported: + case PrinterAttributes.JobPasswordLengthSupported: + case PrinterAttributes.JobReleaseActionDefault: + case PrinterAttributes.JobReleaseActionSupported: + case PrinterAttributes.JobSheetsDefault: + case PrinterAttributes.JobSheetsSupported: + case PrinterAttributes.LandscapeOrientationRequestedPreferred: + case PrinterAttributes.MarginsPreAppliedDefault: + case PrinterAttributes.MarginsPreAppliedSupported: + case PrinterAttributes.MarkerColors: + case PrinterAttributes.MarkerHighLevels: + case PrinterAttributes.MarkerLevels: + case PrinterAttributes.MarkerLowLevels: + case PrinterAttributes.MarkerNames: + case PrinterAttributes.MarkerTypes: + case PrinterAttributes.MediaColDatabase: + case PrinterAttributes.MediaColDefault: + case PrinterAttributes.MediaColReady: + case PrinterAttributes.MediaColSupported: + case PrinterAttributes.MediaBottomMarginSupported: + case PrinterAttributes.MediaLeftMarginSupported: + case PrinterAttributes.MediaRightMarginSupported: + case PrinterAttributes.MediaTopMarginSupported: + case PrinterAttributes.MediaDefault: + case PrinterAttributes.MediaReady: + case PrinterAttributes.MediaSizeSupported: + case PrinterAttributes.MediaSourceSupported: + case PrinterAttributes.MediaTypeSupported: + case PrinterAttributes.MediaColorSupported: + case PrinterAttributes.MediaSupported: + case PrinterAttributes.MicrosoftPageOrderDefault: + case PrinterAttributes.MicrosoftPageOrderSupported: + case PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion: + case PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem: + case PrinterAttributes.MicrosoftUniversalPrintConnectorId: + case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: + case PrinterAttributes.MicrosoftUniversalPrinterDriverName: + case PrinterAttributes.MicrosoftUniversalPrinterDriverVersion: + case PrinterAttributes.MopriaCertified: + case PrinterAttributes.MultipleDocumentHandlingDefault: + case PrinterAttributes.MultipleDocumentHandlingSupported: + case PrinterAttributes.MultipleDocumentJobsSupported: + case PrinterAttributes.MultipleOperationTimeout: + case PrinterAttributes.MultipleOperationTimeoutAction: + case PrinterAttributes.NaturalLanguageConfigured: + case PrinterAttributes.NumberUpDefault: + case PrinterAttributes.NumberUpSupported: + case PrinterAttributes.OperationsSupported: + case PrinterAttributes.OrientationRequestedDefault: + case PrinterAttributes.OrientationRequestedSupported: + case PrinterAttributes.OutputBinDefault: + case PrinterAttributes.OutputBinSupported: + case PrinterAttributes.OverridesSupported: + case PrinterAttributes.PageRangesSupported: + case PrinterAttributes.PagesPerMinute: + case PrinterAttributes.PagesPerMinuteColor: + case PrinterAttributes.PclmRasterBackSide: + case PrinterAttributes.PclmSourceResolutionSupported: + case PrinterAttributes.PclmStripHeightPreferred: + case PrinterAttributes.PclmStripHeightSupported: + case PrinterAttributes.PdlOverrideSupported: + case PrinterAttributes.PrintContentOptimizeDefault: + case PrinterAttributes.PrintContentOptimizeSupported: + case PrinterAttributes.PrintRenderingIntentDefault: + case PrinterAttributes.PrintRenderingIntentSupported: + case PrinterAttributes.PrintQualityDefault: + case PrinterAttributes.PrintScalingDefault: + case PrinterAttributes.PrintScalingSupported: + case PrinterAttributes.PdfFitToPageDefault: + case PrinterAttributes.PdfFitToPageSupported: + case PrinterAttributes.PdfKOctetsSupported: + case PrinterAttributes.PdfSizeConstraints: + case PrinterAttributes.PdfVersionsSupported: + case PrinterAttributes.PresentationDirectionNumberUpDefault: + case PrinterAttributes.PresentationDirectionNumberUpSupported: + case PrinterAttributes.PrintQualitySupported: + case PrinterAttributes.PrintWFDS: + case PrinterAttributes.PrinterAlert: + case PrinterAttributes.PrinterAlertDescription: + case PrinterAttributes.PrinterConfigChangeDateTime: + case PrinterAttributes.PrinterConfigChangeTime: + case PrinterAttributes.PrinterCurrentTime: + case PrinterAttributes.PrinterDeviceId: + case PrinterAttributes.PrinterFirmwareName: + case PrinterAttributes.PrinterFirmwarePatches: + case PrinterAttributes.PrinterFirmwareStringVersion: + case PrinterAttributes.PrinterFirmwareVersion: + case PrinterAttributes.PrinterGeoLocation: + case PrinterAttributes.PrinterGetAttributesSupported: + case PrinterAttributes.PrinterIccProfiles: + case PrinterAttributes.PrinterIcons: + case PrinterAttributes.PrinterInfo: + case PrinterAttributes.PrinterInputTray: + case PrinterAttributes.PrinterIsAcceptingJobs: + case PrinterAttributes.PrinterKind: + case PrinterAttributes.PrinterLocation: + case PrinterAttributes.PrinterMoreInfo: + case PrinterAttributes.PrinterName: + case PrinterAttributes.PrinterOrganization: + case PrinterAttributes.PrinterOrganizationalUnit: + case PrinterAttributes.PrinterOutputTray: + case PrinterAttributes.PrinterResolutionDefault: + case PrinterAttributes.PrinterResolutionSupported: + case PrinterAttributes.PrinterStateMessage: + case PrinterAttributes.PrinterStateChangeDateTime: + case PrinterAttributes.PrinterStateChangeTime: + case PrinterAttributes.PrinterStaticResourceDirectoryUri: + case PrinterAttributes.PrinterStaticResourceKOctetsFree: + case PrinterAttributes.PrinterStaticResourceKOctetsSupported: + case PrinterAttributes.PrinterSupply: + case PrinterAttributes.PrinterSupplyDescription: + case PrinterAttributes.PrinterSupplyInfoUri: + case PrinterAttributes.PrinterUpTime: + case PrinterAttributes.PrinterUriSupported: + case PrinterAttributes.PrinterUuid: + case PrinterAttributes.PwgRasterDocumentResolutionSupported: + case PrinterAttributes.PwgRasterDocumentSheetBack: + case PrinterAttributes.PwgRasterDocumentTypeSupported: + case PrinterAttributes.QueuedJobCount: + case PrinterAttributes.SidesDefault: + case PrinterAttributes.SidesSupported: + case PrinterAttributes.UrfSupported: + case PrinterAttributes.UriAuthenticationSupported: + case PrinterAttributes.UriSecuritySupported: + case PrinterAttributes.WhichJobsSupported: + case PrinterAttributes.PullPrintEnabledWithOEMJobRelease: + return true; + + default: + return false; + } + } + + /// + /// IPP attributes that are considered Pii. + /// + private bool IsPiiAttribute() + { + switch (this.ValueName) + { + case OperationAttributes.RequestingUserName: + case OperationAttributes.RequestingUserUri: + case OperationAttributes.DocumentName: + case OperationAttributes.JobPassword: + case JobAttributes.JobOriginatingUserName: + case JobAttributes.JobOriginatingUserUri: + case JobAttributes.JobName: + return true; + + default: + return false; + } + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs b/BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs new file mode 100644 index 0000000..22a110b --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs @@ -0,0 +1,187 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using BadgeReleaseDemo.IppLibrary.Common; + + public class IppAttributeGroup + { + private const string LexmarkStringForDuplicateAttributeException = "Lexmark"; + + /// + /// Contains 0 or more IPPAttributes associated with this Attribute Group + /// TBD: consider ConcurrentDictionary() remove the need to create new APIs for Add, Remove, and Replace. [Tracked by: 19959905] + /// + private Dictionary attributes; + + /// + /// Initializes a new instance of the class. + /// + /// The attribute type of the group. + public IppAttributeGroup(Tag attributeType) + { + this.Type = attributeType; + this.attributes = new Dictionary(); + } + + /// + /// Gets or sets the type of this Attribute Group + /// + public Tag Type { get; set; } + + /// + /// Gets the attributes of the group. + /// + public IReadOnlyDictionary Attributes => (IReadOnlyDictionary)this.attributes; + + /// + /// Add an attribute to the group. + /// + /// The attribute to add. + public void AddAttribute(IppAttribute attribute) + { + // Sanity check: duplicate attributes are not allowed (we're allowed to ignore them + // or to return an error, I'm opting for an error..) + if (this.attributes.ContainsKey(attribute.ValueName)) + { + if (attribute.ValueName.Contains(LexmarkStringForDuplicateAttributeException)) + { + // skip attribute for Lexmark native printers due to bug in their firmware + // IcM: https://portal.microsofticm.com/imp/v3/incidents/details/264596539/home + return; + } + + throw new IPPException(StatusCode.ClientErrorBadRequest, "Duplicate attribute name in IPP message:" + attribute.ValueName); + } + + this.attributes.Add(attribute.ValueName, attribute); + } + + /// + /// Remove an attribute from attribute list. + /// + /// The name of the attribute to be removed. + public void RemoveAttribute(string attributeName) + { + if (this.attributes.ContainsKey(attributeName)) + { + this.attributes.Remove(attributeName); + } + } + + /// + /// Replace an attribute. + /// + /// The new attribute object. + public void ReplaceAttribute(IppAttribute attribute) + { + this.RemoveAttribute(attribute.ValueName); + this.AddAttribute(attribute); + } + + /// + /// Serialize the attribute group. + /// + /// The output stream. + public void Serialize(Stream output) + { + // Write the "begin-attribute-group" tag + output.WriteByte((byte)this.Type); + + // And write out the attributes. + // The requirement for Tag.OperationAttributes is to serialize the AttributesCharset and AttributesNaturalLanguage first. + // From RFC 8081: https://tools.ietf.org/html/rfc8011#page-33 + // The "attributes-charset" and "attributes-natural-language" attributes + // MUST be the first two attributes in every IPP request and response, + // as part of the initial Operation Attributes group of the IPP message. + // The "attributes-charset" attribute MUST be the first attribute in the + // group, and the "attributes-natural-language" attribute MUST be the + // second attribute in the group. + if (this.Type == Tag.OperationAttributes) + { + if (this.attributes.TryGetValue(OperationAttributes.AttributesCharset, out IppAttribute charsetValue)) + { + charsetValue.Serialize(output); + } + + if (this.attributes.TryGetValue(OperationAttributes.AttributesNaturalLanguage, out IppAttribute langValue)) + { + langValue.Serialize(output); + } + } + + // The rest of the attributes. + foreach (var attribute in this.attributes.Values) + { + // Except for the AttributesCharset and AttributesNaturalLanguage attributes that are serialized first. + if (string.CompareOrdinal(attribute.ValueName, OperationAttributes.AttributesCharset) == 0 || + string.CompareOrdinal(attribute.ValueName, OperationAttributes.AttributesNaturalLanguage) == 0) + { + continue; + } + + attribute.Serialize(output); + } + } + + /// + /// Serialize to string without IPP attributes that are considered Pii. + /// + /// The string representation of the attribute group. + public override string ToString() + { + // The default is to exclude Pii attributes. This prevents user of this library to accidentally log Pii information. + return this.ToString(false); + } + + /// + /// Serialize to string with option to include IPP attributes that are considered Pii. + /// + public string ToString(bool includePiiAttributes) + { + var sb = new StringBuilder(); + sb.AppendFormat(CultureInfo.InvariantCulture, "Attribute Group: {0}\n", this.Type); + + foreach (var attribute in this.attributes.Values) + { + sb.AppendFormat(CultureInfo.InvariantCulture, " {0}\n", attribute.ToString(includePiiAttributes)); + } + + return sb.ToString(); + } + + /// + /// Compare two attribute groups. + /// + public override bool Equals(object obj) + { + if (obj is IppAttributeGroup other) + { + var isAttributeGroupTypeEqual = this.Type.Equals(other.Type); + var areAttributesEqual = this.attributes.ToList().SequenceEqual(other.attributes.ToList()); + return isAttributeGroupTypeEqual && areAttributesEqual; + } + + return false; + } + + /// + /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. + /// Attribute group comparison is used by test code. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs b/BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs new file mode 100644 index 0000000..ad58c5b --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs @@ -0,0 +1,1183 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +// These are all types/strings/etc defined in the IPP RFCs. Look there for info. +#pragma warning disable SA1602 // Enumeration items should be documented +#pragma warning disable SA1600 // elements should be documented + +namespace BadgeReleaseDemo.IppLibrary +{ + /// + /// From RFC 2911: + /// + /// 0x0000 reserved, not used + /// 0x0001 reserved, not used + /// 0x0002 Print-Job + /// 0x0003 Print-URI + /// 0x0004 Validate-Job + /// 0x0005 Create-Job + /// 0x0006 Send-Document + /// 0x0007 Send-URI + /// 0x0008 Cancel-Job + /// 0x0009 Get-Job-Attributes + /// 0x000A Get-Jobs + /// 0x000B Get-Printer-Attributes + /// 0x000C Hold-Job + /// 0x000D Release-Job + /// 0x000E Restart-Job + /// 0x000F reserved for a future operation + /// 0x0010 Pause-Printer + /// 0x0011 Resume-Printer + /// 0x0012 Purge-Jobs + /// + /// For IPP INFRA + /// From: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf (section 14.4) + /// + /// 0x003f Acknowledge-Document + /// 0x0040 Acknowledge-Identity-Printer + /// 0x0041 Acknowledge-Job + /// 0x0042 Fetch-Document + /// 0x0043 Fetch-Job + /// 0x0044 Get-Output-Device-Attributes + /// 0x0045 Update-Active-Jobs + /// 0x0046 Deregister-Output-Device + /// 0x0047 Update-Document-Status + /// 0x0048 Update-Job-Status + /// 0x0049 Update-Output-Device-Attributes + /// + /// For IPP Job Extensions v2.0 + /// From: https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext20-20190816-5100.7.pdf (section 4) + /// + /// 0x003A CloseJob + /// + /// 0x0013-0x3FFF reserved for future IETF standards track + /// operations (see section 6.4) + /// 0x4000-0x8FFF reserved for vendor extensions (see section 6.4) + /// + public enum Operation + { + // IPP + PrintJob = 0x2, + PrintUri = 0x3, + ValidateJob = 0x4, + CreateJob = 0x5, + SendDocument = 0x6, + SendUri = 0x7, + CancelJob = 0x8, + GetJobAttributes = 0x9, + GetJobs = 0xA, + GetPrinterAttributes = 0xB, + HoldJob = 0xC, + ReleaseJob = 0xD, + RestartJob = 0xE, + PausePrinter = 0x10, + ResumePrinter = 0x11, + PurgeJobs = 0x12, + + // Job/Printer Set operations https://tools.ietf.org/html/rfc3380#section-4 + SetPrinterAttributes = 0x13, + SetJobAttributes = 0x14, + GetPrinterSupportedValues = 0x15, + + // IPP INFRA (from: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 14.3). + CreatePrinterSubscriptions = 0x16, + CreateJobSubscriptions = 0x17, + GetSubscriptionAttributes = 0x18, + GetSubscriptions = 0x19, + RenewSubscription = 0x1A, + CancelSubscription = 0x1B, + GetNotifications = 0x1C, + AcknowledgeDocument = 0x3F, + AcknowledgeIdentityPrinter = 0x40, /* not used in IPP Infra */ + AcknowledgeJob = 0x41, + FetchDocument = 0x42, + FetchJob = 0x43, + GetOutputDeviceAttributes = 0x44, + UpdateActiveJobs = 0x45, + DeregisterOutputDevice = 0x46, + UpdateDocumentStatus = 0x47, + UpdateJobStatus = 0x48, + UpdateOutputDeviceAttributes = 0x49, + + // IPP Job Extensions v2.0 (from: https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext20-20190816-5100.7.pdf section 4). + CloseJob = 0x3A, + + // Custom Windows IPP Extensions for IPP. + GetPrintDeviceCapabilities = 0x5000, + GetPrintDeviceResources = 0x5001, + + // Custom Windows IPP Extensions for IPP Infra. + SetPrintDeviceCapabilities = 0x5100, + SetPrintCapabilities = 0x5101 + } + + public enum PrinterState + { + Idle = 3, + Processing = 4, + Stopped = 5, + } + + public enum Orientation + { + Portrait = 3, + Landscape = 4, + ReverseLandscape = 5, + ReversePortrait = 6, + None = 7 + } + + public enum JobState + { + Pending = 3, + PendingHeld = 4, + Processing = 5, + ProcessingStopped = 6, + Canceled = 7, + Aborted = 8, + Completed = 9 + } + + // RFC 8011 section 5.2.13. + public enum PrintQuality + { + Draft = 3, + Normal = 4, + High = 5 + } + + // https://tools.ietf.org/html/rfc8011#page-115 and PWG5100.1. + public enum Finishings + { + None = 3, + Staple = 4, + Punch = 5, + Cover = 6, + Bind = 7, + SaddleStitch = 8, + EdgeStitch = 9, + Fold = 10, + Trim = 11, + Bale = 12, + BookletMaker = 13, + JogOffset = 14, + Coat = 15, + Laminate = 16, + StapleTopLeft = 20, + StapleBottomLeft = 21, + StapleTopRight = 22, + StapleBottomRight = 23, + EdgeStitchLeft = 24, + EdgeStitchTop = 25, + EdgeStitchRight = 26, + EdgeStitchBottom = 27, + StapleDualLeft = 28, + StapleDualTop = 29, + StapleDualRight = 30, + StapleDualBottom = 31, + StapleTripleLeft = 32, + StapleTripleTop = 33, + StapleTripleRight = 34, + StapleTripleBottom = 35, + BindLeft = 50, + BindTop = 51, + BindRight = 52, + BindBottom = 53, + TrimAfterPages = 60, + TrimAfterDocuments = 61, + TrimAfterCopies = 62, + TrimAfterJob = 63, + PunchTopLeft = 70, + PunchBottomLeft = 71, + PunchTopRight = 72, + PunchBottomRight = 73, + PunchDualLeft = 74, + PunchDualTop = 75, + PunchDualRight = 76, + PunchDualBottom = 77, + PunchTripleLeft = 78, + PunchTripleTop = 79, + PunchTripleRight = 80, + PunchTripleBottom = 81, + PunchQuadLeft = 82, + PunchQuadTop = 83, + PunchQuadRight = 84, + PunchQuadBottom = 85, + PunchMultipleLeft = 86, + PunchMultipleTop = 87, + PunchMultipleRight = 88, + PunchMultipleBottom = 89, + FoldAccordion = 90, + FoldDoubleGate = 91, + FoldGate = 92, + FoldHalf = 93, + FoldHalfZ = 94, + FoldLeftGate = 95, + FoldLetter = 96, + FoldParallel = 97, + FoldPoster = 98, + FoldRightGate = 99, + FoldZ = 100, + FoldEngineeringZ = 101, + } + + /// + /// Describes the standard IPP status codes + /// + public enum StatusCode + { + // Success codes (0x0000-0x00ff) + SuccessfulOk = 0x0000, + SuccessfulOkIgnoredOrSubstitutedAttributes = 0x0001, + SuccessfulOkConflictingAttributes = 0x0002, + SuccessfulOkIgnoredSubscriptions = 0x0003, + SuccessfulOkTooManyEvents = 0x0005, + SuccessfulOkMax = 0x00ff, + + // Client error status codes + // (used '[when] the client seems to have erred.') + ClientErrorBadRequest = 0x0400, + ClientErrorForbidden = 0x0401, + ClientErrorNotAuthenticated = 0x0402, + ClientErrorNotAuthorized = 0x0403, + ClientErrorNotPossible = 0x0404, + ClientErrorTimeout = 0x0405, + ClientErrorNotFound = 0x0406, + ClientErrorGone = 0x0407, + ClientErrorRequestEntityTooLarge = 0x0408, + ClientErrorRequestValueTooLong = 0x0409, + ClientErrorDocumentFormatNotSupported = 0x040a, + ClientErrorAttributesOrValuesNotSupported = 0x040b, + ClientErrorUriSchemeNotSupported = 0x040c, + ClientErrorCharsetNotSupported = 0x040d, + ClientErrorConflictingAttributes = 0x040e, + ClientErrorCompressionNotSupported = 0x040f, + ClientErrorCompressionError = 0x0410, + ClientErrorDocumentFormatError = 0x0411, + ClientErrorDocumentAccessError = 0x0412, + ClientErrorIgnoredAllSubscriptions = 0x0414, + ClientErrorTooManySubscriptions = 0x0415, + + // Server error status codes + // (used '[when] the IPP object is aware that it has erred or is incapable of performing the request.') + ServerErrorInternalError = 0x0500, + ServerErrorOperationNotSupported = 0x0501, + ServerErrorServiceUnavailable = 0x0502, + ServerErrorVersionNotSupported = 0x0503, + ServerErrorDeviceError = 0x0504, + ServerErrorTemporaryError = 0x0505, + ServerErrorNotAcceptingJobs = 0x0506, + ServerErrorBusy = 0x0507, + ServerErrorJobCanceled = 0x0508, + ServerErrorMultipleDocumentJobsNotSupported = 0x0509, + + // Note: this is only for internal consumption when status code is unknown. + Undefined = 0xffff + } + + // Section 13.3.1 of AirPrint Specification Version 2.1.1 (not publicly available, see team sharepoint) + public enum LandscapeOrientationRequestedPreferred + { + NinetyDegreesCounterClockwise = 4, + NinetyDegreesClockwise = 5 + } + + // RFC 8011 section 5.2.8: sides. + public struct Sides + { + public const string OneSided = "one-sided"; + public const string TwoSidedShortEdge = "two-sided-short-edge"; + public const string TwoSidedLongEdge = "two-sided-long-edge"; + } + + // PWG 5100.13 print-color-mode. + public struct ColorModes + { + public const string Auto = "auto"; + public const string BiLevel = "bi-level"; + public const string Color = "color"; + public const string Highlight = "highlight"; + public const string Monochrome = "monochrome"; + public const string ProcessBiLevel = "process-bi-level"; + public const string ProcessMonochrome = "process-monochrome"; + public const string MicrosoftGrayscaleOnly = "microsoft-grayscale-only"; // Custom. Used for grayscale-only Connector printers to show only grayscale option in PDC and print dialog. + public const string MicrosoftMonochromeOnly = "microsoft-monochrome-only"; // Custom. Used for monochrome-only Connector printers to show only monochrome option in PDC and print dialog. + } + + // PWG 5100.16 section 6.2.2. print-scaling values. + public struct PrintScaling + { + public const string Auto = "auto"; + public const string AutoFit = "auto-fit"; + public const string Fill = "fill"; + public const string Fit = "fit"; + public const string None = "none"; + } + + // Keywords for media-front-coating and media-back-coating. + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf. Section 3.13.10. + public struct MediaCoating + { + public const string None = "none"; + public const string Glossy = "glossy"; + public const string HighGloss = "high-gloss"; + public const string SemiGloss = "semi-gloss"; + public const string Satin = "satin"; + public const string Matte = "matte"; + } + + // Keywords for media-color. + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf. Section 3.13.4. + public struct MediaColors + { + public const string NoColor = "no-color"; + public const string White = "white"; + public const string Pink = "pink"; + public const string Yellow = "yellow"; + public const string Blue = "blue"; + public const string Green = "green"; + public const string Buff = "buff"; + public const string GoldenRod = "goldenrod"; + public const string Red = "red"; + public const string Gray = "gray"; + public const string Ivory = "ivory"; + public const string Orange = "orange"; + } + + // Keywords for multiple-document-handling + public struct MultipleDocumentHandling + { + public const string SeparateDocumentsCollatedCopies = "separate-documents-collated-copies"; + public const string SeparateDocumentsUncollatedCopies = "separate-documents-uncollated-copies"; + public const string SingleDocument = "single-document"; + public const string SingleDocumentNewSheet = "single-document-new-sheet"; + } + + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf Table 13. + public struct PresentationDirections + { + public const string ToRightToBottom = "toright-tobottom"; + public const string ToBottomToRight = "tobottom-toright"; + public const string ToLeftToBottom = "toleft-tobottom"; + public const string ToBottomToLeft = "tobottom-toleft"; + public const string ToRightToTop = "toright-totop"; + public const string ToTopToRight = "totop-toright"; + public const string ToLeftToTop = "toleft-totop"; + public const string ToTopToLeft = "totop-toleft"; + } + + /// + /// Media Size Self-Describing Names + /// https://ftp.pwg.org/pub/pwg/candidates/cs-pwgmsn10-20020226-5101.1.pdf section 5. + /// + public struct MediaSizeNames + { + // ASME sizes + public const string AsmeF = "asme_f_28x40in"; + + // ISO A series + public const string Iso2A0 = "iso_2a0_1189x1682mm"; + public const string IsoA0 = "iso_a0_841x1189mm"; + public const string IsoA1 = "iso_a1_594x841mm"; + public const string IsoA1x3 = "iso_a1x3_841x1783mm"; + public const string IsoA1x4 = "iso_a1x4_841x2378mm"; + public const string IsoA2 = "iso_a2_420x594mm"; + public const string IsoA2x3 = "iso_a2x3_594x1261mm"; + public const string IsoA2x4 = "iso_a2x4_594x1682mm"; + public const string IsoA2x5 = "iso_a2x5_594x2102mm"; + public const string IsoA3 = "iso_a3_297x420mm"; + public const string IsoA3Extra = "iso_a3-extra_322x445mm"; + public const string IsoA0x3 = "iso_a0x3_1189x2523mm"; + public const string IsoA3x3 = "iso_a3x3_420x891mm"; + public const string IsoA3x4 = "iso_a3x4_420x1189mm"; + public const string IsoA3x5 = "iso_a3x5_420x1486mm"; + public const string IsoA3x6 = "iso_a3x6_420x1783mm"; + public const string IsoA3x7 = "iso_a3x7_420x2080mm"; + public const string IsoA4Extra = "iso_a4-extra_235.5x322.3mm"; + public const string IsoA4Tab = "iso_a4-tab_225x297mm"; + public const string IsoA4 = "iso_a4_210x297mm"; + public const string IsoA4x3 = "iso_a4x3_297x630mm"; + public const string IsoA4x4 = "iso_a4x4_297x841mm"; + public const string IsoA4x5 = "iso_a4x5_297x1051mm"; + public const string IsoA4x6 = "iso_a4x6_297x1261mm"; + public const string IsoA4x7 = "iso_a4x7_297x1471mm"; + public const string IsoA4x8 = "iso_a4x8_297x1682mm"; + public const string IsoA4x9 = "iso_a4x9_297x1892mm"; + public const string IsoA5Extra = "iso_a5-extra_174x235mm"; + public const string IsoA5 = "iso_a5_148x210mm"; + public const string IsoA6 = "iso_a6_105x148mm"; + public const string IsoA7 = "iso_a7_74x105mm"; + public const string IsoA8 = "iso_a8_52x74mm"; + public const string IsoA9 = "iso_a9_37x52mm"; + public const string IsoA10 = "iso_a10_26x37mm"; + + // ISO B series + public const string IsoB0 = "iso_b0_1000x1414mm"; + public const string IsoB1 = "iso_b1_707x1000mm"; + public const string IsoB2 = "iso_b2_500x707mm"; + public const string IsoB3 = "iso_b3_353x500mm"; + public const string IsoB4 = "iso_b4_250x353mm"; + public const string IsoB5Extra = "iso_b5-extra_201x276mm"; + public const string IsoB5 = "iso_b5_176x250mm"; + public const string IsoB6 = "iso_b6_125x176mm"; + public const string IsoB6C4 = "iso_b6c4_125x324mm"; + public const string IsoB7 = "iso_b7_88x125mm"; + public const string IsoB8 = "iso_b8_62x88mm"; + public const string IsoB9 = "iso_b9_44x62mm"; + public const string IsoB10 = "iso_b10_31x44mm"; + + // ISO C series (envelopes) + public const string IsoC0 = "iso_c0_917x1297mm"; + public const string IsoC1LongEdge = "iso_c1-long-edge_917x648mm"; + public const string IsoC1 = "iso_c1_648x917mm"; + public const string IsoC2LongEdge = "iso_c2-long-edge_648x458mm"; + public const string IsoC2 = "iso_c2_458x648mm"; + public const string IsoC3LongEdge = "iso_c3-long-edge_458x324mm"; + public const string IsoC3 = "iso_c3_324x458mm"; + public const string IsoC4LongEdge = "iso_c4-long-edge_324x229mm"; + public const string IsoC4 = "iso_c4_229x324mm"; + public const string IsoC5LongEdge = "iso_c5-long-edge_229x162mm"; + public const string IsoC5 = "iso_c5_162x229mm"; + public const string IsoC6LongEdge = "iso_c6-long-edge_162x114mm"; + public const string IsoC6 = "iso_c6_114x162mm"; + public const string IsoC6C5 = "iso_c6c5_114x229mm"; + public const string IsoC7LongEdge = "iso_c7-long-edge_114x81mm"; + public const string IsoC7 = "iso_c7_81x114mm"; + public const string IsoC7C6 = "iso_c7c6_81x162mm"; + public const string IsoC8LongEdge = "iso_c8-long-edge_81x57mm"; + public const string IsoC8 = "iso_c8_57x81mm"; + public const string IsoC9LongEdge = "iso_c9-long-edge_57x40mm"; + public const string IsoC9 = "iso_c9_40x57mm"; + public const string IsoC10LongEdge = "iso_c10-long-edge_40x28mm"; + public const string IsoC10 = "iso_c10_28x40mm"; + public const string IsoDlLongEdge = "iso_dl-long-edge_220x110mm"; + public const string IsoDl = "iso_dl_110x220mm"; + + // ISO RA series + public const string IsoRa0 = "iso_ra0_860x1220mm"; + public const string IsoRa1 = "iso_ra1_610x860mm"; + public const string IsoRa2 = "iso_ra2_430x610mm"; + public const string IsoRa3 = "iso_ra3_305x430mm"; + public const string IsoRa4 = "iso_ra4_215x305mm"; + + // ISO SRA series + public const string IsoSra0 = "iso_sra0_900x1280mm"; + public const string IsoSra1 = "iso_sra1_640x900mm"; + public const string IsoSra2 = "iso_sra2_450x640mm"; + public const string IsoSra3 = "iso_sra3_320x450mm"; + public const string IsoSra4 = "iso_sra4_225x320mm"; + + // ISO ID-1 (credit card size) + public const string IsoId1 = "iso_id-1_53.98x85.6mm"; + + // JIS B series + public const string JisB0 = "jis_b0_1030x1456mm"; + public const string JisB1 = "jis_b1_728x1030mm"; + public const string JisB2 = "jis_b2_515x728mm"; + public const string JisB3 = "jis_b3_364x515mm"; + public const string JisB4 = "jis_b4_257x364mm"; + public const string JisB5 = "jis_b5_182x257mm"; + public const string JisB6 = "jis_b6_128x182mm"; + public const string JisB7 = "jis_b7_91x128mm"; + public const string JisB8 = "jis_b8_64x91mm"; + public const string JisB9 = "jis_b9_45x64mm"; + public const string JisB10 = "jis_b10_32x45mm"; + public const string JisExec = "jis_exec_216x330mm"; + + // Japanese envelope and postcard sizes + public const string JpnChou2 = "jpn_chou2_111.1x146mm"; + public const string JpnChou3 = "jpn_chou3_120x235mm"; + public const string JpnChou4 = "jpn_chou4_90x205mm"; + public const string JpnChou40 = "jpn_chou40_90x225mm"; + public const string JpnHagaki = "jpn_hagaki_100x148mm"; + public const string JpnKahu = "jpn_kahu_240x322.1mm"; + public const string JpnKaku1 = "jpn_kaku1_270x382mm"; + public const string JpnKaku2 = "jpn_kaku2_240x332mm"; + public const string JpnKaku3 = "jpn_kaku3_216x277mm"; + public const string JpnKaku4 = "jpn_kaku4_197x267mm"; + public const string JpnKaku5 = "jpn_kaku5_190x240mm"; + public const string JpnKaku7 = "jpn_kaku7_142x205mm"; + public const string JpnKaku8 = "jpn_kaku8_119x197mm"; + public const string JpnOufuku = "jpn_oufuku_148x200mm"; + public const string JpnYou1 = "jpn_you1_120x176mm"; + public const string JpnYou3 = "jpn_you3_98x148mm"; + public const string JpnYou4 = "jpn_you4_105x235mm"; + public const string JpnYou4LongEdge = "jpn_you4_235x105mm"; + public const string JpnYou5 = "jpn_you5_95x217mm"; + public const string JpnYou6 = "jpn_you6_98x190mm"; + public const string JpnYou7 = "jpn_you7_92x165mm"; + public const string JpnYouchou2 = "jpn_youchou2_146x111.1mm"; + public const string JpnYouchou3 = "jpn_youchou3_235x120mm"; + public const string JpnYouchou4 = "jpn_youchou4_205x90mm"; + + // North American sizes + public const string Na5x7 = "na_5x7_5x7in"; + public const string Na6x9 = "na_6x9_6x9in"; + public const string Na7x9 = "na_7x9_7x9in"; + public const string Na9x11 = "na_9x11_9x11in"; + public const string Na10x11 = "na_10x11_10x11in"; + public const string Na10x13 = "na_10x13_10x13in"; + public const string Na10x14 = "na_10x14_10x14in"; + public const string Na10x15 = "na_10x15_10x15in"; + public const string Na11x12 = "na_11x12_11x12in"; + public const string Na11x15 = "na_11x15_11x15in"; + public const string Na12x19 = "na_12x19_12x19in"; + public const string NaA2 = "na_a2_4.375x5.75in"; + public const string NaArchA = "na_arch-a_9x12in"; + public const string NaArchB = "na_arch-b_12x18in"; + public const string NaArchC = "na_arch-c_18x24in"; + public const string NaArchD = "na_arch-d_24x36in"; + public const string NaArchE2 = "na_arch-e2_26x38in"; + public const string NaArchE3 = "na_arch-e3_27x39in"; + public const string NaArchE = "na_arch-e_36x48in"; + public const string NaBPlus = "na_b-plus_12x19.17in"; + public const string NaC5 = "na_c5_6.5x9.5in"; + public const string NaC = "na_c_17x22in"; + public const string NaD = "na_d_22x34in"; + public const string NaE = "na_e_34x44in"; + public const string NaEdp = "na_edp_11x14in"; + public const string NaEurEdp = "na_eur-edp_12x14in"; + public const string NaExecutive = "na_executive_7.25x10.5in"; + public const string NaF = "na_f_44x68in"; + public const string NaFanfoldEur = "na_fanfold-eur_8.5x12in"; + public const string NaFanfoldUs = "na_fanfold-us_11x14.875in"; + public const string NaFoolscap = "na_foolscap_8.5x13in"; + public const string NaGovtLegal = "na_govt-legal_8x13in"; + public const string NaGovtLetter = "na_govt-letter_8x10in"; + public const string NaIndex3x5 = "na_index-3x5_3x5in"; + public const string NaIndex4x6Ext = "na_index-4x6-ext_6x8in"; + public const string NaIndex4x6 = "na_index-4x6_4x6in"; + public const string NaIndex5x8 = "na_index-5x8_5x8in"; + public const string NaInvoice = "na_invoice_5.5x8.5in"; + public const string NaLedger = "na_ledger_11x17in"; + public const string NaLegalExtra = "na_legal-extra_9.5x15in"; + public const string NaLegal = "na_legal_8.5x14in"; + public const string NaLetterExtra = "na_letter-extra_9.5x12in"; + public const string NaLetterPlus = "na_letter-plus_8.5x12.69in"; + public const string NaLetter = "na_letter_8.5x11in"; + public const string NaMonarchLongEdge = "na_monarch-long-edge_7.5x3.875in"; + public const string NaMonarch = "na_monarch_3.875x7.5in"; + public const string NaNumber9LongEdge = "na_number-9-long-edge_8.875x3.875in"; + public const string NaNumber9 = "na_number-9_3.875x8.875in"; + public const string NaNumber10LongEdge = "na_number-10-long-edge_9.5x4.125in"; + public const string NaNumber10 = "na_number-10_4.125x9.5in"; + public const string NaNumber11LongEdge = "na_number-11-long-edge_10.375x4.5in"; + public const string NaNumber11 = "na_number-11_4.5x10.375in"; + public const string NaNumber12LongEdge = "na_number-12-long-edge_11x4.75in"; + public const string NaNumber12 = "na_number-12_4.75x11in"; + public const string NaNumber14LongEdge = "na_number-14-long-edge_11.5x5in"; + public const string NaNumber14 = "na_number-14_5x11.5in"; + public const string NaOficio = "na_oficio_8.5x13.4in"; + public const string NaPersonalLongEdge = "na_personal-long-edge_6.5x3.625in"; + public const string NaPersonal = "na_personal_3.625x6.5in"; + public const string NaQuarto = "na_quarto_8.5x10.83in"; + public const string NaSuperA = "na_super-a_8.94x14in"; + public const string NaSuperB = "na_super-b_13x19in"; + public const string NaWideFormat = "na_wide-format_30x42in"; + + // Other sizes (oe_ prefix) + public const string Oe12x16 = "oe_12x16_12x16in"; + public const string Oe13x22 = "oe_13x22_13x22in"; + public const string Oe14x17 = "oe_14x17_14x17in"; + public const string Oe18x22 = "oe_18x22_18x22in"; + public const string OeA2Plus = "oe_a2plus_17x24in"; + public const string OeBusinessCard = "oe_business-card_2x3.5in"; + public const string OePhoto10r = "oe_photo-10r_10x12in"; + public const string OePhoto12r = "oe_photo-12r_12x15in"; + public const string OePhoto14x18 = "oe_photo-14x18_14x18in"; + public const string OePhoto16r = "oe_photo-16r_16x20in"; + public const string OePhoto20r = "oe_photo-20r_20x24in"; + public const string OePhoto20x30 = "oe_photo-20x30_20x30in"; + public const string OePhoto22r = "oe_photo-22r_22x29.5in"; + public const string OePhoto22x28 = "oe_photo-22x28_22x28in"; + public const string OePhoto24r = "oe_photo-24r_24x31.5in"; + public const string OePhoto24x30 = "oe_photo-24x30_24x30in"; + public const string OePhoto30r = "oe_photo-30r_30x40in"; + public const string OePhotoL = "oe_photo-l_3.5x5in"; + public const string OmPhotoL = "om_photo-l_89x119mm"; + public const string OePhotoS8r = "oe_photo-s8r_8x12in"; + public const string OePhotoS10r = "oe_photo-s10r_10x15in"; + public const string OeSquarePhoto4x4 = "oe_square-photo_4x4in"; + public const string OeSquarePhoto5x5 = "oe_square-photo_5x5in"; + + // Other metric sizes (om_ prefix) + public const string Om16k184x260 = "om_16k_184x260mm"; + public const string Om16k195x270 = "om_16k_195x270mm"; + public const string OmBusinessCard55x85 = "om_business-card_55x85mm"; + public const string OmBusinessCard55x91 = "om_business-card_55x91mm"; + public const string OmCard = "om_card_54x86mm"; + public const string OmDaiPaKai = "om_dai-pa-kai_275x395mm"; + public const string OmDscPhoto = "om_dsc-photo_89x119mm"; + public const string OmFolioSp = "om_folio-sp_215x315mm"; + public const string OmFolio = "om_folio_210x330mm"; + public const string OmInvite = "om_invite_220x220mm"; + public const string OmItalian = "om_italian_110x230mm"; + public const string OmJuuroKuKai = "om_juuro-ku-kai_198x275mm"; + public const string OmLargePhoto = "om_large-photo_200x300mm"; + public const string OmMediumPhoto = "om_medium-photo_130x180mm"; + public const string OmPaKai = "om_pa-kai_267x389mm"; + public const string OmPhoto30x40 = "om_photo-30x40_300x400mm"; + public const string OmPhoto30x45 = "om_photo-30x45_300x450mm"; + public const string OmPhoto30x90 = "om_photo-30x90_300x900mm"; + public const string OmPhoto35x46 = "om_photo-35x46_350x460mm"; + public const string OmPhoto40x60 = "om_photo-40x60_400x600mm"; + public const string OmPhoto50x75 = "om_photo-50x75_500x750mm"; + public const string OmPhoto50x76 = "om_photo-50x76_500x760mm"; + public const string OmPhoto60x90 = "om_photo-60x90_600x900mm"; + public const string OmSmallPhoto = "om_small-photo_100x150mm"; + public const string OmSquarePhoto = "om_square-photo_89x89mm"; + public const string OmWidePhoto = "om_wide-photo_100x200mm"; + + // PRC (People's Republic of China) sizes + public const string Prc1 = "prc_1_102x165mm"; + public const string Prc2 = "prc_2_102x176mm"; + public const string Prc4 = "prc_4_110x208mm"; + public const string Prc6 = "prc_6_120x320mm"; + public const string Prc7 = "prc_7_160x230mm"; + public const string Prc8 = "prc_8_120x309mm"; + public const string Prc16k = "prc_16k_146x215mm"; + public const string Prc32k = "prc_32k_97x151mm"; + + // ROC (Republic of China/Taiwan) sizes + public const string Roc8k = "roc_8k_10.75x15.5in"; + public const string Roc16k = "roc_16k_7.75x10.75in"; + + // other media sizes + public const string Universal11x14LexmarkCustomSize = "oe_universal_11x14in"; + } + + /// + /// keywords for media-source. + /// https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf section 7.6.5. + /// + public struct MediaSource + { + public const string Alternate = "alternate"; + public const string AlternateRoll = "alternate-roll"; + public const string Auto = "auto"; + public const string Bottom = "bottom"; + public const string ByPassTray = "by-pass-tray"; + public const string Center = "center"; + public const string Disc = "disc"; + public const string Envelope = "envelope"; + public const string Hagaki = "hagaki"; + public const string LargeCapacity = "large-capacity"; + public const string Left = "left"; + public const string Main = "main"; + public const string MainRoll = "main-roll"; + public const string Manual = "manual"; + public const string Middle = "middle"; + public const string Photo = "photo"; + public const string Rear = "rear"; + public const string Right = "right"; + public const string Roll1 = "roll-1"; + public const string Roll2 = "roll-2"; + public const string Roll3 = "roll-3"; + public const string Roll4 = "roll-4"; + public const string Roll5 = "roll-5"; + public const string Roll6 = "roll-6"; + public const string Roll7 = "roll-7"; + public const string Roll8 = "roll-8"; + public const string Roll9 = "roll-9"; + public const string Roll10 = "roll-10"; + public const string Side = "side"; + public const string Top = "top"; + public const string Tray1 = "tray-1"; + public const string Tray2 = "tray-2"; + public const string Tray3 = "tray-3"; + public const string Tray4 = "tray-4"; + public const string Tray5 = "tray-5"; + public const string Tray6 = "tray-6"; + public const string Tray7 = "tray-7"; + public const string Tray8 = "tray-8"; + public const string Tray9 = "tray-9"; + public const string Tray10 = "tray-10"; + public const string Tray11 = "tray-11"; + public const string Tray12 = "tray-12"; + public const string Tray13 = "tray-13"; + public const string Tray14 = "tray-14"; + public const string Tray15 = "tray-15"; + public const string Tray16 = "tray-16"; + public const string Tray17 = "tray-17"; + public const string Tray18 = "tray-18"; + public const string Tray19 = "tray-19"; + public const string Tray20 = "tray-20"; + } + + // keywords for media-type + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf section 3.13.2 + // https://ftp.pwg.org/pub/pwg/candidates/cs-pwgmsn10-20020226-5101.1.pdf section 3 + // https://ftp.pwg.org/pub/pwg/ipp/registrations/xerox-mediatypes-20201202.txt + // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xhtml + public struct MediaTypes + { + public const string Aluminum = "aluminum"; + public const string Auto = "auto"; + public const string BackPrintFilm = "back-print-film"; + public const string Cardboard = "cardboard"; + public const string Cardstock = "cardstock"; + public const string CardstockCoated = "cardstock-coated"; + public const string CardstockHeavyweight = "cardstock-heavyweight"; + public const string CardstockHeavyweightCoated = "cardstock-heavyweight-coated"; + public const string CardstockLightweight = "cardstock-lightweight"; + public const string CardstockLightweightCoated = "cardstock-lightweight-coated"; + public const string Cd = "cd"; + public const string Continuous = "continuous"; + public const string ContinuousLong = "continuous-long"; + public const string ContinuousShort = "continuous-short"; + public const string CorrugatedBoard = "corrugated-board"; + public const string Disc = "disc"; + public const string DiscGlossy = "disc-glossy"; + public const string DiscHighGloss = "disc-high-gloss"; + public const string DiscMatte = "disc-matte"; + public const string DiscSatin = "disc-satin"; + public const string DiscSemiGloss = "disc-semi-gloss"; + public const string DoubleWall = "double-wall"; + public const string DryFilm = "dry-film"; + public const string Dvd = "dvd"; + public const string EmbossingFoil = "embossing-foil"; + public const string EndBoard = "end-board"; + public const string Envelope = "envelope"; + public const string EnvelopeArchival = "envelope-archival"; + public const string EnvelopeBond = "envelope-bond"; + public const string EnvelopeCoated = "envelope-coated"; + public const string EnvelopeCotton = "envelope-cotton"; + public const string EnvelopeFine = "envelope-fine"; + public const string EnvelopeHeavyweight = "envelope-heavyweight"; + public const string EnvelopeInkjet = "envelope-inkjet"; + public const string EnvelopeLightweight = "envelope-lightweight"; + public const string EnvelopePlain = "envelope-plain"; + public const string EnvelopePreprinted = "envelope-preprinted"; + public const string EnvelopeWindow = "envelope-window"; + public const string Fabric = "fabric"; + public const string FabricArchival = "fabric-archival"; + public const string FabricGlossy = "fabric-glossy"; + public const string FabricHighGloss = "fabric-high-gloss"; + public const string FabricMatte = "fabric-matte"; + public const string FabricSemiGloss = "fabric-semi-gloss"; + public const string FabricWaterproof = "fabric-waterproof"; + public const string Film = "film"; + public const string FlexoBase = "flexo-base"; + public const string FlexoPhotoPolymer = "flexo-photo-polymer"; + public const string Flute = "flute"; + public const string Foil = "foil"; + public const string FullCutTabs = "full-cut-tabs"; + public const string Glass = "glass"; + public const string GlassColored = "glass-colored"; + public const string GlassOpaque = "glass-opaque"; + public const string GlassSurfaced = "glass-surfaced"; + public const string GlassTextured = "glass-textured"; + public const string GravureCylinder = "gravure-cylinder"; + public const string ImageSetterPaper = "image-setter-paper"; + public const string ImagingCylinder = "imaging-cylinder"; + public const string Labels = "labels"; + public const string LabelsColored = "labels-colored"; + public const string LabelsContinuous = "labels-continuous"; + public const string LabelsGlossy = "labels-glossy"; + public const string LabelsHeavyweight = "labels-heavyweight"; + public const string LabelsHighGloss = "labels-high-gloss"; + public const string LabelsInkjet = "labels-inkjet"; + public const string LabelsLightweight = "labels-lightweight"; + public const string LabelsMatte = "labels-matte"; + public const string LabelsPermanent = "labels-permanent"; + public const string LabelsSatin = "labels-satin"; + public const string LabelsSecurity = "labels-security"; + public const string LabelsSemiGloss = "labels-semi-gloss"; + public const string LaminatingFoil = "laminating-foil"; + public const string Letterhead = "letterhead"; + public const string Metal = "metal"; + public const string MetalGlossy = "metal-glossy"; + public const string MetalHighGloss = "metal-high-gloss"; + public const string MetalMatte = "metal-matte"; + public const string MetalSatin = "metal-satin"; + public const string MetalSemiGloss = "metal-semi-gloss"; + public const string MountingTape = "mounting-tape"; + public const string MultiLayer = "multi-layer"; + public const string MultiPartForm = "multi-part-form"; + public const string Other = "other"; + public const string Paper = "paper"; + public const string Photographic = "photographic"; + public const string PhotographicArchival = "photographic-archival"; + public const string PhotographicFilm = "photographic-film"; + public const string PhotographicGlossy = "photographic-glossy"; + public const string PhotographicHighGloss = "photographic-high-gloss"; + public const string PhotographicMatte = "photographic-matte"; + public const string PhotographicSatin = "photographic-satin"; + public const string PhotographicSemiGloss = "photographic-semi-gloss"; + public const string Plastic = "plastic"; + public const string PlasticArchival = "plastic-archival"; + public const string PlasticColored = "plastic-colored"; + public const string PlasticGlossy = "plastic-glossy"; + public const string PlasticHighGloss = "plastic-high-gloss"; + public const string PlasticMatte = "plastic-matte"; + public const string PlasticSatin = "plastic-satin"; + public const string PlasticSemiGloss = "plastic-semi-gloss"; + public const string Plate = "plate"; + public const string Polyester = "polyester"; + public const string PreCutTabs = "pre-cut-tabs"; + public const string Roll = "roll"; + public const string Screen = "screen"; + public const string ScreenPaged = "screen-paged"; + public const string SelfAdhesive = "self-adhesive"; + public const string SelfAdhesiveFilm = "self-adhesive-film"; + public const string ShrinkFoil = "shrink-foil"; + public const string SingleFace = "single-face"; + public const string SingleWall = "single-wall"; + public const string Sleeve = "sleeve"; + public const string Stationery = "stationery"; + public const string StationeryArchival = "stationery-archival"; + public const string StationeryBond = "stationery-bond"; + public const string StationeryCoated = "stationery-coated"; + public const string StationeryColored = "stationery-colored"; + public const string StationeryCotton = "stationery-cotton"; + public const string StationeryFine = "stationery-fine"; + public const string StationeryHeavyweight = "stationery-heavyweight"; + public const string StationeryHeavyweightCoated = "stationery-heavyweight-coated"; + public const string StationeryInkjet = "stationery-inkjet"; + public const string StationeryLetterhead = "stationery-letterhead"; + public const string StationeryLightweight = "stationery-lightweight"; + public const string StationeryPreprinted = "stationery-preprinted"; + public const string StationeryPrepunched = "stationery-prepunched"; + public const string StationeryRecycled = "stationery-recycled"; + public const string TabStock = "tab-stock"; + public const string Tractor = "tractor"; + public const string Transfer = "transfer"; + public const string Transparency = "transparency"; + public const string TripleWall = "triple-wall"; + public const string WetFilm = "wet-film"; + } + + // keywords for presentation-direction-number-up + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf + public struct PresentationDirectionNumberUp + { + public const string BottomLeft = "tobottom-toleft"; + public const string BottomRight = "tobottom-toright"; + public const string LeftBottom = "toleft-tobottom"; + public const string LeftTop = "toleft-totop"; + public const string RightBottom = "toright-tobottom"; + public const string RightTop = "toright-totop"; + public const string TopLeft = "totop-toleft"; + public const string TopRight = "totop-toright"; + } + + // keywords for page-order-received + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf + public struct PageOrderReceived + { + public const string OnetoNOrder = "1-to-n-order"; + public const string NtoOneOrder = "n-to-1-order"; + } + + // keywords for media-source-feed-direction. + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf section 7.6.6.1 + public struct MediaSourceFeedDirections + { + public const string LongEdgeFirst = "long-edge-first"; + public const string ShortEdgeFirst = "short-edge-first"; + } + + // keywords for output-bin-supported, etc. + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippoutputbin10-20010207-5100.2.pdf section 2.1. + public struct OutputBins + { + public const string Auto = "auto"; + public const string Top = "top"; + public const string Middle = "middle"; + public const string Bottom = "bottom"; + public const string Side = "side"; + public const string Left = "left"; + public const string Right = "right"; + public const string Center = "center"; + public const string Rear = "rear"; + public const string FaceUp = "face-up"; + public const string FaceDown = "face-down"; + public const string LargeCapacity = "large-capacity"; + public const string Stacker1 = "stacker-1"; + public const string Stacker2 = "stacker-2"; + public const string Stacker3 = "stacker-3"; + public const string Stacker4 = "stacker-4"; + public const string Stacker5 = "stacker-5"; + public const string Stacker6 = "stacker-6"; + public const string Stacker7 = "stacker-7"; + public const string Stacker8 = "stacker-8"; + public const string Stacker9 = "stacker-9"; + public const string Stacker10 = "stacker-10"; + public const string Mailbox1 = "mailbox-1"; + public const string Mailbox2 = "mailbox-2"; + public const string Mailbox3 = "mailbox-3"; + public const string Mailbox4 = "mailbox-4"; + public const string Mailbox5 = "mailbox-5"; + public const string Mailbox6 = "mailbox-6"; + public const string Mailbox7 = "mailbox-7"; + public const string Mailbox8 = "mailbox-8"; + public const string Mailbox9 = "mailbox-9"; + public const string Mailbox10 = "mailbox-10"; + public const string Mailbox11 = "mailbox-11"; + public const string Mailbox12 = "mailbox-12"; + public const string Mailbox13 = "mailbox-13"; + public const string Mailbox14 = "mailbox-14"; + public const string Mailbox15 = "mailbox-15"; + public const string Mailbox16 = "mailbox-16"; + public const string Mailbox17 = "mailbox-17"; + public const string Mailbox18 = "mailbox-18"; + public const string Mailbox19 = "mailbox-19"; + public const string Mailbox20 = "mailbox-20"; + public const string Mailbox21 = "mailbox-21"; + public const string Mailbox22 = "mailbox-22"; + public const string Mailbox23 = "mailbox-23"; + public const string Mailbox24 = "mailbox-24"; + public const string Mailbox25 = "mailbox-25"; + public const string MyMailbox = "my-mailbox"; + public const string Tray1 = "tray-1"; + public const string Tray2 = "tray-2"; + public const string Tray3 = "tray-3"; + public const string Tray4 = "tray-4"; + public const string Tray5 = "tray-5"; + public const string Tray6 = "tray-6"; + public const string Tray7 = "tray-7"; + public const string Tray8 = "tray-8"; + public const string Tray9 = "tray-9"; + public const string Tray10 = "tray-10"; + public const string Tray11 = "tray-11"; + public const string Tray12 = "tray-12"; + public const string Tray13 = "tray-13"; + public const string Tray14 = "tray-14"; + public const string Tray15 = "tray-15"; + public const string Tray16 = "tray-16"; + public const string Tray17 = "tray-17"; + public const string Tray18 = "tray-18"; + public const string Tray19 = "tray-19"; + public const string Tray20 = "tray-20"; + } + + // keywords for printer-state-reasons + // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xhtml + // we currently accept any non-empty values of printer-state-reasons + // the list below only includes some values from the entire list to be used during testing + public struct PrinterStateReasons + { + public const string None = "none"; + public const string Other = "other"; + public const string MediaNeeded = "media-needed"; + public const string MediaJam = "media-jam"; + public const string MovingToPaused = "moving-to-paused"; + public const string Paused = "paused"; + public const string Shutdown = "shutdown"; + public const string ConnectingToDevice = "connecting-to-device"; + public const string TimedOut = "timed-out"; + public const string Stopping = "stopping"; + public const string StoppedPartly = "stopped-partly"; + public const string TonerLow = "toner-low"; + public const string TonerEmpty = "toner-empty"; + public const string SpoolAreaFull = "spool-area-full"; + public const string CoverOpen = "cover-open"; + public const string InterlockOpen = "interlock-open"; + public const string DoorOpen = "door-open"; + public const string InputTrayMissing = "input-tray-missing"; + public const string MediaLow = "media-low"; + public const string MediaEmpty = "media-empty"; + public const string OutputTrayMissing = "output-tray-missing"; + public const string OutputAreaAlmostFull = "output-area-almost-full"; + public const string OutputAreaFull = "output-area-full"; + public const string MarkerSupplyLow = "marker-supply-low"; + public const string MarkerSupplyEmpty = "marker-supply-empty"; + public const string MarkerWasteAlmostFull = "marker-waste-almost-full"; + public const string MarkerWasteFull = "marker-waste-full"; + public const string FuserOverTemp = "fuser-over-temp"; + public const string FuserUnderTemp = "fuser-under-temp"; + public const string OpcNearEol = "opc-near-eol"; + public const string OpcLifeOver = "opc-life-over"; + public const string DeveloperLow = "developer-low"; + public const string DeveloperEmpty = "developer-empty"; + public const string InterpreterResourceUnavailable = "interpreter-resource-unavailable"; + } + + public struct PrinterStateReasonSuffix + { + public const string Report = "-report"; + public const string Warning = "-warning"; + public const string Error = "-error"; + } + + public struct JobStateReasons + { + public const string None = "none"; + public const string JobIncoming = "job-incoming"; + public const string JobDataInsufficient = "job-data-insufficient"; + public const string DocumentAccessError = "document-access-error"; + public const string SubmissionInterrupted = "submission-interrupted"; + public const string JobOutgoing = "job-outgoing"; + public const string JobHoldUntilSpecified = "job-hold-until-specified"; + public const string ResourcesAreNotReady = "resources-are-not-ready"; + public const string PrinterStoppedPartly = "printer-stopped-partly"; + public const string PrinterStopped = "printer-stopped"; + public const string JobInterpreting = "job-interpreting"; + public const string JobQueued = "job-queued"; + public const string JobTransforming = "job-transforming"; + public const string JobQueuedForMarker = "job-queued-for-marker"; + public const string JobPrinting = "job-printing"; + public const string JobCanceledByUser = "job-canceled-by-user"; + public const string JobCanceledByOperator = "job-canceled-by-operator"; + public const string JobCanceledAtDevice = "job-canceled-at-device"; + public const string AbortedBySystem = "aborted-by-system"; + public const string UnsupportedCompression = "unsupported-compression"; + public const string CompressionError = "compression-error"; + public const string UnsupportedDocumentFormat = "unsupported-document-format"; + public const string DocumentFormatError = "document-format-error"; + public const string ProcessingToStopPoint = "processing-to-stop-point"; + public const string ServiceOffLine = "service-off-line"; + public const string JobCompletedSuccessfully = "job-completed-successfully"; + public const string JobCompletedWithWarnings = "job-completed-with-warnings"; + public const string JobCompletedWithErrors = "job-completed-with-errors"; + public const string QueuedInDevice = "queued-in-device"; + public const string JobFetchable = "job-fetchable"; + public const string JobReleaseWait = "job-release-wait"; + public const string JobHeldForAuthorization = "job-held-for-authorization"; + public const string JobHeldForButtonPress = "job-held-for-button-press"; + public const string JobHeldForRelease = "job-held-for-release"; //Replacement for JobReleaseWait + public const string JobPasswordWait = "job-password-wait"; + public const string OtherError = "other-error"; + public const string JobFetchableByUser = "job-fetchable-by-user"; + } + + // http://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf + public struct IppFeaturesSupported + { + public const string AirPrintTwoDotOne = "airprint-2.1"; + public const string DocumentObject = "document-object"; + public const string JobSave = "job-save"; + public const string None = "none"; + public const string PageOverrides = "page-overrides"; + public const string ProofPrint = "proof-print"; + public const string SubscriptionObject = "subscription-object"; + } + + // Mopria 1.3 spec Wi-Fi_Direct_Services_Print_Technical_Specification_v1.0 + // https://microsoft.sharepoint.com/:w:/t/STACKTeam-CoreNetworkingMobileConnectivityPeripheralsStackSe/EQkk8BUSMqxGh1b0wkNLlhIBWqsk28NRk3n3F-jEKB69Gg?e=5iUo4A + public struct PdfVersionsSupported + { + public const string Adobe13 = "adobe-1.3"; + public const string Adobe14 = "adobe-1.4"; + public const string Adobe15 = "adobe-1.5"; + public const string Adobe16 = "adobe-1.6"; + public const string Iso1593012001 = "iso-15930-1_2001"; + public const string Iso1593032002 = "iso-15930-3_2002"; + public const string Iso1593042003 = "iso-15930-4_2003"; + public const string Iso1593062003 = "iso-15930-6_2003"; + public const string Iso1593072010 = "iso-15930-7_2010"; + public const string Iso1593082010 = "iso-15930-8_2010"; + public const string Iso1661222010 = "iso-16612-2_2010"; + public const string Iso1900512005 = "iso-19005-1_2005"; + public const string Iso1900522011 = "iso-19005-2_2011"; + public const string Iso1900532012 = "iso-19005-3_2012"; + public const string Iso3200012008 = "iso-32000-1_2008"; + public const string Pwg51023 = "pwg-5102.3"; + public const string None = "none"; + } + + // RFC 5100.7 Section 7.6 https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext10-20031031-5100.7.pdf + public struct DocumentFormatDetailsSupported + { + public const string DocumentSourceApplicationName = "document-source-application-name"; + public const string DocumentSourceApplicationVersion = "document-source-application-version"; + public const string DocumentSourceOsName = "document-source-os-name"; + public const string DocumentSourceOsVersion = "document-source-os-version"; + public const string DocumentFormat = "document-format"; + public const string DocumentFormatDeviceId = "document-format-device-id"; + public const string DocumentFormatVersion = "document-format-version"; + public const string DocumentNaturalLanguage = "document-natural-language"; + } + + // Section 6.4.12 of https://ftp.pwg.org/pub/pwg/candidates/cs-ipptrans10-20131108-5100.16.pdf + public struct PrinterKinds + { + public const string Disc = "disc"; + public const string Document = "document"; + public const string Envelope = "envelope"; + public const string Label = "label"; + public const string LargeFormat = "large-format"; + public const string Photo = "photo"; + public const string Postcard = "postcard"; + public const string Receipt = "receipt"; + public const string Roll = "roll"; + } + + // Section 5.3.3 of https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext20-20190816-5100.7.pdf + public struct PrintContentOptimize + { + public const string Auto = "auto"; // AirPrint specific, see section 9.3.57 of AirPrint Version 2.1.1 specification + public const string Graphics = "graphics"; + public const string Photo = "photo"; + public const string Text = "text"; + public const string TextAndGraphics = "text-and-graphics"; + } + + // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.28 + public struct PdlOverride + { + public const string Attempted = "attempted"; + public const string Guaranteed = "guaranteed"; + public const string NotAttempted = "not-attempted"; + } + + // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.2 + // Section 9.7 of https://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf + // Section 7.8 of https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf + public struct UriAuthentication + { + public const string Basic = "basic"; + public const string Certificate = "certificate"; + public const string Digest = "digest"; + public const string Negotiate = "negotiate"; + public const string None = "none"; + public const string Oauth = "oauth"; + public const string RequestingUserName = "requesting-user-name"; + } + + // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.3 + public struct UriSecurity + { + public const string None = "none"; + public const string Ssl3 = "ssl3"; + public const string Tls = "tls"; + } + + // https://ftp.pwg.org/pub/pwg/ipp/wd/wd-ippnodriver20-20201029.pdf#section-6.6.17 + public struct MultipleOperationTimeoutAction + { + public const string AbortJob = "abort-job"; + public const string HoldJob = "hold-job"; + public const string ProcessJob = "process-job"; + } + + // https://ftp.pwg.org/pub/pwg/ipp/wd/wd-ippepx20-20230206.pdf#section-6.1.3 + // MopriaCloudPrintSpecificationv1.1.027#section 4.10 + public struct JobReleaseAction + { + public const string None = "none"; + public const string ButtonPress = "button-press"; + public const string JobPassword = "job-password"; + public const string OwnerAuthorized = "owner-authorized"; + public const string OwnerAuthorizedBadge = "owner-authorized-badge"; + public const string OwnerAuthorizedUsernamePassword = "owner-authorized-username-password"; + public const string OwnerAuthorizedBiometrics = "owner-authorized-biometrics"; + public const string OwnerAuthorizedOther = "owner-authorized-other"; + public const string WorkflowApp = "workflow-app"; + } + + // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.32 + public struct Compression + { + public const string None = "none"; + public const string Compress = "compress"; + public const string Deflate = "deflate"; + public const string Gzip = "gzip"; + } +} + +#pragma warning restore SA1602 // Enumeration items should be documented +#pragma warning restore SA1600 // Elements should be documented diff --git a/BadgeReleaseDemo/IppLibrary/IPPEncoding.cs b/BadgeReleaseDemo/IppLibrary/IPPEncoding.cs new file mode 100644 index 0000000..86a72e0 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPEncoding.cs @@ -0,0 +1,198 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +#pragma warning disable SA1402 // File may only contain a single type. IPP types, this is fine. +#pragma warning disable SA1602 // Document enum values. IPP types, this is fine. + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using BadgeReleaseDemo.IppLibrary.Common; + + /// + /// The IPP version supported by this library. + /// + public enum IppMajorVersion : sbyte + { + Version1 = 0x1, + Version2 = 0x2, + } + + /// + /// Describes the standard IPP tag types. + /// + public enum Tag + { + // Attribute tags + Reserved = 0x0, + OperationAttributes = 0x1, + JobAttributes = 0x2, + EndOfAttributes = 0x3, + PrinterAttributes = 0x4, + UnsupportedAttributes = 0x5, + SubscriptionAttributes = 0x6, + EventNotificationAttributes = 0x7, + DocumentAttributes = 0x9, + + // RawValue tags + Unsupported = 0x10, + ReservedDefault = 0x11, + Unknown = 0x12, + NoValue = 0x13, + + Integer = 0x21, + Boolean = 0x22, + Enum = 0x23, + + OctetString = 0x30, + DateTime = 0x31, + Resolution = 0x32, + RangeOfInteger = 0x33, + BegCollection = 0x34, + TextWithLanguage = 0x35, + NameWithLanguage = 0x36, + EndCollection = 0x37, + + TextWithoutLanguage = 0x41, + NameWithoutLanguage = 0x42, + Keyword = 0x44, + Uri = 0x45, + UriScheme = 0x46, + Charset = 0x47, + NaturalLanguage = 0x48, + MimeMediaType = 0x49, + + MemberAttrName = 0x4a, + + TypeExtension = 0x7f, // indicates a type beyond 255, first 4 bytes of value field are the tag value. + } + + /// + /// Public facing encoding related utility functions. + /// + public static class IppEncodingUtil + { + public static async Task> DeserializeAttributeGroupAsync(Stream input, IppAttributeGroup newGroup, CancellationToken cancellationToken) + { + var ippStream = new IPPInputStream(input); + return await DeserializeAttributeGroupAsync(ippStream, newGroup, cancellationToken); + } + + /// + /// Deserialize the input stream to IPPAttributeGroups stored in this.AttributeGroups. + /// + public static async Task> DeserializeAttributeGroupAsync(IPPInputStream input, IppAttributeGroup newGroup, CancellationToken cancellationToken) + { + var isOperationGroupRfcCompliant = true; + var isOperationGroup = newGroup.Type == Tag.OperationAttributes; + + // An attribute group contains zero or more attribute fields. + // Read a single "attribute-with-one-value" field; + // this may be followed by one or more "additional-value" fields + // for attributes with multiple values, or it may be a + // begin-attribute-group tag (indicating an empty group) or an + // end-of-attributes tag (indicating the end of attributes) + var tag = await input.ReadTagAsync(cancellationToken); + string nextAttributeName = null; + + while (Helpers.IsValueTag(tag)) + { + var returnedValue = await IppAttribute.DeserializeIppAttributeAsync(input, cancellationToken, tag, nextAttributeName); + var newAttribute = returnedValue.Item1; + tag = returnedValue.Item2; + nextAttributeName = returnedValue.Item3; + + // Per RFC 2911 section 3.1.4 + // "However, for these two attributes within the Operation Attributes group, the order + // is critical.The "attributes-charset" attribute MUST be the first attribute in the group and + // the "attributes-natural-language" attribute MUST be the second attribute in the group" + if (isOperationGroup) + { + // At least for now, we only support UTF-8 charset, otherwise we need to appropriately handle localized text, name encoding and decoding. + if (string.Compare(newAttribute.ValueName, OperationAttributes.AttributesCharset, StringComparison.OrdinalIgnoreCase) == 0 && + string.Compare(newAttribute.Values[0].GetNativeValue(), Constants.CharSet, StringComparison.OrdinalIgnoreCase) != 0) + { + throw new IPPException(BadgeReleaseDemo.IppLibrary.StatusCode.ClientErrorCharsetNotSupported, FormattableString.Invariant($"IPP charset {newAttribute.Values[0].GetNativeValue()} not supported")); + } + + if (newGroup.Attributes.Count == 0 && + string.Compare(newAttribute.ValueName, OperationAttributes.AttributesCharset, StringComparison.Ordinal) != 0) + { + isOperationGroupRfcCompliant = false; + } + + if (newGroup.Attributes.Count == 1 && + string.Compare(newAttribute.ValueName, OperationAttributes.AttributesNaturalLanguage, StringComparison.Ordinal) != 0) + { + isOperationGroupRfcCompliant = false; + } + } + + newGroup.AddAttribute(newAttribute); + } + + return new Tuple(tag, isOperationGroupRfcCompliant); + } + + /// + /// Deserialize a complete IPP attribute group object. Intended to deserialize a single IppAttributeGroup stored on + /// its own, e.g. in the PrinterObject. This will throw if an IPP request or response stream is passed in. + /// + /// The serialized attribute group. + /// The deserialized IPPAttributeGroup object. + public static async Task DeserializeFullAttributeGroupAsync(Stream input, IppAttributeGroup attrGroup, CancellationToken cancellationToken) + { + if (input == null) + { + throw new ArgumentNullException(nameof(input)); + } + + if (attrGroup == null) + { + throw new ArgumentNullException(nameof(attrGroup)); + } + + input.Seek(0, SeekOrigin.Begin); + attrGroup.Type = (Tag)input.ReadByte(); + + // This function is used to deserialize IppAttributeGroup objects, as opposed to deserializing requests and responses. + // Hence it is valid for the stream to end immediately after the begin-attribute-group-tag if the group is empty. + // Read next byte rather than Position and Length because Length can be 0 when HTTP chunked encoding is being used. + if (await input.ReadAsync(new byte[sizeof(byte)], 0, sizeof(byte), cancellationToken) <= 0) + { + return; + } + + // We still need the byte we read above to perform deserialization, so seek back to it. + input.Seek(-1, SeekOrigin.Current); + + // The byte returned by deserialization should be -1 because DeserializeAttributeGroupAsync uses ReadByte, + // which returns -1 when the end of the stream is reached. If a single, serialized attribute group was + // passed in, we should be at the end of the stream once deserialization is finished. + var lastByteRead = (await DeserializeAttributeGroupAsync(input, attrGroup, cancellationToken)).Item1; + if (lastByteRead >= 0) + { + throw new InvalidDataException($"Additional data found after serialized IPP attribute group. First byte of additional data: {lastByteRead}"); + } + } + } + + internal static class IppEncoding + { + public static sbyte IppVersionMajor => (sbyte)IppMajorVersion.Version2; + + public static sbyte IppVersionMinor => 0; + + public static string ContentType => "application/ipp"; + + public static string CharSet => "UTF-8"; + } +} +#pragma warning restore SA1402 // File may only contain a single type +#pragma warning restore SA1602 // Document enum values. IPP types, this is fine. diff --git a/BadgeReleaseDemo/IppLibrary/IPPException.cs b/BadgeReleaseDemo/IppLibrary/IPPException.cs new file mode 100644 index 0000000..7eca277 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPException.cs @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + + /// + /// IPPException is expected to be thrown in any case where an error has occurred + /// that is meant to be surfaced to the calling client via an error response. + /// It encapsulates the IPP status code pertinent to the error as well as an associated + /// 'friendly' message (as well as an optional "detailed" status). + /// + public class IPPException : Exception + { + public IPPException(StatusCode statusCode, string message, IppAttributeGroup unsupportedAttributes = null) + : base(message) + { + this.StatusCode = statusCode; + this.DetailedStatusMessage = message; + this.UnsupportedAttributes = unsupportedAttributes; + } + + public StatusCode StatusCode { get; } + + public string DetailedStatusMessage { get; } + + /// + /// Gets or sets the IPP major version. + /// + public sbyte IPPRequestMajorVersion { get; set; } + + /// + /// Gets or sets the requested IPP minor version. + /// + public sbyte IPPRequestMinorVersion { get; set; } + + /// + /// Gets or sets the request id in IPP header. + /// + public int IPPRequestId { get; set; } + + /// + /// Gets or sets a value indicating whether the request was from printer. + /// + public bool IsRequestFromPrinter { get; set; } + + public string DetailedInternalInfo { get; set; } + + /// + /// Unsupported attributes group, to be sent in response in case of exception + /// https://datatracker.ietf.org/doc/html/rfc8011#section-4.1.7 + /// + public IppAttributeGroup UnsupportedAttributes { get; set; } + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs b/BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs new file mode 100644 index 0000000..5856a70 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs @@ -0,0 +1,40 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + + /// + /// Factory helper + /// + public static class IppFactoryHelper + { + /// + /// Create an instance of IIppRequestFactory class. + /// + /// The host of the cloud printer service. e.g., printer.microsoft.com. + /// The cloud device UUID. + /// For requesting-user-name attribute. + /// For requesting-user-uri attribute. + /// an instance of the factory + public static IIppRequestFactory CreateIppRequestFactory(string host, string printerId, string requestingUserName, string requestingUserUri) + { + // Below is text from RFC 8011. Essentially, the printer URI must be a URL. + // https://tools.ietf.org/html/rfc8011#section-4.1.5 regarding printer URI: + // In all cases, the target URIs contained within the body of IPP + // operation requests and responses MUST be in absolute format rather + // than relative format(a relative URL identifies a resource with the + // scope of the HTTP server, but does not include scheme, host, + // or port). + var printerUri = "ipps://" + host + "/printers/" + printerId; + return new IppRequestFactory() + { + PrinterUri = new Uri(printerUri), + RequestingUserName = requestingUserName, + RequestingUserUri = requestingUserUri + }; + } + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IPPInputStream.cs b/BadgeReleaseDemo/IppLibrary/IPPInputStream.cs new file mode 100644 index 0000000..62608d4 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPInputStream.cs @@ -0,0 +1,183 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.IO; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + + public class IPPInputStream : IDisposable + { + public IPPInputStream(Stream inputStream) + { + this.Stream = inputStream; + this.BytesRead = 0; + } + + public long BytesRead { get; private set; } + + // would have preferred to keep Stream as private variable, however, need to keep this as public 'get' because it is assigned to IPPRequest.Data for reading print document payload + public Stream Stream { get; private set; } + + public void Dispose() + { + if (this.Stream != null) + { + this.Stream.Dispose(); + this.Stream = null; + } + } + + public async Task ReadNetworkShortAsync(CancellationToken cancellationToken) + { + var buffer = await this.ReadInternalAsync(sizeof(short), cancellationToken); + return (short)((buffer[0] << 8) | buffer[1]); + } + + public async Task ReadNetworkIntegerAsync(CancellationToken cancellationToken) + { + byte[] buffer = await this.ReadInternalAsync(sizeof(Int32), cancellationToken); + return (int)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]); + } + + public async Task ReadTagAsync(CancellationToken cancellationToken) + { + try + { + byte[] buffer = await this.ReadInternalAsync(sizeof(byte), cancellationToken); + return (Tag)buffer[0]; + } + catch (InvalidDataException) + { + // mocking the behavior for (Tag)input.ReadByte() when end of stream is reached + return (Tag)(-1); + } + } + + public async Task ReadSbyteAsync(CancellationToken cancellationToken) + { + var buffer = await this.ReadInternalAsync(sizeof(sbyte), cancellationToken); + return (sbyte)buffer[0]; + } + + /// + /// Reads an IPP string (ANSI) from the stream. + /// + /// + /// + /// + public async Task ReadStringAsync(int length, CancellationToken cancellationToken) + { + StringBuilder sb = new StringBuilder(); + + byte[] buffer = await this.ReadInternalAsync(length, cancellationToken); + + for (int i = 0; i < length; i++) + { + sb.Append((char)buffer[i]); + } + + return sb.ToString(); + } + + public async Task ReadAsync(int count, CancellationToken cancellationToken) + { + return await this.ReadInternalAsync(count, cancellationToken); + } + + /// + /// see: ReadNumberOfBytesOrEndOfStreamInternalAsync() + /// + public async Task ReadNumberOfBytesOrEndOfStreamAsync(int count, CancellationToken cancellationToken) + { + byte[] buffer = new byte[count]; + return await this.ReadNumberOfBytesOrEndOfStreamInternalAsync(buffer, cancellationToken); + } + + /// + /// Reads the stream and creates a buffer with a maximum of given number of bytes + /// + /// Caller provided buffer. + /// Cancellation token + /// Byte array with the content read from buffer + public async Task ReadNumberOfBytesOrEndOfStreamAsync(byte[] buffer, CancellationToken cancellationToken) + { + return await this.ReadNumberOfBytesOrEndOfStreamInternalAsync(buffer, cancellationToken); + } + + /// + /// Reads a give number of bytes from the stream + /// + /// Number of bytes to be read. + /// Cancellation token + /// Byte array with the content read from buffer + private async Task ReadInternalAsync(int count, CancellationToken cancellationToken) + { + int bytesLeftToRead = count; + byte[] buffer = new byte[count]; + var bytesRead = 0; + while (bytesLeftToRead > 0) + { + bytesRead = await this.Stream.ReadAsync(buffer, count - bytesLeftToRead, bytesLeftToRead, cancellationToken); + + if (bytesRead == bytesLeftToRead) + { + break; + } + else if (bytesRead > 0) + { + bytesLeftToRead -= bytesRead; + } + else + { + throw new InvalidDataException("Buffer returned 0 bytes"); + } + } + + this.BytesRead += count; + return buffer; + } + + /// + /// Reads the stream and creates a buffer with a maximum of given number of bytes + /// + /// Caller provided buffer. + /// Cancellation token + /// Byte array with the content read from buffer + private async Task ReadNumberOfBytesOrEndOfStreamInternalAsync(byte[] buffer, CancellationToken cancellationToken) + { + int count = buffer.Length; + int bytesLeftToRead = count; + var bytesRead = 0; + var totalBytesRead = 0; + while (bytesLeftToRead > 0) + { + bytesRead = await this.Stream.ReadAsync(buffer, count - bytesLeftToRead, bytesLeftToRead, cancellationToken); + totalBytesRead += bytesRead; + + if (bytesRead == bytesLeftToRead) + { + break; + } + else if (bytesRead > 0) + { + bytesLeftToRead -= bytesRead; + } + else + { + Array.Resize(ref buffer, totalBytesRead); + break; + } + } + + this.BytesRead += totalBytesRead; + return buffer; + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPJob.cs b/BadgeReleaseDemo/IppLibrary/IPPJob.cs new file mode 100644 index 0000000..501d3cb --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPJob.cs @@ -0,0 +1,227 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + + /// + /// An object of IPP job description and status attribute as described in + /// https://tools.ietf.org/html/rfc8011#section-5.3. + /// + public class IppJob + { + public const int JobStateUnknown = -1; + + public const int JobIdUnknown = 0; + + /// + /// Initializes a new instance of the class. + /// + /// Optional job attributes. + /// Optional operation attributes. + public IppJob(IppAttributeGroup jobAttributeGroup = null, IppAttributeGroup operationAttributeGroup = null) + { + if (jobAttributeGroup == null) + { + return; + } + + this.JobAttributeGroup = jobAttributeGroup; + + this.OperationAttributeGroup = operationAttributeGroup; + + this.JobStateReasons = new List(); + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobUri)) + { + this.JobUri = (string)jobAttributeGroup.Attributes[Common.JobAttributes.JobUri].FirstValue.GetNativeValue(); + } + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobId)) + { + this.JobId = (int)jobAttributeGroup.Attributes[Common.JobAttributes.JobId].FirstValue.GetNativeValue(); + } + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobState)) + { + this.JobState = (int)jobAttributeGroup.Attributes[Common.JobAttributes.JobState].FirstValue.GetNativeValue(); + } + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobName)) + { + this.JobName = jobAttributeGroup.Attributes[Common.JobAttributes.JobName].FirstValue.GetNativeValue().ToString(); + } + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobOriginatingUserName)) + { + this.JobOriginatingUserName = jobAttributeGroup.Attributes[Common.JobAttributes.JobOriginatingUserName].FirstValue.GetNativeValue().ToString(); + } + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobOriginatingUserUri)) + { + this.JobOriginatingUserUri = new Uri(jobAttributeGroup.Attributes[Common.JobAttributes.JobOriginatingUserUri].FirstValue.GetNativeValue().ToString()); + } + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobStateReasons)) + { + var jobStateReasonsAttributeValues = jobAttributeGroup.Attributes[Common.JobAttributes.JobStateReasons].Values; + foreach (var value in jobStateReasonsAttributeValues) + { + this.JobStateReasons.Add(value.GetNativeValue().ToString()); + } + } + + // DateTimeAtCreation is always available for a job. + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.DateTimeAtCreation)) + { + this.DateTimeAtCreation = jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtCreation].FirstValue.GetNativeValue().ToString(); + } + + // Service returns unknown ipp type if job is not yet processed. + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.DateTimeAtProcessing) && + jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtProcessing].FirstValue.ValueType == Tag.DateTime) + { + this.DateTimeAtProcessing = jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtProcessing].FirstValue.GetNativeValue().ToString(); + } + else + { + this.DateTimeAtProcessing = string.Empty; + } + + // Service returns unknown ipp type if job is not yet completed. + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.DateTimeAtCompleted) && + jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtCompleted].FirstValue.ValueType == Tag.DateTime) + { + this.DateTimeAtCompleted = jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtCompleted].FirstValue.GetNativeValue().ToString(); + } + else + { + this.DateTimeAtCompleted = string.Empty; + } + + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.TimeAtCreation)) + { + this.TimeAtCreation = this.GetIntOrUnknownValue(jobAttributeGroup, Common.JobAttributes.TimeAtCreation); + } + + // Service returns no-value ipp type if job is not yet processed. + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.TimeAtProcessing) && + jobAttributeGroup.Attributes[Common.JobAttributes.TimeAtProcessing].FirstValue.ValueType == Tag.Integer) + { + this.TimeAtProcessing = this.GetIntOrUnknownValue(jobAttributeGroup, Common.JobAttributes.TimeAtProcessing); + } + else + { + this.TimeAtProcessing = 0; + } + + // Service returns no-value ipp type if job is not yet completed. + if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.TimeAtCompleted) && + jobAttributeGroup.Attributes[Common.JobAttributes.TimeAtCompleted].FirstValue.ValueType == Tag.Integer) + { + this.TimeAtCompleted = this.GetIntOrUnknownValue(jobAttributeGroup, Common.JobAttributes.TimeAtCompleted); + } + else + { + this.TimeAtCompleted = 0; + } + } + + /// + /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.2 + /// + public string JobUri { get; set; } = string.Empty; + + /// + /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.1 + /// + public int JobId { get; set; } = JobIdUnknown; + + /// + /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.7 + /// + public int JobState { get; set; } = JobStateUnknown; + + /// + /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.8 + /// + public List JobStateReasons { get; set; } + + /// + /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.5 + /// + public string JobName { get; set; } = string.Empty; + + /// + /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.6 + /// + public string JobOriginatingUserName { get; set; } = string.Empty; + + /// + /// Gets or sets the: https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf + /// section 5.3.3. + /// + public Uri JobOriginatingUserUri { get; set; } = null; + + /// + /// Gets or sets the creation date time. https://tools.ietf.org/html/rfc8011#section-5.3.14.5 + /// + public string DateTimeAtCreation { get; set; } + + /// + /// Gets or sets the job processing date time. https://tools.ietf.org/html/rfc8011#section-5.3.14.6 + /// + public string DateTimeAtProcessing { get; set; } + + /// + /// Gets or sets the job completed date time. https://tools.ietf.org/html/rfc8011#section-5.3.14.7 + /// + public string DateTimeAtCompleted { get; set; } + + /// + /// Gets or sets the creation time. https://tools.ietf.org/html/rfc8011#section-5.3.14.1 + /// + public int TimeAtCreation { get; set; } = 0; + + /// + /// Gets or sets the processing time. https://tools.ietf.org/html/rfc8011#section-5.3.14.2 + /// + public int TimeAtProcessing { get; set; } = 0; + + /// + /// Gets or sets the completed time. https://tools.ietf.org/html/rfc8011#section-5.3.14.3 + /// + public int TimeAtCompleted { get; set; } = 0; + + /// + /// Gets the complete job attributes. + /// + public IppAttributeGroup JobAttributeGroup { get; } = null; + + /// + /// Gets the complete operation attributes. + /// + public IppAttributeGroup OperationAttributeGroup { get; } = null; + + /// + /// If attribute is unknown then return 0 otherwise attribute value. + /// + private int GetIntOrUnknownValue(IppAttributeGroup jobAttributeGroup, string attributeName) + { + object value = jobAttributeGroup.Attributes[attributeName].FirstValue.GetNativeValue(); + int returnInt = 0; + + if (value != null) + { + returnInt = (int)value; + } + + return returnInt; + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs b/BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs new file mode 100644 index 0000000..32899f3 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs @@ -0,0 +1,154 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + + public class IppMemberAttribute + { + private readonly IppAttribute MemberAttributeImpl; + + /// + /// Initializes a new instance of the class with no values and member attributes. + /// + public IppMemberAttribute(string attributeName) + { + this.MemberAttributeImpl = new IppAttribute(attributeName); + } + + /// + /// Initializes a new instance of the class with a value. + /// + public IppMemberAttribute(string attributeName, IppValue value) + : this(attributeName, new List { value }) + { + } + + /// + /// Initializes a new instance of the class with a list of values. + /// + public IppMemberAttribute(string attributeName, List values) + { + this.MemberAttributeImpl = new IppAttribute(attributeName, values); + } + + /// + /// Gets the values of the attribute. + /// + public IReadOnlyList Values => this.MemberAttributeImpl.Values; + + /// + /// Gets the name of the value, i.e., attribute name. + /// + public string ValueName => this.MemberAttributeImpl.ValueName; + + /// + /// Gets the the first value. + /// + public IppValue FirstValue => this.Values.First(); + + /// + /// Serialize a member attribute. + /// + public void SerializeMemberAttribute(Stream output, int collectionDepth) + { + if (this.Values.Count == 0) + { + throw new Exception("Values of a member attribute cannot be empty"); + } + + // Setup the member attribute: https://tools.ietf.org/html/rfc8010#section-3.1.6 + this.Values.ElementAt(0).SerializeAsMemberAttribute(output, this.ValueName, collectionDepth); // The first attribute value carries the name of the attribute + + for (int i = 1; i < this.Values.Count; i++) + { + this.Values.ElementAt(i).SerializeAsMemberAttribute(output, string.Empty, collectionDepth); // Additional attribute values use a name length of 0x0000. + } + } + + /// + /// Add additional value to the attribute. + /// + /// The value to add. + public void AddAdditionalValue(IppValue ippValue) + { + this.MemberAttributeImpl.AddAdditionalValue(ippValue); + } + + /// + /// Gets value indicating whether this is a collection attribute. + /// + public bool IsCollectionAttribute() + { + return this.MemberAttributeImpl.IsCollectionAttribute; + } + + /// + /// Compare two attributes. + /// + public override bool Equals(object obj) + { + if (obj is IppMemberAttribute other) + { + var isAttributeNameEqual = this.ValueName.Equals(other.ValueName, StringComparison.Ordinal); + var areValuesEqual = this.Values.SequenceEqual(other.Values); + return isAttributeNameEqual && areValuesEqual; + } + + return false; + } + + /// + /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. + /// Attribute comparison is used by test code. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + /// Serialize the member attribute to string. + /// + /// String representation of the attribute. + public override string ToString() + { + if (this.Values.Count == 0) + { + throw new Exception("Values of a member attribute cannot be empty"); + } + + if (this.Values.Count > 1) + { + var sb = new StringBuilder(); + if (string.IsNullOrEmpty(this.ValueName)) + { + sb.Append(string.Empty); + } + + sb.AppendFormat(CultureInfo.InvariantCulture, "Member Attribute {0} - Multiple Values:\n", this.ValueName); + + foreach (var value in this.Values) + { + sb.Append("\t\t" + value.ToString()); + } + + return sb.ToString(); + } + else + { + // Single valued attribute + return string.Format(CultureInfo.InvariantCulture, "\t\tMember Attribute {0}: {1}", this.ValueName, this.FirstValue.ToString()); + } + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPRequest.cs b/BadgeReleaseDemo/IppLibrary/IPPRequest.cs new file mode 100644 index 0000000..96d946f --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPRequest.cs @@ -0,0 +1,352 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using BadgeReleaseDemo.IppLibrary.Common; + + /// + /// Represents a raw IPP request and provides deserialization from + /// stream representations. + /// No semantic validation is done here, merely conformance to RFC 2910. + /// (See section 3 here: https://tools.ietf.org/html/rfc2910) + /// + public class IppRequest : RawIppEncodingBase + { + /// + /// The input stream for an incoming IPP request. + /// + private readonly IPPInputStream input; + + /// + /// Initializes a new instance of the class. + /// + private IppRequest() + { + } + + /// + /// Initializes a new instance of the class. + /// Private constructor. An IPPRequest object is created by CreateIppRequestFromStreamAsync(). + /// + private IppRequest(Stream input) + { + this.input = new IPPInputStream(input) ?? throw new InvalidOperationException("IPPRequest requires input stream."); + this.BaseAttributeGroups = new List(); + } + + /// + /// Gets the number of bytes read from the request stream + /// + public long BytesRead { get => this.input.BytesRead; } + + /// + /// Create a new IppRequest object with print payload. + /// + /// The operation id of the request. + /// The request id of the request. + /// The print payload. + /// + public static Task CreateAsync(Operation operationId, int requestId, Stream printPayload) + { + var request = new IppRequest() + { + OperationId = operationId, + RequestId = requestId, + MajorVersionNumber = (sbyte)IppEncoding.IppVersionMajor, + MinorVersionNumber = (sbyte)IppEncoding.IppVersionMinor, + Data = printPayload + }; + + return Task.FromResult(request); + } + + /// + /// Create a new IppRequest object without print payload. + /// + /// The operation id of the request. + /// The request id of the request. + /// + public static Task CreateAsync(Operation operationId, int requestId) + { + var request = new IppRequest() + { + OperationId = operationId, + RequestId = requestId, + MajorVersionNumber = (sbyte)IppEncoding.IppVersionMajor, + MinorVersionNumber = (sbyte)IppEncoding.IppVersionMinor, + }; + + return Task.FromResult(request); + } + + /// + /// Create a new IPP request from incoming stream. + /// + public static async Task CreateIppRequestFromStreamAsync(Stream requestStream, CancellationToken cancellationToken, Func throttlingControl = null) + { + var newIppRequest = new IppRequest(requestStream); + await newIppRequest.DeserializeAsync(cancellationToken, throttlingControl); + return newIppRequest; + } + + /// + /// Create a new IPP request from incoming stream. + /// + public static async Task CreateAsync(Stream requestStream) + { + return await CreateIppRequestFromStreamAsync(requestStream, new CancellationTokenSource().Token); + } + + /// + /// Gets or sets a value indicating whether the deserialized Operation attribute group is compliant with RFC. + /// Specifically: RFC 2911 requires first and second attributes of Operation groups to be + /// attributes-charset and attributes-natural-language respectively. + /// + public bool IsOperationGroupAttributesRfcCompliant { get; set; } = true; + + /// + /// Gets the requested operation. + /// + public Operation OperationId { get; set; } + + /// + /// Gets the request id. + /// + public int RequestId { get; set; } + + /// + /// Gets the attribute groups in the request. + /// + public List AttributeGroups => this.BaseAttributeGroups; + + public override void Dispose() + { + this.input?.Dispose(); + base.Dispose(); + } + + public List LookupAttributeGroup(Tag type) + { + var matchedAttributes = new List(); + foreach (var attribute in this.BaseAttributeGroups) + { + if (attribute.Type == type) + { + matchedAttributes.Add(attribute); + } + } + + return matchedAttributes; + } + + /// + /// Returns a string that represents the IPPRequest object. + /// + /// + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat(CultureInfo.InvariantCulture, "Operation ID: {0}\n", this.OperationId); + sb.AppendFormat(CultureInfo.InvariantCulture, "Request ID: {0}\n", this.RequestId); + sb.Append(base.ToString()); + return sb.ToString(); + } + + /// + /// Must have required attributes. + /// + public void CheckOperationAttributes() + { + if (this.RequestId <= 0) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Request id must be a positive integer."); + } + + List operationAttributesList = this.LookupAttributeGroup(Tag.OperationAttributes); + if (operationAttributesList.Count == 0) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "No operation-attributes group."); + } + + IppAttributeGroup operationAttributes = operationAttributesList[0]; + if (!operationAttributes.Attributes.ContainsKey(RequiredAttributes.AttributesCharset)) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "No attributes-charset operation attribute."); + } + + if (!operationAttributes.Attributes.ContainsKey(RequiredAttributes.AttributesNaturalLanguage)) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "No attributes-natural-language operation attribute."); + } + + if (!this.IsOperationGroupAttributesRfcCompliant) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Incorrect order of attributes-charset and attributes-natural-language"); + } + } + + /// + /// Deserialize IPPRequest from stream. + /// + /// A task. + public async Task DeserializeAsync(CancellationToken cancellationToken, Func throttlingControl = null) + { + // Parse the header blob + this.MajorVersionNumber = await this.input.ReadSbyteAsync(cancellationToken); + this.MinorVersionNumber = await this.input.ReadSbyteAsync(cancellationToken); + this.OperationId = (Operation)await this.input.ReadNetworkShortAsync(cancellationToken); + this.RequestId = await this.input.ReadNetworkIntegerAsync(cancellationToken); + + if (throttlingControl != null) + { + await throttlingControl.Invoke(this.OperationId); + } + + // Read zero or more attributes from the request + var tag = await this.input.ReadTagAsync(cancellationToken); + + while (tag != Tag.EndOfAttributes) + { + // This should be a "begin-attribute-group" tag specifying the type. + if (!Helpers.IsBeginAttributeGroupTag(tag)) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Expected a begin-attribute-group tag here."); + } + + // Form a new attribute group. + var newGroup = new IppAttributeGroup(tag); + + var groupProps = await IppEncodingUtil.DeserializeAttributeGroupAsync(this.input, newGroup, cancellationToken); + var rfcCompliant = groupProps.Item2; + tag = groupProps.Item1; + + if (!rfcCompliant) + { + this.IsOperationGroupAttributesRfcCompliant = false; + } + + // Duplicate attribute groups are not expressly prohibited. If this is a duplicate group, + // merge the attributes from the new group into the existing group. See Bug #39498460 + var existingGroups = this.LookupAttributeGroup(newGroup.Type); + if (existingGroups.Count == 0) + { + this.BaseAttributeGroups.Add(newGroup); + } + else if (existingGroups.Count == 1) + { + foreach (var attribute in newGroup.Attributes) + { + existingGroups[0].AddAttribute(attribute.Value); + } + } + else // unreachable + { + throw new Exception("Serialization failed to merge duplicate attribute groups."); + } + } + + // We have read in all attribute-groups at this point. If there is anything left, it is the "data" field. + this.Data = this.input.Stream; + + // Done. + } + + /// + /// Serialize IPPRequest per RFC 8010 (https://tools.ietf.org/html/rfc8010). + /// + public Stream Serialize() + { + Stream memoryStream = new MemoryStream(); + + // IPP requestHeader + memoryStream.WriteByte((byte)this.MajorVersionNumber); + memoryStream.WriteByte((byte)this.MinorVersionNumber); + Helpers.WriteNetworkShort(memoryStream, (short)this.OperationId); + Helpers.WriteNetworkInteger(memoryStream, this.RequestId); + + foreach (var attributeGroup in this.AttributeGroups) + { + attributeGroup.Serialize(memoryStream); + } + + // End of attributes. + memoryStream.WriteByte((byte)Tag.EndOfAttributes); + + // Copy the print payload if any. + if (this.Data != null) + { + this.Data.Seek(0, SeekOrigin.Begin); + this.Data.CopyTo(memoryStream); + } + + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + /// + /// Compare two attributes. + /// + public override bool Equals(object obj) + { + if (obj is IppRequest other) + { + if (this.OperationId != other.OperationId) + { + return false; + } + + if (this.RequestId != other.RequestId) + { + return false; + } + + if (this.MajorVersionNumber != other.MajorVersionNumber || this.MinorVersionNumber != other.MinorVersionNumber) + { + return false; + } + + if (!this.AttributeGroups.SequenceEqual(other.AttributeGroups)) + { + return false; + } + + // just compare size of the payload. + if (this.Data != null) + { + var otherDataLength = other.Data.Length - other.Data.Position; + if (this.Data.Length != otherDataLength) + { + return false; + } + } + } + else + { + return false; + } + + return true; + } + + /// + /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. + /// Attribute comparison is used by test code. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs b/BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs new file mode 100644 index 0000000..cb06aec --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs @@ -0,0 +1,892 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + using BadgeReleaseDemo.IppLibrary.Common; + + /// + /// Provides implementations for standard IPP INFRA operations + /// + internal class IppRequestFactory : IIppRequestFactory + { + /// + /// Gets or sets iPP requesting-user-name. + /// + public string RequestingUserName { get; set; } + + /// + /// Gets or sets iPP-INFRA requesting-user-uri. + /// + public string RequestingUserUri { get; set; } + + /// + /// Gets or sets iPP printer_uri. + /// + public Uri PrinterUri { get; set; } + + /// + /// Create Cancel-Job request. + /// + /// Request id of the ipp request. + /// + /// See IPP-INFRA section 5. + /// The id of the job. + /// The uri of the job. + /// + public async Task CreateCancelJobRequestAsync( + int requestId, + string requestingUserName, + string requestingUserUri, + int jobId, + string jobUri) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + + if (string.IsNullOrEmpty(jobUri)) + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); + } + else + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.JobUri, IppValue.CreateURIValue(jobUri))); + } + + var request = await IppRequest.CreateAsync(Operation.CancelJob, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create Get-Printer-Attributes request. + /// + /// Request id of the ipp request. + /// + /// See IPP-INFRA section 5. + /// Additional operation attributes to included. + /// Printer attributes requested. + /// + public async Task CreateGetPrinterAttributesRequestAsync( + int requestId, + string requestingUserName, + string requestingUserUri, + List extraOperationAttributes, + List requestedAttributes) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + + if (extraOperationAttributes != null + && extraOperationAttributes.Count != 0) + { + foreach (var attr in extraOperationAttributes) + { + operationAttributeGroup.AddAttribute(attr); + } + } + + /* RequestedAttributes is optional according to IPP. If requestedAttributes is null, IPP service should consider it as requesting "ALL" attributes + and should return all attributes of printer. Currently, MPS is not doing this and a bug has been created for same. Bug 20689820 */ + this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); + var request = await IppRequest.CreateAsync(Operation.GetPrinterAttributes, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create Get-Output-Device-Attributes request. + /// + /// Request id of the ipp request. + /// + /// See IPP-INFRA section 5. + /// Additional operation attributes to included. + /// Printer attributes requested. + /// + public async Task CreateGetOutputDeviceAttributesRequestAsync( + int requestId, + string requestingUserName, + string requestingUserUri, + List extraOperationAttributes, + List requestedAttributes) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + + if (extraOperationAttributes != null) + { + foreach (var attr in extraOperationAttributes) + { + operationAttributeGroup.AddAttribute(attr); + } + } + + this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); + var request = await IppRequest.CreateAsync(Operation.GetOutputDeviceAttributes, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create Set-Printer-Attributes request. + /// + /// Request id of the ipp request. + /// + /// See IPP-INFRA section 5. + /// Attributes to set + /// + public async Task CreateSetPrinterAttributesRequestAsync( + int requestId, + string requestingUserName, + string requestingUserUri, + IEnumerable attributesToSet) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + + var request = await IppRequest.CreateAsync(Operation.SetPrinterAttributes, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + + var attributesGrp = new IppAttributeGroup(Tag.PrinterAttributes); + + foreach (var attr in attributesToSet) + { + attributesGrp.AddAttribute(attr); + } + + request.AttributeGroups.Add(attributesGrp); + + return request; + } + + /// + /// Create Get-Printer-Supported-Values request. + /// + /// Request id of the ipp request. + /// + /// See IPP-INFRA section 5. + /// Printer attributes requested. + /// + public async Task CreateGetSupportedPrinterAttributesRequestAsync( + int requestId, + string requestingUserName, + string requestingUserUri, + List requestedAttributes) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + + if (requestedAttributes != null) + { + this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); + } + + var request = await IppRequest.CreateAsync(Operation.GetPrinterSupportedValues, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create Get-Printer-Device-Capabilities request. + /// + /// Request id of the ipp request. + /// + /// See IPP-INFRA section 5. + public async Task CreateGetPrinterDeviceCapabilitiesRequestAsync( + int requestId, + string requestingUserName, + string requestingUserUri) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + + var request = await IppRequest.CreateAsync(Operation.GetPrintDeviceCapabilities, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + +#pragma warning disable SA1515 // Single-line comment must be preceded by blank line +#pragma warning disable SA1614 // Element parameter documentation must have text + /// + /// Create Print-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.1). + /// + /// Request id of the IPP packet. + /// + /// + /// + /// + /// + /// representation + public async Task CreatePrintJobRequestAsync( + int requestId, + string jobName, + string documentName, + Stream documentPayload, + IppAttributeGroup extraOperationAttributes = null, + IppAttributeGroup jobTemplateAttributes = null) +#pragma warning restore SA1614 // Element parameter documentation must have text +#pragma warning restore SA1515 // Single-line comment must be preceded by blank line + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobName, IppValue.CreateURIValue(jobName))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentName, IppValue.CreateURIValue(documentName))); + + if (extraOperationAttributes != null) + { + foreach (var attr in extraOperationAttributes.Attributes) + { + operationAttributeGroup.AddAttribute(attr.Value); + } + } + + var request = await IppRequest.CreateAsync(Operation.PrintJob, requestId, documentPayload); + request.AttributeGroups.Add(operationAttributeGroup); + + if (jobTemplateAttributes != null) + { + request.AttributeGroups.Add(jobTemplateAttributes); + } + + return request; + } + +#pragma warning disable SA1515 // Single-line comment must be preceded by blank line +#pragma warning disable SA1614 // Element parameter documentation must have text + /// + /// Create Validate-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.3). + /// + /// Request id of the IPP packet. + /// + /// + /// + /// + /// representation + public async Task CreateValidateJobRequestAsync( + int requestId, + string jobName, + string documentName, + IppAttributeGroup extraOperationAttributes = null, + IppAttributeGroup jobTemplateAttributes = null) +#pragma warning restore SA1614 // Element parameter documentation must have text +#pragma warning restore SA1515 // Single-line comment must be preceded by blank line + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobName, IppValue.CreateURIValue(jobName))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentName, IppValue.CreateURIValue(documentName))); + + if (extraOperationAttributes != null) + { + foreach (var attr in extraOperationAttributes.Attributes) + { + operationAttributeGroup.AddAttribute(attr.Value); + } + } + + var request = await IppRequest.CreateAsync(Operation.ValidateJob, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + + if (jobTemplateAttributes != null) + { + request.AttributeGroups.Add(jobTemplateAttributes); + } + + return request; + } + +#pragma warning disable SA1515 // Single-line comment must be preceded by blank line +#pragma warning disable SA1614 // Element parameter documentation must have text + /// + /// Create Create-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.4). + /// + /// Request id of the IPP packet. + /// + /// + /// representation + public async Task CreateCreateJobRequestAsync( + int requestId, + string jobName = null, + IppAttributeGroup jobTemplateAttributes = null) +#pragma warning restore SA1614 // Element parameter documentation must have text +#pragma warning restore SA1515 // Single-line comment must be preceded by blank line + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + + // As per https://www.rfc-editor.org/rfc/rfc8011.html#section-4.1.5, printer-uri must be third operation attribute. + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + this.AddUserNameAndUserUri(operationAttributeGroup); + if (jobName != null) + { + operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobName, IppValue.CreateURIValue(jobName))); + } + + var request = await IppRequest.CreateAsync(Operation.CreateJob, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + if (jobTemplateAttributes != null) + { + request.AttributeGroups.Add(jobTemplateAttributes); + } + + return request; + } + +#pragma warning disable SA1515 // Single-line comment must be preceded by blank line +#pragma warning disable SA1614 // Element parameter documentation must have text + /// + /// Create Send-Document request. + /// + /// requestId + /// + /// jobUri + /// jobId + /// documentPayload + /// Attributes to be included in the operation attribute group. + /// The job template attributes to be included in the request. + /// representation + public async Task CreateSendDocumentRequestAsync( + int requestId, + bool lastDocument, + string jobUri = null, + int jobId = 0, + Stream documentPayload = null, + IppAttributeGroup extraOperationAttributes = null, + IppAttributeGroup jobTemplateAttributes = null) +#pragma warning restore SA1614 // Element parameter documentation must have text +#pragma warning restore SA1515 // Single-line comment must be preceded by blank line + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddJobUriOrId(jobUri, jobId, operationAttributeGroup); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.LastDocument, IppValue.CreateBooleanValue(lastDocument))); + if (extraOperationAttributes != null) + { + foreach (var attr in extraOperationAttributes.Attributes) + { + operationAttributeGroup.AddAttribute(attr.Value); + } + } + + var request = await IppRequest.CreateAsync(Operation.SendDocument, requestId, documentPayload); + request.AttributeGroups.Add(operationAttributeGroup); + if (jobTemplateAttributes != null) + { + request.AttributeGroups.Add(jobTemplateAttributes); + } + + return request; + } + + /// + /// Create Close-Job request. + /// + /// requestId + /// jobUri + /// jobId + /// representation + public async Task CreateCloseJobRequestAsync( + int requestId, + string jobUri = null, + int jobId = 0) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddJobUriOrId(jobUri, jobId, operationAttributeGroup); + this.AddUserNameAndUserUri(operationAttributeGroup); + + var request = await IppRequest.CreateAsync(Operation.CloseJob, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + +#pragma warning disable SA1515 // Single-line comment must be preceded by blank line +#pragma warning disable SA1614 // Element parameter documentation must have text + /// + /// Create Get-Job-AttributeGroups operation (https://tools.ietf.org/html/rfc8011#section-4.3.4). + /// + /// Request id of the IPP packet. + /// + /// + /// representation + public async Task CreateGetJobAttributesRequestAsync(int requestId, string jobUri, List requestedAttributes) +#pragma warning restore SA1614 // Element parameter documentation must have text +#pragma warning restore SA1515 // Single-line comment must be preceded by blank line + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobUri, IppValue.CreateURIValue(jobUri))); + + this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); + var request = await IppRequest.CreateAsync(Operation.GetJobAttributes, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create Get-Jobs request. + /// + /// Request id of the IPP packet. + /// + /// Determine the value of "which-jobs" + /// See: section 8.2 http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf + /// + /// See IPP-INFRA section 5. + /// All of the new operations defined in this section are sent by the Proxy to the Infrastructure + /// Printer. For each operation, the "requesting-user-name" [RFC2911] and "requesting-useruri" + /// [PWG5100.13] operation attributes provide the unauthenticated identity of the Proxy + /// owner, e.g., "Jane Smith" and "mailto:jane.smith@example.com". + /// + /// See IPP-INFRA section 5. + /// Value for printer-uri attribute. + /// Value for output-device-uuid attribute (required for get-jobs requests from IPP-INFRA printers). + /// Job attributes requested. + /// representation + public async Task CreateGetJobsRequestAsync( + int requestId, + string jobType, + string requestingUserName, + string requestingUserUri, + string printerUri, + string outputDeviceUuid, + List requestedAttributes) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + + if (!string.IsNullOrEmpty(requestingUserUri)) + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.MyJobs, IppValue.CreateBooleanValue(true))); + } + + // Use printerUri value if provided. + var printerUriToUse = string.IsNullOrEmpty(printerUri) ? this.PrinterUri.ToString() : printerUri; + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(printerUriToUse))); + + // Add output-device-uuid if provided. + if (!string.IsNullOrEmpty(outputDeviceUuid)) + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + } + + if (!string.IsNullOrEmpty(jobType)) + { + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.WhichJobs, IppValue.CreateKeywordValue(jobType))); + } + + this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); + var request = await IppRequest.CreateAsync(Operation.GetJobs, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Creates a Fetch-Job request + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The id of the job. + /// representation + public async Task CreateFetchJobRequestAsync(int requestId, string outputDeviceUuid, int jobId) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); + + var request = await IppRequest.CreateAsync(Operation.FetchJob, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create IPP-INFRA Acknowledge-Job request. + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The id of the job. + /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. + /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. again + /// representation + public async Task CreateAcknowledgeJobRequestAsync(int requestId, string outputDeviceUuid, int jobId, StatusCode fetchStatusCode, string fetchStatusMessage) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); + + // Undefined means the caller is not sending any value for fetchStatusCode. + if (fetchStatusCode != StatusCode.Undefined) + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.FetchStatusCode, IppValue.CreateEnumValue((int)fetchStatusCode))); + } + + // Include optional fetch-status-message attribute. + if (string.IsNullOrEmpty(fetchStatusMessage) == false) + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.FetchStatusMessage, IppValue.CreateTextWithoutLanguageValue(fetchStatusMessage))); + } + + var request = await IppRequest.CreateAsync(Operation.AcknowledgeJob, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create IPP-INFRA Acknowledge-Document request. + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The id of the job. + /// The document number (a job can have multiple documents). + /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. + /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. again + /// representation + public async Task CreateAcknowledgeDocumentRequestAsync( + int requestId, string outputDeviceUuid, int jobId, int documentNumber, StatusCode fetchStatusCode, string fetchStatusMessage) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentNumber, IppValue.CreateIntegerValue(documentNumber))); + + // Undefined means the caller is not sending any value for fetchStatusCode. + if (fetchStatusCode != StatusCode.Undefined) + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.FetchStatusCode, IppValue.CreateEnumValue((int)fetchStatusCode))); + } + + // Include optional fetch-status-message attribute. + if (string.IsNullOrEmpty(fetchStatusMessage) == false) + { + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.FetchStatusMessage, IppValue.CreateTextWithoutLanguageValue(fetchStatusMessage))); + } + + var request = await IppRequest.CreateAsync(Operation.AcknowledgeDocument, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Create IPP-INFRA Fetch-Document request. + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The id of the job. + /// The document number (a job can have multiple documents). + /// representation + public async Task CreateFetchDocumentRequestAsync(int requestId, string outputDeviceUuid, int jobId, int documentNumber) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentNumber, IppValue.CreateIntegerValue(documentNumber))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); + + var request = await IppRequest.CreateAsync(Operation.FetchDocument, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + return request; + } + + /// + /// Creates an Update-Output-device-attributes request + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// additional attributes + /// representation + public async Task CreateUpdateOutputDeviceAttributesRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalPrinterAttributes) + { + // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" + // OPTIONAL (common): (UserName+Uri) "requesting-user-name", "requesting-user-uri" + // OPTIONAL (group1): "printer-name", "copies-supported", "color-supported", "duplex-supported" + + // Generate the required required operation attributes + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + + // 'outputDeviceUuid' (aka., Device Id) is really the AADID + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + + // Generate a request for the caller with the supplied information + return await this.CreateUpdateRequestInternalAsync(Operation.UpdateOutputDeviceAttributes, requestId, operationAttributeGroup, optionalPrinterAttributes); + } + + /// + /// Creates an Get-Active-Jobs-Request + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// additional attributes + /// representation + public async Task CreateUpdateActiveJobsRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalOperationAttributes) + { + // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" + // OPTIONAL (group1): (UserName+Uri), "job-ids", "output-device-job-states", "output-device-job-reasons" + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute( + new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + + if (optionalOperationAttributes != null) + { + foreach (var attr in optionalOperationAttributes.Attributes) + { + operationAttributeGroup.AddAttribute(attr.Value); + } + } + + return await this.CreateUpdateRequestInternalAsync(Operation.UpdateActiveJobs, requestId, operationAttributeGroup, null); + } + + /// + /// Creates a Update-Doument-Status request + /// + /// representation + public IppRequest CreateUpdateDocumentStatusRequest() + { + // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" + // REQUIRED (group1): "job-id", "document-number" + // OPTIONAL (group1): (UserName+Uri) + // OPTIONAL (subset of group2): "pages-completed", "output-device-document-state", "output-device-document-state-reasons" + throw new NotImplementedException(); + } + + /// + /// Creates a Update-Job-Status request + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// additional attributes + /// representation + public async Task CreateUpdateJobStatusRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalJobAttributes) + { + // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" + // REQUIRED (group1): "job-id" + // OPTIONAL (group1): (UserName+Uri) + // OPTIONAL (subset of group2): "job-pages-completed", "output-device-job-state", "output-device-job-reasons" + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute( + OperationAttributes.OutputDeviceUuid, IppValue.CreateTextWithoutLanguageValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + + if (optionalJobAttributes != null) + { + // If there is JobId in the JobAttributes group, move it to Operation group + if (optionalJobAttributes.Attributes.ContainsKey(JobAttributes.JobId)) + { + var jobId = optionalJobAttributes.Attributes[JobAttributes.JobId]; + operationAttributeGroup.AddAttribute(jobId); + optionalJobAttributes.RemoveAttribute(JobAttributes.JobId); + } + } + + return await this.CreateUpdateRequestInternalAsync(Operation.UpdateJobStatus, requestId, operationAttributeGroup, optionalJobAttributes); + } + + /// + /// Creates Microsoft's custom Set-Printer-Device-Capabilities request. + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The PDC stream. + /// representation + public async Task CreateSendPdcRequestAsync(int requestId, string outputDeviceUuid, Stream pdcData) + { + var request = await this.CreateDataRequestAsync(Operation.SetPrintDeviceCapabilities, requestId, outputDeviceUuid, pdcData); + return request; + } + + /// + /// Creates Microsoft's custom Set-Print-Capabilities request. + /// + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The PC stream. + /// representation + public async Task CreateSendPcRequestAsync(int requestId, string outputDeviceUuid, Stream pcData) + { + var request = await this.CreateDataRequestAsync(Operation.SetPrintCapabilities, requestId, outputDeviceUuid, pcData); + return request; + } + + /// + /// Creates a Create-Printer-Subscriptions request + /// + /// Request id of the IPP packet. + /// additional attributes + /// representation + public async Task CreateSubscriptionRelatedRequestAsync(Operation operation, int requestId, List optionalSubscriptionAttributeGroups) + { + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + var request = await IppRequest.CreateAsync(operation, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + if (optionalSubscriptionAttributeGroups != null) + { + foreach (var attrGroup in optionalSubscriptionAttributeGroups) + { + request.AttributeGroups.Add(attrGroup); + } + } + + return request; + } + + /// + /// + /// + /// + /// Request id of the IPP packet. + /// + /// + /// + private async Task CreateUpdateRequestInternalAsync(Operation operation, int requestId, IppAttributeGroup operationAttributes, IppAttributeGroup optionalAttributes) + { + var request = await IppRequest.CreateAsync(operation, requestId); + request.AttributeGroups.Add(operationAttributes); + if (optionalAttributes != null) + { + request.AttributeGroups.Add(optionalAttributes); + } + + return request; + } + + /// + /// Add user name and/or user uri to the provided operation attribute group. + /// + /// Attribute group to add the user name and uri attributes. + private void AddUserNameAndUserUri(IppAttributeGroup operationAttributeGroup) + { + // The user name and user uri are initialized in CreateIppRequestFactory call. + if (!string.IsNullOrEmpty(this.RequestingUserName)) + { + operationAttributeGroup.AddAttribute(new IppAttribute( + OperationAttributes.RequestingUserName, + IppValue.CreateURIValue(this.RequestingUserName))); + } + + if (!string.IsNullOrEmpty(this.RequestingUserUri)) + { + operationAttributeGroup.AddAttribute(new IppAttribute( + OperationAttributes.RequestingUserUri, + IppValue.CreateURIValue(this.RequestingUserUri))); + } + } + + /// + /// Creates a minimal IPP request with a data payload. + /// + /// The type of IPP operation to create. + /// Request id of the IPP packet. + /// The identify of the output device for the request. + /// The PC stream. + /// representation + public async Task CreateDataRequestAsync(Operation operation, int requestId, string outputDeviceUuid, Stream data) + { + // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" + var operationAttributeGroup = Helpers.CreateOperationAttributes(); + this.AddUserNameAndUserUri(operationAttributeGroup); + operationAttributeGroup.AddAttribute(new IppAttribute( + OperationAttributes.OutputDeviceUuid, IppValue.CreateTextWithoutLanguageValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); + + var request = await IppRequest.CreateAsync(operation, requestId); + request.AttributeGroups.Add(operationAttributeGroup); + request.Data = data; + return request; + } + + /// + /// Create URI format of output-device-uuid. + /// Example: urn:uuid:01234567-89AB-CDEF-FEDC-BA9876543210. + /// + /// Plain uuid. + /// + private static Uri CreateOutputDeviceUuidUri(string outputdeviceUuid) + { + const string urnScheme = "urn:"; + const string uuidPrefix = "uuid:"; + return new UriBuilder(urnScheme + uuidPrefix + outputdeviceUuid).Uri; + } + + /// + /// Adds the given list of requested attributes to the operation attributes group of the request + /// + /// Reqeusted attributes to be sent in the request + /// Operation attribute group of the request + private void AddRequestedAttributesToOperationAttributeGroup(List requestedAttributes, IppAttributeGroup operationAttributeGroup) + { + if (requestedAttributes?.Count > 0) + { + var requestedAttributesCollection = new IppAttribute(OperationAttributes.RequestedAttributes, IppValue.CreateKeywordValue(requestedAttributes[0].ValueName)); + foreach (var reqAttribute in requestedAttributes.GetRange(1, requestedAttributes.Count - 1)) + { + requestedAttributesCollection.AddAdditionalValue(IppValue.CreateKeywordValue(reqAttribute.ValueName)); + } + + operationAttributeGroup.AddAttribute(requestedAttributesCollection); + } + } + + private void AddJobUriOrId(string jobUri, int jobId, IppAttributeGroup operationAttributeGroup) + { + // See https://www.rfc-editor.org/rfc/rfc8011.html#section-4.1.5 + // for operation target attributes and their ordering + if (!((!string.IsNullOrEmpty(jobUri)) ^ (jobId != 0))) + { + throw new ArgumentException("Either the job-uri or job-id must be provided, but not both."); + } + + if (string.IsNullOrEmpty(jobUri)) + { + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); + } + else + { + operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobUri, IppValue.CreateURIValue(jobUri))); + } + } + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IPPResponse.cs b/BadgeReleaseDemo/IppLibrary/IPPResponse.cs new file mode 100644 index 0000000..54a942f --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPResponse.cs @@ -0,0 +1,396 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using BadgeReleaseDemo.IppLibrary.Common; + + /// + /// Represents a raw IPP response and provides serialization to a + /// stream representations. Automatically fills in the required OperationAttributes (and + /// as such creates the OperationAttributes attribute-group as well. + /// + public class IppResponse : RawIppEncodingBase + { + /// + /// The input stream for an incoming IPP response. + /// + private Stream input; + + public IppResponse(sbyte majorVersion, sbyte minorVersion, int responseId, StatusCode statusCode, string naturalLanguage = null) + { + // According to spec, any request with major version > 0 can be accepted. Ref: https://tools.ietf.org/html/rfc8010#section-9 + // "IPP objects should respond with a response containing the same "version-number" value used by the Client in the request (if the Client - supplied "version-number" is supported) or + // the highest "version-number" supported by the Printer(if the Client - supplied "version-number" is not supported)." + this.StatusCode = statusCode; + this.ResponseId = responseId; + this.MajorVersionNumber = (majorVersion != 0) ? majorVersion : (sbyte)IppMajorVersion.Version2; + this.MinorVersionNumber = (majorVersion == 0 && minorVersion == 0) ? (sbyte)IppMajorVersion.Version1 : minorVersion; + this.BaseAttributeGroups = new List(); + + // Add the REQUIRED operation attributes (3.1.4.2) to the response. + this.AddRequiredAttributes(naturalLanguage); + } + + /// + /// Initializes a new instance of the class. + /// + /// The IPP major version." + /// The IPP minor version." + /// The response ID of the the IPP response." + /// The status code of the response. + /// The detailed status message for the response. + /// The natural language of the response. + public IppResponse(sbyte majorVersion, sbyte minorVersion, int responseId, StatusCode statusCode, string detailedStatusMessage, string naturalLanguage) + : this(majorVersion, minorVersion, responseId, statusCode, naturalLanguage) + { + var operationAttributes = this.LookupAttributeGroup(Tag.OperationAttributes)[0]; + operationAttributes.AddAttribute( + new IppAttribute(OperationAttributes.DetailedStatusMessage, IppValue.CreateTextWithoutLanguageValue(detailedStatusMessage, Constants.MaxTextLength))); + } + + /// + /// Initializes a new instance of the class. + /// + private IppResponse() + { + } + + /// + /// Initializes a new instance of the class. + /// + private IppResponse(Stream input) + { + this.input = input ?? throw new InvalidOperationException("IPPRequest requires input stream."); + } + + /// + /// Gets or sets the status code of the response. + /// + public StatusCode StatusCode { get; set; } + + /// + /// Gets the response id. + /// + public int ResponseId { get; set; } + + /// + /// Gets the list of attribute groups in the response. + /// + public List AttributeGroups => this.BaseAttributeGroups; + + /// + /// Gets or sets the payload of the response. + /// + public new Stream Data + { + get => base.Data; + set => base.Data = value; + } + + /// + /// Search for attribute group matching provided tag type. + /// + /// The type of the attribute group. + /// A list of attribute groups with matching type. + public List LookupAttributeGroup(Tag type) + { + var matchedAttributes = new List(); + foreach (IppAttributeGroup attribute in this.BaseAttributeGroups) + { + if (attribute.Type == type) + { + matchedAttributes.Add(attribute); + } + } + + return matchedAttributes; + } + + /// + /// Serialize the response to string. + /// + /// String form of the response. + public override string ToString() + { + var sb = new StringBuilder(); + sb.AppendFormat(CultureInfo.InvariantCulture, "Status code {0}", this.StatusCode); + sb.AppendFormat(CultureInfo.InvariantCulture, "Response ID {0}", this.ResponseId); + sb.Append(base.ToString()); + return sb.ToString(); + } + + /// + /// Add required attributes for the response. + /// + /// Natural language of the response. + private void AddRequiredAttributes(string naturalLanguage = null) + { + var operationAttributes = new IppAttributeGroup(Tag.OperationAttributes); + + operationAttributes.AddAttribute( + new IppAttribute(IppTemplates.RequiredAttributes.AttributesCharset, IppValue.CreateCharsetValue(Constants.CharSet))); + + if (naturalLanguage == null) + { + naturalLanguage = Helpers.GetNaturalLanguage(); + } + + operationAttributes.AddAttribute( + new IppAttribute(IppTemplates.RequiredAttributes.AttributesNaturalLanguage, IppValue.CreateNaturalLanguageValue(naturalLanguage))); + + this.BaseAttributeGroups.Add(operationAttributes); + } + + /// + /// Create IppResponse object with message. + /// + public static Task CreateAsync(IppRequest ippRequest, StatusCode statusCode, string detailedStatusMessage, string naturalLanguage) + { + var response = new IppResponse() + { + StatusCode = statusCode, + ResponseId = ippRequest?.RequestId ?? 0, + MajorVersionNumber = ippRequest?.MajorVersionNumber ?? (sbyte)IppMajorVersion.Version2, + MinorVersionNumber = ippRequest?.MinorVersionNumber ?? (sbyte)IppMajorVersion.Version1 + }; + + // Add the REQUIRED operation attributes (3.1.4.2) to the response. + response.AddRequiredAttributes(naturalLanguage); + + var operationAttributes = response.LookupAttributeGroup(Tag.OperationAttributes)[0]; + operationAttributes.AddAttribute( + new IppAttribute(OperationAttributes.DetailedStatusMessage, IppValue.CreateTextWithoutLanguageValue(detailedStatusMessage, Constants.MaxTextLength))); + + return Task.FromResult(response); + } + + /// + /// Create IppResponse object without message. + /// + public static Task CreateAsync(IppRequest ippRequest, StatusCode statusCode, string naturalLanguage = null) + { + var response = new IppResponse() + { + StatusCode = statusCode, + ResponseId = ippRequest.RequestId, + MajorVersionNumber = ippRequest.MajorVersionNumber, + MinorVersionNumber = ippRequest.MinorVersionNumber, + }; + + // Add the REQUIRED operation attributes (3.1.4.2) to the response. + response.AddRequiredAttributes(naturalLanguage); + + return Task.FromResult(response); + } + + /// + /// Create the IPPResponse object from input stream. + /// + /// + /// + /// + /// + public static async Task CreateAsync(Stream input, bool stuffRemainingBytesInData, CancellationToken cancellationToken) + { + var response = new IppResponse(input); + await response.DeserializeAsync(stuffRemainingBytesInData, cancellationToken); + return response; + } + + /// + /// TBD: handle member attribute. + /// + /// + /// + public async Task DeserializeAsync(bool stuffRemainingBytesInData, CancellationToken cancellationToken) + { + var input = this.input; + var ippStream = new IPPInputStream(input); + + // Parse the header blob + try + { + this.MajorVersionNumber = await ippStream.ReadSbyteAsync(cancellationToken); + } + catch (InvalidDataException) + { + // Callers (e.g., the Connector) use this exception for indication that the Stream was disconnected. + throw new ZeroByteReadException("Initial read returned 0 bytes"); + } + + this.MinorVersionNumber = await ippStream.ReadSbyteAsync(cancellationToken); + this.StatusCode = (StatusCode)await ippStream.ReadNetworkShortAsync(cancellationToken); + this.ResponseId = await ippStream.ReadNetworkIntegerAsync(cancellationToken); + + // Read zero or more attributes from the request + var tag = await ippStream.ReadTagAsync(cancellationToken); + + while (tag != Tag.EndOfAttributes) + { + // This should be a "begin-attribute-group" tag specifying the type. + if (!Helpers.IsBeginAttributeGroupTag(tag)) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Expected a begin-attribute-group tag here."); + } + + // Form a new attribute group. + var newGroup = new IppAttributeGroup(tag); + + var groupProps = await IppEncodingUtil.DeserializeAttributeGroupAsync(this.input, newGroup, cancellationToken); + var rfcCompliant = groupProps.Item2; + tag = groupProps.Item1; + + this.BaseAttributeGroups.Add(newGroup); + } + + // copy the remaining stream part to a new one, + // if that is requested by caller + if (input.CanSeek) + { + if (input.Position < input.Length && stuffRemainingBytesInData) + { + Stream restOfDataStream = new System.IO.MemoryStream(); + // Forward the 'CancellationToken' parameter to methods + // Disabled here because the CopyToAsync overload in .net462 (MinimumIppLibraryFramework) that accepts a cancel token also requires a buffer size + // We want the library to use its internal buffer size calculator instead. +#pragma warning disable CA2016 + await input.CopyToAsync(restOfDataStream); +#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods + this.Data = restOfDataStream; + } + } + } + + /// + /// Serialize the response. + /// + public Stream Serialize() + { + var output = new MemoryStream(); + this.Serialize(output); + return output; + } + + /// + /// Serialize the response. + /// + /// The output stream. + public void Serialize(Stream output) + { + // Write out the header blob + output.WriteByte((byte)this.MajorVersionNumber); + output.WriteByte((byte)this.MinorVersionNumber); + Helpers.WriteNetworkShort(output, (short)this.StatusCode); + Helpers.WriteNetworkInteger(output, this.ResponseId); + + // Response requirement: https://tools.ietf.org/html/rfc8011#section-4.1.3 + // Later in this section, each operation is formally defined by + // identifying the allowed and expected groups of attributes for each + // request and response.The model identifies a specific order for each + // group in each request or response, but the attributes within each + // group can be in any order, unless specified otherwise. + // + // In general: operation attribute group first, unsupported attribute next, the rest of the attribute groups followed. + foreach (var atrributeGroup in this.BaseAttributeGroups) + { + if (atrributeGroup.Type != Tag.OperationAttributes) + { + continue; + } + + atrributeGroup.Serialize(output); + break; + } + + foreach (var atrributeGroup in this.BaseAttributeGroups) + { + if (atrributeGroup.Type != Tag.UnsupportedAttributes) + { + continue; + } + + atrributeGroup.Serialize(output); + break; + } + + // Begin writing out attributes if there are any + foreach (IppAttributeGroup atrributeGroup in this.BaseAttributeGroups) + { + if (atrributeGroup.Type != Tag.OperationAttributes && atrributeGroup.Type != Tag.UnsupportedAttributes) + { + atrributeGroup.Serialize(output); + } + } + + // End of attributes + output.WriteByte((byte)Tag.EndOfAttributes); + + // Write out any data if there is any. + base.Data?.CopyTo(output); + + // And we're done. + output.Seek(0, SeekOrigin.Begin); + } + + /// + /// Compare two attributes. + /// + public override bool Equals(object obj) + { + if (obj is IppResponse other) + { + if (this.ResponseId != other.ResponseId) + { + return false; + } + + if (this.StatusCode != other.StatusCode) + { + return false; + } + + if (!this.AttributeGroups.SequenceEqual(other.AttributeGroups)) + { + return false; + } + + // just compare size of the payload. + if (this.Data != null) + { + var otherDataLength = other.Data.Length - other.Data.Position; + if (this.Data.Length != otherDataLength) + { + return false; + } + } + } + else + { + return false; + } + + return true; + } + + /// + /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. + /// Attribute comparison is used by test code. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPTemplates.cs b/BadgeReleaseDemo/IppLibrary/IPPTemplates.cs new file mode 100644 index 0000000..d47149d --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPTemplates.cs @@ -0,0 +1,512 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary.Common +{ + using System.Collections.Generic; + + public static class IppTemplates + { + /// + /// Gets a list of all supported template attributes applicable to a job + /// (as returned by get-job-attributes or get-jobs in a request for 'all' attributes) + /// https://tools.ietf.org/html/rfc8011#section-5.2 and + /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.16. + /// + public static string[] JobTemplateAttributes { get; } = + { + JobAttributes.Copies, + JobAttributes.Finishings, + JobAttributes.JobAccountId, + JobAttributes.JobAccountingUserId, + JobAttributes.JobPriority, + JobAttributes.JobHoldUntil, + JobAttributes.JobSheets, + JobAttributes.Media, + JobAttributes.MultipleDocumentHandling, + JobAttributes.NumberUp, + JobAttributes.OrientationRequested, + JobAttributes.PageRanges, + JobAttributes.PresentationDirectionNumberUp, + JobAttributes.PdfFitToPage, + JobAttributes.PrintScaling, + JobAttributes.PrinterResolution, + JobAttributes.PrinterQuality, + JobAttributes.Sides, + + // https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf#page=30 + JobAttributes.JobOriginatingUserName, + JobAttributes.JobOriginatingUserUri, + + // INFRA only + JobAttributes.FeedOrientation, + JobAttributes.FinishingsCol, + JobAttributes.MediaCol, + JobAttributes.MediaBottomMargin, + JobAttributes.MediaLeftMargin, + JobAttributes.MediaRightMargin, + JobAttributes.MediaSize, + JobAttributes.MediaSource, + JobAttributes.MediaTopMargin, + JobAttributes.MediaType, + JobAttributes.OutputBin, + JobAttributes.Overrides, + JobAttributes.PrintColorMode, + JobAttributes.PrintContentOptimize, + JobAttributes.PrintRenderingIntent, + JobAttributes.PrintQuality, + JobAttributes.PrinterResolution + }; + + /// + /// Gets a list of document attributes defined as document template attributes in IPP Infra spec. + /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.12. + /// + public static string[] DocumentTemplateAttributes { get; } = + { + DocumentAttributes.Copies, + DocumentAttributes.FeedOrientation, + DocumentAttributes.Finishings, + DocumentAttributes.FinishingsCol, + DocumentAttributes.Media, + DocumentAttributes.MediaCol, + DocumentAttributes.MediaBottomMargin, + DocumentAttributes.MediaLeftMargin, + DocumentAttributes.MediaRightMargin, + DocumentAttributes.MediaSize, + DocumentAttributes.MediaSource, + DocumentAttributes.MediaTopMargin, + DocumentAttributes.MediaType, + DocumentAttributes.OrientationRequested, + DocumentAttributes.OutputBin, + DocumentAttributes.Overrides, + DocumentAttributes.PageRanges, + DocumentAttributes.PrintColorMode, + DocumentAttributes.PrintContentOptimize, + DocumentAttributes.PrintRenderingIntent, + DocumentAttributes.PrintQuality, + DocumentAttributes.PrinterResolution, + DocumentAttributes.Sides + }; + + /// + /// Gets a list of job description attributes defined in: + /// IPP INFRA: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.14. + /// IPP: https://tools.ietf.org/html/rfc8011#section-5.3. + /// + public static string[] JobDescriptionAttributes { get; } = + { + JobAttributes.JobImpressions, + JobAttributes.JobImpressionsCompleted, + JobAttributes.JobKOctets, + JobAttributes.JobMediaSheets, + JobAttributes.JobName, + JobAttributes.JobStateReasons, + JobAttributes.JobState, + JobAttributes.JobName, + JobAttributes.JobOriginatingUserName + }; + + /// + /// Gets a list of job status attributes defined in + /// IPP INFRA: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.15. and + /// IPP: https://tools.ietf.org/html/rfc8011#section-5.3. + /// + public static string[] JobStatusAttributes { get; } = + { + JobAttributes.CompressionSupplied, + JobAttributes.DateTimeAtCompleted, + JobAttributes.DateTimeAtCreation, + JobAttributes.DateTimeAtProcessing, + JobAttributes.DocumentFormatSupplied, + JobAttributes.DocumentFormatVersionSupplied, + JobAttributes.DocumentNameSupplied, + JobAttributes.JobId, + JobAttributes.JobImpressionsCompleted, + JobAttributes.JobOriginatingUserName, + JobAttributes.JobPrinterUpTime, + JobAttributes.JobPrinterUri, + JobAttributes.JobReleaseAction, + JobAttributes.JobReleaseActionActual, + JobAttributes.JobReleaseActionId, + JobAttributes.JobState, + JobAttributes.JobStateMessage, + JobAttributes.JobStateReasons, + OperationAttributes.JobUri, + JobAttributes.JobUuid, + JobAttributes.TimeAtCompleted, + JobAttributes.TimeAtCreation, + + // IPP only. + JobAttributes.DateTimeAtProcessing, + JobAttributes.DateTimeAtCompleted, + JobAttributes.DateTimeAtCreation, + JobAttributes.DetailedStatusMessage, + JobAttributes.DocumentAccessError, + JobAttributes.JobKOctetsProcessed, + JobAttributes.JobMediaSheetsCompleted, + JobAttributes.JobMessageFromOperator, + JobAttributes.JobMoreInfo, + JobAttributes.NumberOfDocuments, + JobAttributes.NumberOfInterveningJobs, + JobAttributes.OutputDeviceAssigned, + JobAttributes.TimeAtCreation + }; + + /// + /// Gets a list of document status attributes defined in: + /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.11. + /// + public static string[] DocumentStatusAttributes { get; } = + { + OperationAttributes.AttributesCharset, + OperationAttributes.AttributesNaturalLanguage, + OperationAttributes.Compression, + DocumentAttributes.DateTimeAtCompleted, + JobAttributes.DateTimeAtCreation, + JobAttributes.DateTimeAtProcessing, + OperationAttributes.DocumentFormat, + DocumentAttributes.DocumentJobId, + OperationAttributes.DocumentNumber, + DocumentAttributes.DocumentPrinterUri, + DocumentAttributes.DocumentState, + DocumentAttributes.DocumentStateMessage, + DocumentAttributes.DocumentStateReasons, + OperationAttributes.DocumentUri, + DocumentAttributes.DocumentUuid, + DocumentAttributes.ImpressionsCompleted, + DocumentAttributes.LastDocument, + OperationAttributes.PrinterUpTime, + JobAttributes.TimeAtCompleted, + JobAttributes.TimeAtCreation, + JobAttributes.TimeAtProcessing + }; + + /// + /// Gets a list of printer description attributes. + /// RFC 8011: Printer attributes are divided into 2 groups. + /// "job-template" and "printer-description" attributes. + /// Here's the list for the printer-description attributes. + /// + public static string[] PrinterDescriptionAttributes { get; } = + { + PrinterAttributes.CharsetConfigured, + PrinterAttributes.CharsetSupported, + PrinterAttributes.ColorSupported, + PrinterAttributes.ColorModeSupported, + PrinterAttributes.ColorModeDefault, + PrinterAttributes.CompressionSupported, + PrinterAttributes.CopiesDefault, + PrinterAttributes.CopiesSupported, + PrinterAttributes.DocumentAccessSupported, + PrinterAttributes.DocumentFormatDefault, + PrinterAttributes.DocumentFormatDetailsSupported, + PrinterAttributes.DocumentFormatSupported, + PrinterAttributes.DocumentPasswordSupported, + PrinterAttributes.FeedOrientationDefault, + PrinterAttributes.FeedOrientationSupported, + PrinterAttributes.FinishingsColDatabase, + PrinterAttributes.FinishingsColDefault, + PrinterAttributes.FinishingsColReady, + PrinterAttributes.FinishingsColSupported, + PrinterAttributes.FinishingsDefault, + PrinterAttributes.FinishingsReady, + PrinterAttributes.FinishingsSupported, + PrinterAttributes.GeneratedNaturalLanguageSupported, + PrinterAttributes.IdentifyActionsDefault, + PrinterAttributes.IdentifyActionsSupported, + PrinterAttributes.IppFeaturesSupported, + PrinterAttributes.IppVersionsSupported, + PrinterAttributes.IppgetEventLife, + PrinterAttributes.JobAccountIdDefault, + PrinterAttributes.JobAccountIdSupported, + PrinterAttributes.JobAccountingUserIdDefault, + PrinterAttributes.JobAccountingUserIdSupported, + PrinterAttributes.JobConstraintsSupported, + PrinterAttributes.JobCreationAttributesSupported, + PrinterAttributes.JobIdsSupported, + PrinterAttributes.JobImpressionsSupported, + PrinterAttributes.JobMandatoryAttributesSupported, + PrinterAttributes.JobMediaSheetsSupported, + PrinterAttributes.JobPagesPerSetSupported, + PrinterAttributes.JobPasswordEncryptionSupported, + PrinterAttributes.JobPasswordSupported, + PrinterAttributes.JobPasswordLengthSupported, + PrinterAttributes.JobReleaseActionDefault, + PrinterAttributes.JobReleaseActionSupported, + PrinterAttributes.JobResolversSupported, + PrinterAttributes.MarginsPreAppliedDefault, + PrinterAttributes.MarginsPreAppliedSupported, + PrinterAttributes.MediaBottomMarginSupported, + PrinterAttributes.MediaColDatabase, + PrinterAttributes.MediaColDefault, + PrinterAttributes.MediaColReady, + PrinterAttributes.MediaColSupported, + PrinterAttributes.MediaDefault, + PrinterAttributes.MediaLeftMarginSupported, + PrinterAttributes.MediaReady, + PrinterAttributes.MediaRightMarginSupported, + PrinterAttributes.MediaSizeSupported, + PrinterAttributes.MediaSourceProperties, + PrinterAttributes.MediaSourceSupported, + PrinterAttributes.MediaSupported, + PrinterAttributes.MediaTopMarginSupported, + PrinterAttributes.MediaTypeSupported, + PrinterAttributes.MediaColorSupported, + PrinterAttributes.MicrosoftPageOrderDefault, + PrinterAttributes.MicrosoftPageOrderSupported, + PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion, + PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem, + PrinterAttributes.MicrosoftUniversalPrintConnectorId, + PrinterAttributes.MicrosoftUniversalPrinterDriverName, + PrinterAttributes.MicrosoftUniversalPrinterDriverVersion, + PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion, + PrinterAttributes.MopriaCertified, + PrinterAttributes.MultipleDocumentHandlingDefault, + PrinterAttributes.MultipleDocumentHandlingSupported, + PrinterAttributes.MultipleDocumentJobsSupported, + PrinterAttributes.MultipleOperationTimeout, + PrinterAttributes.MultipleOperationTimeoutAction, + PrinterAttributes.NaturalLanguageConfigured, + PrinterAttributes.NotifyPullMethodSupported, + PrinterAttributes.NumberUpDefault, + PrinterAttributes.NumberUpSupported, + PrinterAttributes.OauthAuthorizationServerUri, + PrinterAttributes.OperationsSupported, + PrinterAttributes.OrientationRequestedDefault, + PrinterAttributes.OrientationRequestedSupported, + PrinterAttributes.OutputBinDefault, + PrinterAttributes.OutputBinSupported, + PrinterAttributes.OverridesSupported, + PrinterAttributes.PageRangesSupported, + PrinterAttributes.PageColorModeDefault, + PrinterAttributes.PageColorModeSupported, + PrinterAttributes.PclmRasterBackSide, + PrinterAttributes.PclmSourceResolutionSupported, + PrinterAttributes.PclmStripHeightPreferred, + PrinterAttributes.PclmStripHeightSupported, + PrinterAttributes.PdfFitToPageDefault, + PrinterAttributes.PdfFitToPageSupported, + PrinterAttributes.PdfKOctetsSupported, + PrinterAttributes.PdfSizeConstraints, + PrinterAttributes.PdfVersionsSupported, + PrinterAttributes.PdlOverrideSupported, + PrinterAttributes.PresentationDirectionNumberUpDefault, + PrinterAttributes.PresentationDirectionNumberUpSupported, + PrinterAttributes.PrintContentOptimizeDefault, + PrinterAttributes.PrintContentOptimizeSupported, + PrinterAttributes.PrintRenderingIntentDefault, + PrinterAttributes.PrintRenderingIntentSupported, + PrinterAttributes.PrintQualityDefault, + PrinterAttributes.PrintQualitySupported, + PrinterAttributes.PrintScalingDefault, + PrinterAttributes.PrintScalingSupported, + PrinterAttributes.PrintWFDS, + PrinterAttributes.PrinterDeviceId, + PrinterAttributes.PrinterGeoLocation, + PrinterAttributes.PrinterGetAttributesSupported, + PrinterAttributes.PrinterIccProfiles, + PrinterAttributes.PrinterIcons, + PrinterAttributes.PrinterInfo, + PrinterAttributes.PrinterKind, + PrinterAttributes.PrinterLocation, + PrinterAttributes.PrinterMakeAndModel, + PrinterAttributes.PrinterMoreInfoManufacturer, + PrinterAttributes.PrinterName, + PrinterAttributes.PrinterOrganization, + PrinterAttributes.PrinterOrganizationalUnit, + PrinterAttributes.PrinterOutputTray, + PrinterAttributes.PrinterResolutionDefault, + PrinterAttributes.PrinterResolutionSupported, + PrinterAttributes.PrinterStaticResourceDirectoryUri, + PrinterAttributes.PrinterStaticResourceKOctetsSupported, + PrinterAttributes.PrinterSupplyInfoUri, + PrinterAttributes.PrinterUriSupported, + PrinterAttributes.PwgRasterDocumentResolutionSupported, + PrinterAttributes.PwgRasterDocumentSheetBack, + PrinterAttributes.PwgRasterDocumentTypeSupported, + PrinterAttributes.SidesDefault, + PrinterAttributes.SidesSupported, + PrinterAttributes.WhichJobsSupported, + }; + + /// + /// Gets the list of printer status attributes. + /// + public static string[] PrinterStatusAttributes { get; } = + { + PrinterAttributes.LandscapeOrientationRequestedPreferred, + PrinterAttributes.PagesPerMinute, + PrinterAttributes.PagesPerMinuteColor, + PrinterAttributes.PrinterAlert, + PrinterAttributes.PrinterAlertDescription, + PrinterAttributes.PrinterConfigChangeDateTime, + PrinterAttributes.PrinterConfigChangeTime, + PrinterAttributes.PrinterCurrentTime, + PrinterAttributes.PrinterFirmwareName, + PrinterAttributes.PrinterFirmwarePatches, + PrinterAttributes.PrinterFirmwareStringVersion, + PrinterAttributes.PrinterFirmwareVersion, + PrinterAttributes.PrinterIsAcceptingJobs, + PrinterAttributes.PrinterMoreInfo, + PrinterAttributes.PrinterState, + PrinterAttributes.PrinterStateMessage, + PrinterAttributes.PrinterStateReasons, + PrinterAttributes.PrinterStateChangeDateTime, + PrinterAttributes.PrinterStateChangeTime, + PrinterAttributes.PrinterStateChangeMessage, + PrinterAttributes.PrinterStaticResourceKOctetsFree, + PrinterAttributes.PrinterSupply, + PrinterAttributes.PrinterSupplyDescription, + PrinterAttributes.PrinterSupplyInfoUri, + PrinterAttributes.PrinterUpTime, + PrinterAttributes.PrinterUriSupported, + PrinterAttributes.PrinterUuid, + PrinterAttributes.UrfSupported, + PrinterAttributes.UriSecuritySupported, + PrinterAttributes.UriAuthenticationSupported, + + // CUPS IPP Printer Status Attributes (Optional for Mopria 2.0 support) + PrinterAttributes.MarkerColors, + PrinterAttributes.MarkerHighLevels, + PrinterAttributes.MarkerLevels, + PrinterAttributes.MarkerLowLevels, + PrinterAttributes.MarkerNames, + PrinterAttributes.MarkerTypes, + }; + + public static HashSet PrinterDescriptionAttributesAutoPopulatedByService { get; } = new HashSet() + { + PrinterAttributes.IppFeaturesSupported, + PrinterAttributes.IppVersionsSupported, + PrinterAttributes.JobCreationAttributesSupported, + PrinterAttributes.JobImpressionsSupported, + PrinterAttributes.JobMediaSheetsSupported, + PrinterAttributes.JobPagesPerSetSupported, + PrinterAttributes.MultipleDocumentJobsSupported, + PrinterAttributes.MultipleOperationTimeout, + PrinterAttributes.MultipleOperationTimeoutAction, + PrinterAttributes.OperationsSupported, + PrinterAttributes.PrinterDeviceId, + PrinterAttributes.PrinterGeoLocation, + PrinterAttributes.PrinterIcons, + PrinterAttributes.PrinterLocation, + PrinterAttributes.PrinterMoreInfo, + PrinterAttributes.PrinterName, + PrinterAttributes.PrinterOrganization, + PrinterAttributes.PrinterOrganizationalUnit, + PrinterAttributes.PrinterSupplyInfoUri, + PrinterAttributes.PrinterUriSupported, + PrinterAttributes.PrinterUuid, + PrinterAttributes.UriAuthenticationSupported, + PrinterAttributes.UriSecuritySupported + }; + + /// + /// Must have attributes. + /// + public static class RequiredAttributes + { + public const string AttributesCharset = OperationAttributes.AttributesCharset; + public const string AttributesNaturalLanguage = OperationAttributes.AttributesNaturalLanguage; + } + + /// + /// Special set of requested attributes. + /// + public static class RequestedAttributes + { + public const string All = "all"; + public const string JobTemplate = "job-template"; + public const string JobDescription = "job-description"; + public const string PrinterDescription = "printer-description"; + } + + public static HashSet GetMopriaRequiredPrinterDescriptionAttributes(IppAttribute documentFormatSupportedAttribute) + { + var printerDescriptionAttributesRequiredByMopria = new HashSet + { + PrinterAttributes.CharsetConfigured, + PrinterAttributes.CharsetSupported, + PrinterAttributes.CopiesDefault, + PrinterAttributes.CopiesSupported, + PrinterAttributes.DocumentFormatDefault, + PrinterAttributes.DocumentFormatSupported, + PrinterAttributes.FinishingsDefault, + PrinterAttributes.FinishingsSupported, + PrinterAttributes.GeneratedNaturalLanguageSupported, + PrinterAttributes.IppFeaturesSupported, + PrinterAttributes.IppVersionsSupported, + PrinterAttributes.MediaColDatabase, + PrinterAttributes.MediaColDefault, + PrinterAttributes.MediaColSupported, + PrinterAttributes.MediaSupported, + PrinterAttributes.MediaTypeSupported, + PrinterAttributes.NaturalLanguageConfigured, + PrinterAttributes.OperationsSupported, + PrinterAttributes.OrientationRequestedDefault, + PrinterAttributes.OrientationRequestedSupported, + PrinterAttributes.OutputBinDefault, + PrinterAttributes.OutputBinSupported, + PrinterAttributes.PrintColorModeDefault, + PrinterAttributes.PrintColorModeSupported, + PrinterAttributes.PrintQualityDefault, + PrinterAttributes.PrintQualitySupported, + PrinterAttributes.PrinterIsAcceptingJobs, + PrinterAttributes.PrinterLocation, + PrinterAttributes.PrinterMakeAndModel, + PrinterAttributes.PrinterMoreInfo, + PrinterAttributes.PrinterResolutionDefault, + PrinterAttributes.PrinterResolutionSupported, + PrinterAttributes.PrinterState, + PrinterAttributes.PrinterStateReasons, + PrinterAttributes.PrinterUriSupported, + PrinterAttributes.SidesDefault, + PrinterAttributes.SidesSupported, + PrinterAttributes.UriSecuritySupported + }; + + if (documentFormatSupportedAttribute != null && documentFormatSupportedAttribute.Values != null) + { + foreach (var value in documentFormatSupportedAttribute.Values) + { + switch (value.GetNativeValue()) + { + case Constants.DocumentFormatPdf: + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PdfKOctetsSupported); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PdfVersionsSupported); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingDefault); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingSupported); + break; + + case Constants.DocumentFormatPclm: + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PageRangesSupported); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmRasterBackSide); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmSourceResolutionSupported); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmStripHeightPreferred); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmStripHeightSupported); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.MarginsPreAppliedDefault); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.MarginsPreAppliedSupported); + break; + + case Constants.DocumentFormatPwgRaster: + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PwgRasterDocumentResolutionSupported); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PwgRasterDocumentSheetBack); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PwgRasterDocumentTypeSupported); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingDefault); + printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingSupported); + break; + + default: + break; + } + } + } + + return printerDescriptionAttributesRequiredByMopria; + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/IPPValue.cs b/BadgeReleaseDemo/IppLibrary/IPPValue.cs new file mode 100644 index 0000000..2796a9a --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/IPPValue.cs @@ -0,0 +1,951 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Text; + using BadgeReleaseDemo.IppLibrary.Common; + + public class IppValue + { + /// + /// Gets the value of the property. + /// + private byte[] Value { get; } + + /// + /// Gets the member attributes of the collection attribute. + /// + private List memberAttributes { get; } + + /// + /// Initializes a new instance of the class. + /// Constructs a TypeValuePair from raw data (used in deserialization and by factory methods) + /// We will validate that the size of the incoming data is appropriate for the specified type. + /// + /// The type of the attribute. + /// The value of the attribute. + /// Should only be used by unit tests to prep bad data. + public IppValue(Tag type, byte[] value, bool skipDataValidation = false) + { + if (!skipDataValidation) + { + ValidateDataSize(type, value); + } + + this.ValueType = type; + this.ExtendedType = 0; + this.Value = value; + + if (type == Tag.BegCollection) + { + this.memberAttributes = new List(); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The extended type of the attribute. + /// The value of the attribute. + public IppValue(int extendedType, byte[] value) + { + // Note: In general we can't validate the size of the value here. + // As we add support for extended types we can add validation for them. + this.ValueType = Tag.TypeExtension; + this.ExtendedType = extendedType; + this.Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + /// The type of the attribute. + public IppValue(Tag type) + { + this.ValueType = type; + this.ExtendedType = 0; + if (type == Tag.BegCollection) + { + this.memberAttributes = new List(); + } + } + + /// + /// Gets the value type of the property. + /// + public Tag ValueType { get; } + + /// + /// Gets the extended type of the property. + /// + public int ExtendedType { get; } + + /// + /// Gets the member attributes of the collection attribute. + /// + public IReadOnlyList MemberAttributes => this.memberAttributes?.AsReadOnly(); + + /// + /// Factory constructors for IPP types. These are used to construct and encode TypeValuePairs for IPP responses + /// + /// The value for the integer attribute. + /// The integer attribute. + public static IppValue CreateIntegerValue(int value) + { + return new IppValue(Tag.Integer, Helpers.IntegerToByteArray(value)); + } + + /// + /// Creates a boolean attribute. + /// + /// The value for the boolean attribute. + /// The boolean attribute. + public static IppValue CreateBooleanValue(bool value) + { + return new IppValue(Tag.Boolean, Helpers.BoolToByteArray(value)); + } + + /// + /// Creates an enum attribute. + /// + /// The value for the enum attribute. + /// The enum attribute. + public static IppValue CreateEnumValue(int value) + { + return new IppValue(Tag.Enum, Helpers.IntegerToByteArray(value)); + } + + /// + /// Creates an octet string attribute. + /// + /// The values of the octet string attribute. + /// The octet string attribute. + public static IppValue CreateOctetStringValue(byte[] value) + { + // For clarity: an OCTET-STRING in IPP is not a text string (necessarily). + // it's just one or more bytes (effectively an array). + return new IppValue(Tag.OctetString, value); + } + + /// + /// Creates an octet string attribute. + /// + /// The values of the octet string attribute. + /// The octet string attribute. + public static IppValue CreateBadOctetStringValueForUnitTest(byte[] value) + { + // For clarity: an OCTET-STRING in IPP is not a text string (necessarily). + // it's just one or more bytes (effectively an array). + return new IppValue(Tag.OctetString, value, skipDataValidation: true); + } + + /// + /// Creates a date time attribute. + /// + /// The value of the attribute. + /// A date time attribute object. + public static IppValue CreateDateTimeValue(DateTime value) + { + return new IppValue(Tag.DateTime, Helpers.DateTimeToByteArray(value)); + } + + /// + /// Creates a resolution attribute. + /// + /// The x value of the attribute. + /// The y value of the attribute. + /// The unit of the attribute. + /// A resolution attribute object. + public static IppValue CreateResolutionValue(int x, int y, sbyte units) + { + return new IppValue(Tag.Resolution, Helpers.ResolutionToByteArray(x, y, units)); + } + + /// + /// Creates a range of integer attribute. + /// + /// The lower range of the value. + /// The upper range of the value. + /// A range of integer attribute object. + public static IppValue CreateRangeOfIntegerValue(int lower, int upper) + { + return new IppValue(Tag.RangeOfInteger, Helpers.IntegerRangeToByteArray(lower, upper)); + } + + /// + /// Creates a text with language value attribute. + /// + /// The value of the text. + /// The value of the natural language. + /// A text with natural language attribute. + public static IppValue CreateTextWithLanguageValue(string theString, string naturalLanguage) + { + return new IppValue(Tag.TextWithLanguage, Helpers.StringWithNaturalLanguageToByteArray(theString, naturalLanguage)); + } + + /// + /// Creates a name with language value attribute. + /// + /// The value of the name string. + /// The value of the natural language. + /// A name with natural language attribute. + public static IppValue CreateNameWithLanguageValue(string theString, string naturalLanguage) + { + return new IppValue(Tag.NameWithLanguage, Helpers.StringWithNaturalLanguageToByteArray(theString, naturalLanguage)); + } + + /// + /// Creates a text without language value. + /// + /// The value of the text. + /// The maximum length of the text. + /// A text without natural language attribute. + public static IppValue CreateTextWithoutLanguageValue(string value, int maxLength = Constants.MaxTextLength) + { + // Truncate strings longer than maxLength + if (value.Length > maxLength) + { + value = value.Substring(0, maxLength); + } + + // Note: Since we are only supporting UTF-8 encodings for both requests and responses, + // we don't need or care to support specifying an encoding here, we just always + // encode the string into UTF-8 in the byte array, which StringToByteArray does. + return new IppValue(Tag.TextWithoutLanguage, Helpers.StringToUTF8ByteArray(value)); + } + + /// + /// Creates a name without language value. + /// + /// The value of the name. + /// The maximum length of the name. + /// A name without language value attribute. + public static IppValue CreateNameWithoutLanguageValue(string value, int maxLength = Constants.MaxNameLength) + { + // Truncate strings longer than maxLength + if (value.Length > maxLength) + { + value = value.Substring(0, maxLength); + } + + // Note: See above comments. + return new IppValue(Tag.NameWithoutLanguage, Helpers.StringToUTF8ByteArray(value)); + } + + /// + /// Creates a key word attribute. + /// + /// The value of the keyword. + /// A keyword attribute. + public static IppValue CreateKeywordValue(string value) + { + if (value.Length > Constants.MaxKeywordLength) + { + throw new IPPException(StatusCode.ServerErrorInternalError, string.Format(CultureInfo.InvariantCulture, "Keyword value is greater than maximum keyword length: {0}", value)); + } + + // Note: Keywords are always encoded as US-ASCII; this means that anything unicode + // in the incoming string is ignored (we strip off the top 8 bits). + return new IppValue(Tag.Keyword, Helpers.StringToASCIIByteArray(value)); + } + + /// + /// Creates a uri attribute. + /// + /// The uri value. + /// A uri attribute object. + public static IppValue CreateURIValue(string value) + { + if (value.Length > Constants.MaxUriLength) + { + throw new IPPException(StatusCode.ServerErrorInternalError, string.Format(CultureInfo.InvariantCulture, "Uri value is greater than maximum url length: {0}", value)); + } + + return new IppValue(Tag.Uri, Helpers.StringToASCIIByteArray(value)); + } + + /// + /// Creates a uri scheme attribute. + /// + /// The value of the uri scheme. + /// A uri scheme attribute object. + public static IppValue CreateUriSchemeValue(string value) + { + return new IppValue(Tag.UriScheme, Helpers.StringToASCIIByteArray(value)); + } + + /// + /// Creates a charset value attribute. + /// + /// The value of the attribute. + /// The new charset attribute object. + public static IppValue CreateCharsetValue(string value) + { + return new IppValue(Tag.Charset, Helpers.StringToASCIIByteArray(value)); + } + + /// + /// Creates a natural language attribute. + /// + /// The value of the natural language. + /// The new attribute. + public static IppValue CreateNaturalLanguageValue(string value) + { + return new IppValue(Tag.NaturalLanguage, Helpers.StringToASCIIByteArray(value)); + } + + /// + /// Creates a mime media type attribute. + /// + /// The mime value. + /// The newly created attribute. + public static IppValue CreateMimeMediaTypeValue(string value) + { + if (value.Length > Constants.MaxMimeTypeLength) + { + throw new IPPException(StatusCode.ServerErrorInternalError, string.Format(CultureInfo.InvariantCulture, "Mime type value is greater than maximum mime type length: {0}", value)); + } + + return new IppValue(Tag.MimeMediaType, Helpers.StringToASCIIByteArray(value)); + } + + /// + /// Creates no-value value attribute. + /// + /// A value with Tag.NoVaue. + public static IppValue CreateNoValueValue() + { + // Empty byte array. + return new IppValue(Tag.NoValue, new byte[] { }); + } + + /// + /// Creates unknown value attribute. + /// + /// A value with Tag.Unknown. + public static IppValue CreateUnknownValue() + { + // Empty byte array. + return new IppValue(Tag.Unknown, new byte[] { }); + } + + /// + /// Creates collection attribute value. + /// + /// A beg collection. + public static IppValue CreateCollectionAttributeValue() + { + return new IppValue(Tag.BegCollection); + } + + /// + /// Adds member attribute to the collection value + /// + /// Member attribute to be added to the current collection value + public void AddMemberAttribute(IppMemberAttribute memberAttribute) + { + if (this.IsCollectionAttributeValue()) + { + this.memberAttributes.Add(memberAttribute); + } + else + { + throw new IPPException(StatusCode.ClientErrorBadRequest, "Cannot add member attributes to a non-collection attribute"); + } + } + + /// + /// Adds member attribute to the collection value + /// + /// Name to use for the member attribute. + /// Member attribute to be added to the current collection value + public void AddMemberAttribute(string memberAttributeName, IppValue memberAttributeValue) + { + this.AddMemberAttribute(new IppMemberAttribute(memberAttributeName, memberAttributeValue)); + } + + public bool IsCollectionAttributeValue() + { + return this.ValueType == Tag.BegCollection; + } + + public bool IsOutOfBandValue() + { + return this.ValueType == Tag.Unknown || this.ValueType == Tag.NoValue; + } + + /// + /// Returns the serialized binary value + /// + public byte[] GetSerializedValue() + { + return this.Value; + } + + /// + /// Returns the value as a .NET type (used for consuming the value) + /// + public T GetNativeValue() + { + return (T)this.GetNativeValue(); + } + + /// + /// Returns the value as a .NET type (used for consuming the value) + /// + public object GetNativeValue() + { + object value = null; + + switch (this.ValueType) + { + case Tag.Resolution: + value = Helpers.ByteArrayToResolution(this.Value); + break; + + case Tag.DateTime: + value = Helpers.ByteArrayToDateTime(this.Value); + break; + + case Tag.Integer: + case Tag.Enum: + value = Helpers.ByteArrayToInteger(this.Value); + break; + + case Tag.RangeOfInteger: + value = Helpers.ByteArrayToIntegerRange(this.Value); + break; + + case Tag.Boolean: + value = Helpers.ByteArrayToBool(this.Value); + break; + + case Tag.OctetString: + value = this.Value; + break; + + case Tag.TextWithLanguage: + case Tag.NameWithLanguage: + // Untill we find some customer scenarioes that need the language code, we decided to only give back the string value. + // At least Windows client side does NOT need to do any parsing or casing based on the language code. + value = Helpers.ByteArrayToStringWithNaturalLanguage(this.Value).Item1; + break; + + /* + * https://tools.ietf.org/html/rfc8010#section-3 + * 3.9. (Attribute) "value". + * NOTE: textWithoutLanguage is LOCALIZED - STRING + +----------------------+--------------------------------------------+ + + | Syntax of Attribute | Encoding | + + | Value | | + +----------------------+--------------------------------------------+ + + | textWithoutLanguage, | LOCALIZED - STRING + */ + case Tag.TextWithoutLanguage: + case Tag.NameWithoutLanguage: + value = Helpers.UTF8ByteArrayToString(this.Value); + break; + + case Tag.Keyword: + case Tag.Uri: + case Tag.UriScheme: + case Tag.Charset: + case Tag.NaturalLanguage: + case Tag.MimeMediaType: + value = Helpers.ASCIIByteArrayToString(this.Value); + break; + + case Tag.TypeExtension: + throw new NotImplementedException("Extended types not supported yet."); + + case Tag.NoValue: + case Tag.Unknown: + value = null; + break; + + case Tag.BegCollection: + throw new IPPException(StatusCode.ClientErrorBadRequest, "Collection attribute cannot have a native value."); + default: + throw new IPPException(StatusCode.ClientErrorBadRequest, "Unhandled attribute type."); + } + + return value; + } + + /// + /// Serializes this pair as an "attribute-with-one-value" if attribute name is non-empty + /// Serializes this pair as an "additional-value" if attribute name is empty or null + /// + /// The output stream. + /// The name of the attribute. + public void Serialize(Stream output, string attributeName = null) + { + if (this.IsCollectionAttributeValue()) + { + // Setup the collection attribute: https://tools.ietf.org/html/rfc8010#section-3.1.6 + output.WriteByte((byte)Tag.BegCollection); // value-tag This is a collection attribute: 0x34. + + if (string.IsNullOrEmpty(attributeName)) + { + Helpers.WriteNetworkShort(output, (short)0x0000); // name-length Set to 0 for additional collection attribute values. see https://tools.ietf.org/html/rfc8010#section-3.1.6. + } + else + { + Helpers.WriteNetworkShort(output, (short)attributeName.Length); // name-length Length of the attribute name. + Helpers.WriteAsciiString(output, attributeName); // name Attribute name. + } + + Helpers.WriteNetworkShort(output, (short)0x00); // value-length Always 0, signifying a collection attribute. + + // Serialize the member attributes. + for (var i = 0; i < this.memberAttributes.Count; i++) + { + this.memberAttributes[i].SerializeMemberAttribute(output, 1); // member-attribute The serialized content of all member attributes. + } + + // Complete the collection attribute. + output.WriteByte((byte)Tag.EndCollection); // end-value-tag Always 0x37 denoting end of the collection. + Helpers.WriteNetworkShort(output, (short)0x00); // end-name-length Always 0x0000. + Helpers.WriteNetworkShort(output, (short)0x00); // end-value-length Always 0x0000. + } + else + { + output.WriteByte((byte)this.ValueType); // value-tag Type of attribute. + + if (string.IsNullOrEmpty(attributeName)) + { + Helpers.WriteNetworkShort(output, (short)0x0000); // name-length 0x0000 for additional values. + } + else + { + Helpers.WriteNetworkShort(output, (short)attributeName.Length); // name-length Length of the attribute name. + Helpers.WriteAsciiString(output, attributeName); // name Attribute name. + } + + Helpers.WriteNetworkShort(output, (short)this.Value.Length); // value-length Length of the attribute value. + output.Write(this.Value, 0, this.Value.Length); // value Value of the attribute. + } + } + + /// + /// Serializes a member attribute. + /// + /// The output stream. + public void SerializeAsMemberAttribute(Stream output, string memberAttributeName, int collectionDepth) + { + // Setup the member attribute: https://tools.ietf.org/html/rfc8010#section-3.1.6 + if (this.IsCollectionAttributeValue()) + { + const int MaxCollectionDepthAllowed = 5; + + // Limit how many layers of collection of collection are allowed. + if (collectionDepth++ > MaxCollectionDepthAllowed) + { + throw new IPPException( + StatusCode.ClientErrorBadRequest, + FormattableString.Invariant($"Max collection depth of {MaxCollectionDepthAllowed} reached.")); + } + + // Setup this collection attribute as a member attribute. + output.WriteByte((byte)Tag.MemberAttrName); // value-tag This is a member attribute: 0x4a. + Helpers.WriteNetworkShort(output, (short)0x00); // name-length Always 0, signifying a member attr. + + if (string.IsNullOrEmpty(memberAttributeName)) + { + Helpers.WriteNetworkShort(output, (short)0x0000); // name-length 0x0000 for additional values. + } + else + { + Helpers.WriteNetworkShort(output, (short)memberAttributeName.Length); // value-length Length of member-name. + Helpers.WriteAsciiString(output, memberAttributeName); // value The member-name. + } + + output.WriteByte((byte)Tag.BegCollection); // member-value-tag This is a collection member attribute. + Helpers.WriteNetworkShort(output, (short)0x00); // name-length Second name length, always 0. + Helpers.WriteNetworkShort(output, (short)0x00); // member-value-length Always zero as what follows are member attributes. + + // Serialize the member attributes. + foreach (var memberAttribute in this.memberAttributes) + { + memberAttribute.SerializeMemberAttribute(output, collectionDepth); + } + + // Complete this collection attribute. + output.WriteByte((byte)Tag.EndCollection); // end-value-tag Always 0x37 denoting end of the collection. + Helpers.WriteNetworkShort(output, (short)0x00); // end-name-length Always 0x0000. + Helpers.WriteNetworkShort(output, (short)0x00); // end-value-length Always 0x0000. + } + else + { + output.WriteByte((byte)Tag.MemberAttrName); // value-tag This is a member attribute: 0x4a. + Helpers.WriteNetworkShort(output, (short)0x0000); // name-length Always 0, signifying a member attr. + + if (string.IsNullOrEmpty(memberAttributeName)) + { + Helpers.WriteNetworkShort(output, (short)0x0000); // name-length 0x0000 for additional values. + } + else + { + Helpers.WriteNetworkShort(output, (short)memberAttributeName.Length); // value-length Length of member-name. + Helpers.WriteAsciiString(output, memberAttributeName); // value The member-name. + } + + output.WriteByte((byte)this.ValueType); // member-value-tag Type of member attribute. + Helpers.WriteNetworkShort(output, (short)0x0000); // name-length Second name length, always 0. + Helpers.WriteNetworkShort(output, (short)this.Value.Length); // member-value-length Length of the member attribute value. + output.Write(this.Value, 0, this.Value.Length); // member-value The value of the member attribute. + } + } + + public override string ToString() + { + if (this.IsCollectionAttributeValue()) + { + var sb = new StringBuilder(); + + sb.AppendFormat(CultureInfo.InvariantCulture, "Collection Attribute Value - Member Attribute list:\n"); + foreach (var memberAttribute in this.memberAttributes) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "\t" + memberAttribute.ToString() + "\n"); + } + + return sb.ToString(); + } + else + { + return string.Format( + CultureInfo.InvariantCulture, + "SimpleIppValue-Type:{0}-Value:{1}\n", + this.ValueType, + (object)this.GetNativeValue()); + } + } + + /// + /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. + /// Attribute comparison is used by test code. + /// + public override int GetHashCode() + { + return base.GetHashCode(); + } + + /// + /// Compare two IppValue objects. + /// + public override bool Equals(object obj) + { + if (obj is IppValue other) + { + if (this.IsCollectionAttributeValue()) + { + var isCollectionValueEqual = this.IsCollectionAttributeValue().Equals(other.IsCollectionAttributeValue()); + if (isCollectionValueEqual) + { + var areMemberAttributesEqual = this.memberAttributes.SequenceEqual(other.memberAttributes); + return areMemberAttributesEqual; + } + } + else + { + var isValueTypeEqual = this.ValueType.Equals(other.ValueType); + var isValueEqual = this.Value.SequenceEqual(other.Value); + return isValueTypeEqual && isValueEqual; + } + } + + return false; + } + + /// + /// Return a member attribute with name that matches memberAttributeName. + /// + /// The name of the member attribute. + /// The member attribute. + public IppMemberAttribute GetMemberAttribute(string memberAttributeName) + { + if (!this.IsCollectionAttributeValue()) + { + return null; + } + + return this.memberAttributes.Find(x => string.Equals(x.ValueName, memberAttributeName, StringComparison.Ordinal)); + } + + /// + /// Return a member attribute with name that matches memberAttributeName. + /// + /// The name of the member attribute. + /// The member attribute. + public object GetMemberAttributeNativeValue(string memberAttributeName) + { + if (!this.IsCollectionAttributeValue()) + { + return null; + } + + return this.memberAttributes.Find(x => string.Equals(x.ValueName, memberAttributeName, StringComparison.Ordinal)).FirstValue.GetNativeValue(); + } + + public static bool IsUPKnownMediaTypeKeyword(string mediaType) + { + switch (mediaType.ToLower()) + { + case MediaTypes.Aluminum: + case MediaTypes.Auto: + case MediaTypes.BackPrintFilm: + case MediaTypes.Cardboard: + case MediaTypes.Cardstock: + case MediaTypes.CardstockCoated: + case MediaTypes.CardstockHeavyweight: + case MediaTypes.CardstockHeavyweightCoated: + case MediaTypes.CardstockLightweight: + case MediaTypes.CardstockLightweightCoated: + case MediaTypes.Cd: + case MediaTypes.Continuous: + case MediaTypes.ContinuousLong: + case MediaTypes.ContinuousShort: + case MediaTypes.CorrugatedBoard: + case MediaTypes.Disc: + case MediaTypes.DiscGlossy: + case MediaTypes.DiscHighGloss: + case MediaTypes.DiscMatte: + case MediaTypes.DiscSatin: + case MediaTypes.DiscSemiGloss: + case MediaTypes.DoubleWall: + case MediaTypes.DryFilm: + case MediaTypes.Dvd: + case MediaTypes.EmbossingFoil: + case MediaTypes.EndBoard: + case MediaTypes.Envelope: + case MediaTypes.EnvelopeArchival: + case MediaTypes.EnvelopeBond: + case MediaTypes.EnvelopeCoated: + case MediaTypes.EnvelopeCotton: + case MediaTypes.EnvelopeFine: + case MediaTypes.EnvelopeHeavyweight: + case MediaTypes.EnvelopeInkjet: + case MediaTypes.EnvelopeLightweight: + case MediaTypes.EnvelopePlain: + case MediaTypes.EnvelopePreprinted: + case MediaTypes.EnvelopeWindow: + case MediaTypes.Fabric: + case MediaTypes.FabricArchival: + case MediaTypes.FabricGlossy: + case MediaTypes.FabricHighGloss: + case MediaTypes.FabricMatte: + case MediaTypes.FabricSemiGloss: + case MediaTypes.FabricWaterproof: + case MediaTypes.Film: + case MediaTypes.FlexoBase: + case MediaTypes.FlexoPhotoPolymer: + case MediaTypes.Flute: + case MediaTypes.Foil: + case MediaTypes.FullCutTabs: + case MediaTypes.Glass: + case MediaTypes.GlassColored: + case MediaTypes.GlassOpaque: + case MediaTypes.GlassSurfaced: + case MediaTypes.GlassTextured: + case MediaTypes.GravureCylinder: + case MediaTypes.ImageSetterPaper: + case MediaTypes.ImagingCylinder: + case MediaTypes.Labels: + case MediaTypes.LabelsColored: + case MediaTypes.LabelsContinuous: + case MediaTypes.LabelsGlossy: + case MediaTypes.LabelsHeavyweight: + case MediaTypes.LabelsHighGloss: + case MediaTypes.LabelsInkjet: + case MediaTypes.LabelsLightweight: + case MediaTypes.LabelsMatte: + case MediaTypes.LabelsPermanent: + case MediaTypes.LabelsSatin: + case MediaTypes.LabelsSecurity: + case MediaTypes.LabelsSemiGloss: + case MediaTypes.LaminatingFoil: + case MediaTypes.Letterhead: + case MediaTypes.Metal: + case MediaTypes.MetalGlossy: + case MediaTypes.MetalHighGloss: + case MediaTypes.MetalMatte: + case MediaTypes.MetalSatin: + case MediaTypes.MetalSemiGloss: + case MediaTypes.MountingTape: + case MediaTypes.MultiLayer: + case MediaTypes.MultiPartForm: + case MediaTypes.Other: + case MediaTypes.Paper: + case MediaTypes.Photographic: + case MediaTypes.PhotographicArchival: + case MediaTypes.PhotographicFilm: + case MediaTypes.PhotographicGlossy: + case MediaTypes.PhotographicHighGloss: + case MediaTypes.PhotographicMatte: + case MediaTypes.PhotographicSatin: + case MediaTypes.PhotographicSemiGloss: + case MediaTypes.Plastic: + case MediaTypes.PlasticArchival: + case MediaTypes.PlasticColored: + case MediaTypes.PlasticGlossy: + case MediaTypes.PlasticHighGloss: + case MediaTypes.PlasticMatte: + case MediaTypes.PlasticSatin: + case MediaTypes.PlasticSemiGloss: + case MediaTypes.Plate: + case MediaTypes.Polyester: + case MediaTypes.PreCutTabs: + case MediaTypes.Roll: + case MediaTypes.Screen: + case MediaTypes.ScreenPaged: + case MediaTypes.SelfAdhesive: + case MediaTypes.SelfAdhesiveFilm: + case MediaTypes.ShrinkFoil: + case MediaTypes.SingleFace: + case MediaTypes.SingleWall: + case MediaTypes.Sleeve: + case MediaTypes.Stationery: + case MediaTypes.StationeryArchival: + case MediaTypes.StationeryBond: + case MediaTypes.StationeryCoated: + case MediaTypes.StationeryColored: + case MediaTypes.StationeryCotton: + case MediaTypes.StationeryFine: + case MediaTypes.StationeryHeavyweight: + case MediaTypes.StationeryHeavyweightCoated: + case MediaTypes.StationeryInkjet: + case MediaTypes.StationeryLetterhead: + case MediaTypes.StationeryLightweight: + case MediaTypes.StationeryPreprinted: + case MediaTypes.StationeryPrepunched: + case MediaTypes.StationeryRecycled: + case MediaTypes.TabStock: + case MediaTypes.Tractor: + case MediaTypes.Transfer: + case MediaTypes.Transparency: + case MediaTypes.TripleWall: + case MediaTypes.WetFilm: + return true; + + default: + return false; + } + } + + /// + /// Validate that there are expected number of bytes for the given type. + /// + private static void ValidateDataSize(Tag type, byte[] value) + { + var invalid = false; + switch (type) + { + case Tag.Integer: + case Tag.Enum: + invalid = value.Length != 4; + break; + + case Tag.RangeOfInteger: + invalid = value.Length != 8; + break; + + case Tag.Boolean: + invalid = value.Length != 1; + break; + + case Tag.OctetString: + // See: https://tools.ietf.org/html/rfc8011#section-5.1.11 + invalid = value.Length > 1023; + break; + + case Tag.DateTime: + // See:https://tools.ietf.org/html/rfc2579 page 18. + invalid = value.Length != 11; + break; + + case Tag.Resolution: + // See: https://tools.ietf.org/html/rfc8011#section-5.1.16. + invalid = value.Length != 9; + break; + + case Tag.TextWithLanguage: + case Tag.NameWithLanguage: + // Note: from the RFC: + // a. a SIGNED-SHORT which is the number of + // octets in the following field + // b. a value of type natural-language, + // c. a SIGNED-SHORT which is the number of + // octets in the following field, + // d. a value of type textWithoutLanguage. + // The length of a textWithLanguage value MUST be + // 2 + the value of field a + 2 + the value of field c. + + // Validate that the buffer is at least 4 bytes long (the + // degenerate case). + invalid = value.Length < 4; + + // Validate the length of the buffer based on the length fields + if (!invalid) + { + short nlLength = Helpers.ByteArrayToShort(value); + invalid = value.Length < 2 + nlLength; + + if (!invalid) + { + short textLength = (short)(ushort)((value[2 + nlLength] << 8) | value[2 + nlLength + 1]); + invalid = value.Length != 2 + nlLength + 2 + textLength; + } + } + + break; + + case Tag.TextWithoutLanguage: + case Tag.NameWithoutLanguage: + case Tag.Keyword: + case Tag.Uri: + case Tag.UriScheme: + case Tag.Charset: + case Tag.NaturalLanguage: + case Tag.MimeMediaType: + // These cases require no specific validation, they are string values. + // For some of these (keyword, name, etc) having a zero-length string + // seems odd, but I can find nothing in the RFC specifically disallowing it. + invalid = false; + break; + + case Tag.NoValue: + case Tag.Unknown: + invalid = value.Length != 0; + break; + + case Tag.BegCollection: + // TBD: check that this is a valid collection attribute. + invalid = false; + break; + + default: + throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid type in encoding."); + } + + if (invalid) + { + throw new IPPException(StatusCode.ClientErrorBadRequest, FormattableString.Invariant($"Invalid encoding for type {type}.")) + { + DetailedInternalInfo = FormattableString.Invariant($"Invalid encoding for type {type}. Got {value.Length} bytes of data '{BitConverter.ToString(value).Replace("-", string.Empty)}'") + }; + } + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/JobAttributes.cs b/BadgeReleaseDemo/IppLibrary/JobAttributes.cs new file mode 100644 index 0000000..e59d4a4 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/JobAttributes.cs @@ -0,0 +1,214 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + /// + /// A list of job attributes defined in the following IPP specs: + /// https://tools.ietf.org/html/rfc8011 + /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf + /// A list of job template attributes with "-actual" keyword suffix + /// https://ftp.pwg.org/pub/pwg/candidates/cs-ippactuals10-20030313-5100.8.pdf + /// + public static class JobAttributes + { + public const string CompressionSupplied = "compression-supplied"; + public const string Copies = DocumentAttributes.Copies; + + /// + /// Indicates the actual value of copies printed from the job's PT. + /// + public const string CopiesActual = DocumentAttributes.Copies + "-actual"; + + public const string CoverType = "cover-type"; + public const string DateTimeAtCompleted = "date-time-at-completed"; + public const string DateTimeAtCreation = "date-time-at-creation"; + public const string DateTimeAtProcessing = "date-time-at-processing"; + public const string DetailedStatusMessage = "detailed-status-message"; + public const string DocumentAccessError = "document-access-error"; + public const string DocumentFormatSupplied = "document-format-supplied"; + public const string DocumentFormatVersionSupplied = "document-format-version-supplied"; + public const string DocumentNameSupplied = "document-name-supplied"; + public const string FeedOrientation = DocumentAttributes.FeedOrientation; + public const string Finishings = DocumentAttributes.Finishings; + public const string FinishingsCol = DocumentAttributes.FinishingsCol; + public const string IppAttributeFidelity = OperationAttributes.IppAttributeFidelity; + public const string JobHoldUntil = "job-hold-until"; + public const string JobAccountId = "job-account-id"; + public const string JobAccountingUserId = "job-accounting-user-id"; + public const string JobId = OperationAttributes.JobId; + public const string JobImpressions = "job-impressions"; + public const string JobImpressionsCompleted = "job-impressions-completed"; + public const string JobKOctets = "job-k-octets"; + public const string JobKOctetsCompleted = "job-k-octets-completed"; + public const string JobKOctetsProcessed = "job-k-octets-processed"; + public const string JobMandatoryAttributes = "job-mandatory-attributes"; + public const string JobMediaSheets = "job-media-sheets"; + public const string JobMediaSheetsCompleted = "job-media-sheets-completed"; + public const string JobMessageFromOperator = "job-message-from-operator"; + public const string JobMoreInfo = "job-more-info"; + public const string JobName = "job-name"; + public const string JobOriginatingUserName = "job-originating-user-name"; + public const string JobOriginatingUserUri = "job-originating-user-uri"; + public const string JobPagesCompleted = "job-pages-completed"; + public const string JobPagesPerSet = "job-pages-per-set"; + public const string JobPriority = "job-priority"; + public const string JobPrinterUpTime = "job-printer-up-time"; + public const string JobPrinterUri = "job-printer-uri"; + public const string JobReleaseAction = "job-release-action"; + public const string JobReleaseActionId = "job-release-action-id"; + public const string JobReleaseActionActual = "job-release-action-actual"; + public const string JobSheets = "job-sheets"; + public const string JobState = "job-state"; + public const string JobStateMessage = "job-state-message"; + public const string JobStateReasons = "job-state-reasons"; + public const string JobUuid = "job-uuid"; + public const string JobUri = OperationAttributes.JobUri; + public const string Media = DocumentAttributes.Media; + public const string MediaBackCoating = DocumentAttributes.MediaBackCoating; + public const string MediaBottomMargin = DocumentAttributes.MediaBottomMargin; + public const string MediaCol = DocumentAttributes.MediaCol; + public const string MediaColor = DocumentAttributes.MediaColor; + public const string MediaGrain = DocumentAttributes.MediaGrain; + public const string MediaFrontCoating = DocumentAttributes.MediaFrontCoating; + public const string MediaHoleCount = DocumentAttributes.MediaHoleCount; + public const string MediaInfo = DocumentAttributes.MediaInfo; + public const string MediaKey = DocumentAttributes.MediaKey; + public const string MediaLeftMargin = DocumentAttributes.MediaLeftMargin; + public const string MediaOrderCount = DocumentAttributes.MediaOrderCount; + public const string MediaPreprinted = DocumentAttributes.MediaPreprinted; + public const string MediaRecycled = DocumentAttributes.MediaRecycled; + public const string MediaRightMargin = DocumentAttributes.MediaRightMargin; + public const string MediaSize = DocumentAttributes.MediaSize; + public const string MediaSizeName = DocumentAttributes.MediaSizeName; + public const string MediaSource = DocumentAttributes.MediaSource; + public const string MediaThickness = DocumentAttributes.MediaThickness; + public const string MediaTooth = DocumentAttributes.MediaTooth; + public const string MediaTopMargin = DocumentAttributes.MediaTopMargin; + public const string MediaType = DocumentAttributes.MediaType; + public const string MediaWeightMetric = DocumentAttributes.MediaWeightMetric; + + // Attribute extensions + // See reference: https://tools.ietf.org/html/rfc8011#section-7.2 + + /// + /// Only used by the first party Connector. + /// Indicates the time (in seconds) it took the Connector to fetch the print job. + /// Excludes the time that the job spent waiting to be fetched, + /// i.e. it is only the total time taken by Connector to perform the Acknowledge/Fetch-Job/Document operations. + /// + public const string MicrosoftJobFetchedTimeInSeconds = "microsoft-job-fetched-time-seconds"; + + /// + /// Only used by the first party Connector. + /// Indicates the time (in seconds) it took for the print job to be processed by the Connector before the job was sent to the spooler. + /// Excludes the time that was spent on fetching the job, i.e. timing starts after the Connector finished fetching the job. + /// + public const string MicrosoftJobProcessedTimeInSeconds = "microsoft-job-processed-time-seconds"; + + /// + /// Only used by the first party Connector before 2.2. + /// Indicates the time (in seconds) that the job spent in the Windows spooler before being sent to the printer. + /// Replaced by MicrosoftJobSpoolerTimeInSeconds to reduce confusion. + /// + public const string MicrosoftJobPrintedTimeInSeconds = "microsoft-job-printed-time-seconds"; + + /// + /// Only used by the first party Connector 2.2 and newer. + /// Indicates the time (in seconds) that the job spent in the Windows spooler before being sent to the printer. + /// + public const string MicrosoftJobSpoolerTimeInSeconds = "microsoft-job-spooler-time-in-seconds"; + + /// + /// Only used by the first party Connector 2.2 and newer. + /// Indicates the time (in seconds) that the job spent in the printer after being processed in the Windows spooler. + /// + public const string MicrosoftJobPrinterTimeInSeconds = "microsoft-job-printer-time-in-seconds"; + + /// + /// Indicates the time (in milliseconds) it took for converting a PDF format print job to XPS format by the printer. + /// + public const string MicrosoftPdfToXpsJobConversionTimeInMilliseconds = "microsoft-pdf-to-xps-job-conversion-time-milliseconds"; + + /// + /// Only used by the first party Connector. + /// Indicates the time (in milliseconds) it took for the Connector to generate the print ticket that was sent to the spooler + /// (i.e. time taken to transform and merge-and-validate the print ticket). + /// + public const string MicrosoftPrintTicketGenerationTimeInMilliseconds = "microsoft-print-ticket-generation-time-milliseconds"; + + /// + /// Indicates the extended (error code, message, etc) job state message from the printer. + /// + public const string MicrosoftOutputDeviceJobStateMessage = "microsoft-output-device-job-state-message"; + + /// + /// Only used by the first party Connector. + /// Indicates the print ticket mappings generated from the printer's original PDC/PC. + /// + public const string MicrosoftPrintTicketGenerationMappings = "microsoft-print-ticket-generation-mappings"; + + public const string MultipleDocumentHandling = "multiple-document-handling"; + public const string NumberOfDocuments = "number-of-documents"; + public const string NumberOfInterveningJobs = "number-of-intervening-jobs"; + public const string NumberUp = "number-up"; + public const string OrientationRequested = DocumentAttributes.OrientationRequested; + public const string OutputBin = DocumentAttributes.OutputBin; + public const string OutputDeviceAssigned = "output-device-assigned"; + public const string OutputDeviceJobState = "output-device-job-state"; + public const string OutputDeviceJobStateMessage = "output-device-job-state-message"; + public const string OutputDeviceJobStateReasons = "output-device-job-state-reasons"; + public const string OutputDeviceJobStates = "output-device-job-states"; + public const string Overrides = DocumentAttributes.Overrides; + public const string PageRanges = DocumentAttributes.PageRanges; + public const string PdfFitToPage = "pdf-fit-to-page"; + public const string PageOrderReceived = "page-order-received"; + public const string PresentationDirectionNumberUp = "presentation-direction-number-up"; + public const string PrintColorMode = DocumentAttributes.PrintColorMode; + + /// + /// Indicates the actual value of print-color-mode from the job's PT. + /// + public const string PrintColorModeActual = DocumentAttributes.PrintColorMode + "-actual"; + + public const string PrintScaling = "print-scaling"; + public const string PrintContentOptimize = DocumentAttributes.PrintContentOptimize; + public const string PrintRenderingIntent = DocumentAttributes.PrintRenderingIntent; + public const string PrinterQuality = "printer-quality"; + public const string PrinterResolution = DocumentAttributes.PrinterResolution; + public const string PrintQuality = DocumentAttributes.PrintQuality; + public const string Sides = DocumentAttributes.Sides; + + /// + /// Indicates the actual value of sides printed from the job's PT. + /// + public const string SidesActual = DocumentAttributes.Sides + "-actual"; + + public const string TimeAtCompleted = "time-at-completed"; + public const string TimeAtCreation = "time-at-creation"; + public const string TimeAtProcessing = "time-at-processing"; + public const string XDimension = DocumentAttributes.XDimension; + public const string YDimension = DocumentAttributes.YDimension; + + /// + /// The document numbers that the values passed in the "overrides" + /// job attribute applies to. UP doesn't have multiple documents per job, + /// but we still need to have this in "overrides-supported" to be conformant + /// to AirPrint and IPP specs. + /// Naming is non-standard because this is only permitted as an attribute + /// inside the "overrides" collections. + /// + public const string OverridesDocumentNumbers = "document-numbers"; + + /// + /// The pages that the values passed in the "overrides" job attribute applies to. + /// Naming is non-standard because this is only permitted as an attribute + /// inside the "overrides" collections and because "pages" is a ubiquitous term. + /// + public const string OverridesPages = "pages"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/JobEvents.cs b/BadgeReleaseDemo/IppLibrary/JobEvents.cs new file mode 100644 index 0000000..1b483a3 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/JobEvents.cs @@ -0,0 +1,17 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace BadgeReleaseDemo.IppLibrary +{ + public class JobEvents + { + public const string JobStateChanged = "job-state-changed"; + public const string JobCreated = "job-created"; + public const string JobCompleted = "job-completed"; + public const string JobStopped = "job-stopped"; + public const string JobConfigChanged = "job-config-changed"; + public const string JobProgress = "job-progress"; + public const string JobFetchable = "job-fetchable"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs b/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs new file mode 100644 index 0000000..6459bc8 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + public static class NotifyAttributes + { + public const string NotifyAttributesSupported = "notify-attributes-supported"; + public const string NotifyCharset = "notify-charset"; + public const string NotifyEvents = "notify-events"; + public const string NotifyEventsDefault = "notify-events-default"; + public const string NotifyEventsSupported = "notify-events-supported"; + public const string NotifyGetInterval = "notify-get-interval"; + public const string NotifyJobId = "notify-job-id"; + public const string NotifyLeaseDuration = "notify-lease-duration"; + public const string NotifyLeaseDurationDefault = "notify-lease-duration-default"; + public const string NotifyLeaseDurationSupported = "notify-lease-duration-supported"; + public const string NotifyleaseExpirationTime = "notify-lease-expiration-time"; + public const string NotifyMaxEventsSupported = "notify-max-events-supported"; + public const string NotifyNaturalLanguage = "notify-natural-language"; + public const string NotifyPrinterUptime = "notify-printer-up-time"; + public const string NotifyPrinterUri = "notify-printer-uri"; + public const string NotifyPullMethod = "notify-pull-method"; + public const string NotifyPullMethodSupported = "notify-pull-method-supported"; + public const string NotifyRecipientUri = "notify-recipient-uri"; + public const string NotifySchemesSupported = "notify-schemes-supported"; + public const string NotifySequenceNumber = "notify-sequence-number"; + public const string NotifySequenceNumbers = "notify-sequence-numbers"; + public const string NotifyStatuscode = "notify-status-code"; + public const string NotifySubscribedEvent = "notify-subscribed-event"; + public const string NotifySubscriberUsername = "notify-subscriber-user-name"; + public const string NotifySubscriptionId = "notify-subscription-id"; + public const string NotifySubscriptionIds = "notify-subscription-ids"; + public const string NotifyText = "notify-text"; + public const string NotifyTimeInterval = "notify-time-interval"; + public const string NotifyUserData = "notify-user-data Subscription"; + public const string NotifyWait = "notify-wait"; + public const string NotifyIppGet = "ippget"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/OperationAttributes.cs b/BadgeReleaseDemo/IppLibrary/OperationAttributes.cs new file mode 100644 index 0000000..9a25018 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/OperationAttributes.cs @@ -0,0 +1,60 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + public static class OperationAttributes + { + public const string AttributesCharset = "attributes-charset"; + public const string AttributesNaturalLanguage = "attributes-natural-language"; + public const string Compression = "compression"; + public const string CompressionAccepted = "compression-accepted"; // IPP Infra + public const string DetailedStatusMessage = "detailed-status-message"; + public const string DocumentAccess = "document-access"; + public const string DocumentFormat = "document-format"; + public const string DocumentFormatAccepted = "document-format-accepted"; + public const string DocumentFormatDetails = "document-format-details"; + public const string DocumentFormatName = "document-format-name"; + public const string DocumentName = "document-name"; + public const string DocumentNumber = "document-number"; + public const string DocumentPassword = "document-password"; + public const string DocumentPreprocessed = "document-preprocessed"; + public const string DocumentUri = "document-uri"; + public const string FetchStatusCode = "fetch-status-code"; + public const string FetchStatusMessage = "fetch-status-message"; + public const string FirstIndex = "first-index"; + public const string IdentifyActions = "identify-actions"; + public const string IppAttributeFidelity = "ipp-attribute-fidelity"; + public const string JobId = "job-id"; + public const string JobIds = "job-ids"; + public const string JobImpressions = "job-impressions"; + public const string JobMandatoryAttributes = "job-mandatory-attributes"; + public const string JobName = "job-name"; + public const string JobPassword = "job-password"; + public const string JobPasswordEncryption = "job-password-encryption"; + public const string JobUri = "job-uri"; + public const string LastDocument = "last-document"; + public const string Limit = "limit"; + public const string MyJobs = "my-jobs"; + public const string NotifyGetInterval = "notify-get-interval"; + public const string NotifySequenceNumbers = "notify-sequence-numbers"; + public const string NotifySubscriptionId = "notify-subscription-id"; + public const string NotifySubscriptionIds = "notify-subscription-ids"; + public const string NotifyWait = "notify-wait"; + public const string OutputDeviceJobState = JobAttributes.OutputDeviceJobState; + public const string OutputDeviceJobStates = JobAttributes.OutputDeviceJobStates; + public const string OutputDeviceUuid = "output-device-uuid"; + public const string PrinterUpTime = "printer-up-time"; + public const string PrinterUri = "printer-uri"; + public const string RequestedAttributes = "requested-attributes"; + public const string RequestingUserName = "requesting-user-name"; + public const string RequestingUserUri = "requesting-user-uri"; + public const string StatusMessage = "status-message"; + public const string WhichJobs = "which-jobs"; + public const string ContentRange = "content-range"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs b/BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs new file mode 100644 index 0000000..2eca293 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs @@ -0,0 +1,217 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + public static class PrinterAttributes + { + public const string CharsetConfigured = "charset-configured"; + public const string CharsetSupported = "charset-supported"; + public const string ColorSupported = "color-supported"; + public const string ColorModeSupported = "print-color-mode-supported"; + public const string ColorModeDefault = "print-color-mode-default"; + public const string CompressionSupported = "compression-supported"; + public const string CopiesDefault = "copies-default"; + public const string CopiesSupported = "copies-supported"; + public const string DocumentAccessSupported = "document-access-supported"; + public const string DocumentFormatDetailsSupported = "document-format-details-supported"; + public const string DocumentFormatDefault = "document-format-default"; + public const string DocumentFormatPreferred = "document-format-preferred"; // From Windows zero install impl (Mopria). + public const string DocumentFormatSupported = "document-format-supported"; + public const string DocumentPasswordSupported = "document-password-supported"; + public const string FeedOrientationDefault = "feed-orientation-default"; + public const string FeedOrientationSupported = "feed-orientation-supported"; + public const string FinishingsColDatabase = "finishings-col-database"; + public const string FinishingsColDefault = "finishings-col-default"; + public const string FinishingsColReady = "finishings-col-ready"; + public const string FinishingsColSupported = "finishings-col-supported"; + public const string FinishingsDefault = "finishings-default"; + public const string FinishingsReady = "finishings-ready"; + public const string FinishingsSupported = "finishings-supported"; + public const string GeneratedNaturalLanguageSupported = "generated-natural-language-supported"; + public const string IdentifyActionsDefault = "identify-actions-default"; + public const string IdentifyActionsSupported = "identify-actions-supported"; + public const string IppFeaturesSupported = "ipp-features-supported"; + public const string IppVersionsSupported = "ipp-versions-supported"; + public const string IppgetEventLife = "ippget-event-life"; + public const string JobAccountIdDefault = "job-account-id-default"; + public const string JobAccountIdSupported = "job-account-id-supported"; + public const string JobAccountingUserIdDefault = "job-accounting-user-id-default"; + public const string JobAccountingUserIdSupported = "job-accounting-user-id-supported"; + public const string JobConstraintsSupported = "job-constraints-supported"; + public const string JobCreationAttributesSupported = "job-creation-attributes-supported"; + public const string JobIdsSupported = "job-ids-supported"; + public const string JobImpressionsSupported = "job-impressions-supported"; + public const string JobMandatoryAttributesSupported = "job-mandatory-attributes-supported"; + public const string JobMediaSheetsSupported = "job-media-sheets-supported"; + public const string JobPagesPerSetSupported = "job-pages-per-set-supported"; + public const string JobPasswordSupported = "job-password-supported"; + public const string JobPasswordEncryptionSupported = "job-password-encryption-supported"; + public const string JobPasswordLengthSupported = "job-password-length-supported"; + public const string JobReleaseActionDefault = "job-release-action-default"; + public const string JobReleaseActionSupported = "job-release-action-supported"; + public const string JobResolversSupported = "job-resolvers-supported"; + public const string JobSheetsDefault = "job-sheets-default"; + public const string JobSheetsSupported = "job-sheets-supported"; + public const string LandscapeOrientationRequestedPreferred = "landscape-orientation-requested-preferred"; + public const string MarginsPreAppliedDefault = "margins-pre-applied-default"; + public const string MarginsPreAppliedSupported = "margins-pre-applied-supported"; + public const string MediaBottomMarginSupported = "media-bottom-margin-supported"; + public const string MediaColDatabase = "media-col-database"; + public const string MediaColDefault = "media-col-default"; + public const string MediaColReady = "media-col-ready"; + public const string MediaColSupported = "media-col-supported"; + public const string MediaColorSupported = "media-color-supported"; + public const string MediaDefault = "media-default"; + public const string MediaLeftMarginSupported = "media-left-margin-supported"; + public const string MediaReady = "media-ready"; + public const string MediaRightMarginSupported = "media-right-margin-supported"; + public const string MediaSizeSupported = "media-size-supported"; + public const string MediaSourceDefault = "media-source-default"; + public const string MediaSourceFeedDirection = "media-source-feed-direction"; + public const string MediaSourceFeedOrientation = "media-source-feed-orientation"; + public const string MediaSourceProperties = "media-source-properties"; + public const string MediaSourceSupported = "media-source-supported"; + public const string MediaSupported = "media-supported"; + public const string MediaTopMargin = "media-top-margin"; + public const string MediaTopMarginSupported = "media-top-margin-supported"; + public const string MediaTypeSupported = "media-type-supported"; + public const string MopriaCertified = "mopria-certified"; + public const string MultipleDocumentHandlingDefault = "multiple-document-handling-default"; + public const string MultipleDocumentHandlingSupported = "multiple-document-handling-supported"; + public const string MultipleDocumentJobsSupported = "multiple-document-jobs-supported"; + public const string MultipleOperationTimeout = "multiple-operation-time-out"; + public const string MultipleOperationTimeoutAction = "multiple-operation-time-out-action"; + public const string NaturalLanguageConfigured = "natural-language-configured"; + public const string NumberUpDefault = "number-up-default"; + public const string NotifyPullMethodSupported = "notify-pull-method-supported"; + public const string NumberUpSupported = "number-up-supported"; + public const string OauthAuthorizationServerUri = "oauth-authorization-server-uri"; + public const string OperationsSupported = "operations-supported"; + public const string OrientationRequestedDefault = "orientation-requested-default"; + public const string OrientationRequestedSupported = "orientation-requested-supported"; + public const string OutputBinDefault = "output-bin-default"; + public const string OutputBinSupported = "output-bin-supported"; + public const string OverridesSupported = "overrides-supported"; + public const string PageRangesSupported = "page-ranges-supported"; + public const string PageColorModeDefault = "page-color-mode-default"; + public const string PageColorModeSupported = "page-color-mode-supported"; + public const string PageOrderReceivedSupported = "page-order-received-supported"; + public const string PageOrderReceivedDefault = "page-order-received-default"; + public const string PagesPerMinute = "pages-per-minute"; + public const string PagesPerMinuteColor = "pages-per-minute-color"; + public const string PclmRasterBackSide = "pclm-raster-back-side"; + public const string PclmSourceResolutionSupported = "pclm-source-resolution-supported"; + public const string PclmStripHeightPreferred = "pclm-strip-height-preferred"; + public const string PclmStripHeightSupported = "pclm-strip-height-supported"; + public const string PdlOverrideSupported = "pdl-override-supported"; + public const string PdfFitToPageDefault = "pdf-fit-to-page-default"; // from Windows zero install impl (Mopria). + public const string PdfFitToPageSupported = "pdf-fit-to-page-supported"; // from Windows zero install impl (Mopria). + public const string PdfKOctetsSupported = "pdf-k-octets-supported"; + public const string PdfSizeConstraints = "pdf-size-constraints"; + public const string PdfVersionsSupported = "pdf-versions-supported"; + public const string PresentationDirectionNumberUpDefault = "presentation-direction-number-up-default"; + public const string PresentationDirectionNumberUpSupported = "presentation-direction-number-up-supported"; + public const string PrinterConfigChangeTime = "printer-config-change-time"; + public const string PrinterConfigChangeDateTime = "printer-config-change-date-time"; + public const string PrintColorModeDefault = "print-color-mode-default"; + public const string PrintColorModeSupported = "print-color-mode-supported"; + public const string PrintContentOptimizeDefault = "print-content-optimize-default"; + public const string PrintContentOptimizeSupported = "print-content-optimize-supported"; + public const string PrintRenderingIntentDefault = "print-rendering-intent-default"; + public const string PrintRenderingIntentSupported = "print-rendering-intent-supported"; + public const string PrintQualityDefault = "print-quality-default"; + public const string PrintQualitySupported = "print-quality-supported"; + public const string PrintScalingDefault = "print-scaling-default"; + public const string PrintScalingSupported = "print-scaling-supported"; + public const string PrinterAlert = "printer-alert"; + public const string PrinterAlertDescription = "printer-alert-description"; + public const string PrinterCurrentTime = "printer-current-time"; + public const string PrinterDeviceId = "printer-device-id"; + public const string PrinterFirmwareName = "printer-firmware-name"; + public const string PrinterFirmwarePatches = "printer-firmware-patches"; + public const string PrinterFirmwareStringVersion = "printer-firmware-string-version"; + public const string PrinterFirmwareVersion = "printer-firmware-version"; + public const string PrinterGeoLocation = "printer-geo-location"; + public const string PrinterGetAttributesSupported = "printer-get-attributes-supported"; + public const string PrinterIccProfiles = "printer-icc-profiles"; + public const string PrinterIcons = "printer-icons"; + public const string PrinterInfo = "printer-info"; + public const string PrinterInputTray = "printer-input-tray"; + public const string PrinterIsAcceptingJobs = "printer-is-accepting-jobs"; + public const string PrinterKind = "printer-kind"; + public const string PrinterLocation = "printer-location"; + public const string PrinterMakeAndModel = "printer-make-and-model"; + public const string PrinterMoreInfo = "printer-more-info"; + public const string PrinterMoreInfoManufacturer = "printer-more-info-manufacturer"; + public const string PrinterName = "printer-name"; + public const string PrinterOrganization = "printer-organization"; + public const string PrinterOrganizationalUnit = "printer-organizational-unit"; + public const string PrinterOutputTray = "printer-output-tray"; + public const string PrinterResolutionDefault = "printer-resolution-default"; + public const string PrinterResolutionSupported = "printer-resolution-supported"; + public const string PrinterState = "printer-state"; + public const string PrinterStateChangeDateTime = "printer-state-change-date-time"; + public const string PrinterStateChangeTime = "printer-state-change-time"; + public const string PrinterStateChangeMessage = "printer-state-change-message"; + public const string PrinterStateMessage = "printer-state-message"; + public const string PrinterStateReasons = "printer-state-reasons"; + public const string PrinterStaticResourceDirectoryUri = "printer-static-resource-directory-uri"; + public const string PrinterStaticResourceKOctetsFree = "printer-static-resource-k-octets-free"; + public const string PrinterStaticResourceKOctetsSupported = "printer-static-resource-k-octets-supported"; + public const string PrinterSupply = "printer-supply"; + public const string PrinterSupplyDescription = "printer-supply-description"; + public const string PrinterSupplyInfoUri = "printer-supply-info-uri"; + public const string PrinterUpTime = "printer-up-time"; + public const string PrinterUriSupported = "printer-uri-supported"; + public const string PrinterUuid = "printer-uuid"; + public const string PwgRasterDocumentResolutionSupported = "pwg-raster-document-resolution-supported"; + public const string PwgRasterDocumentSheetBack = "pwg-raster-document-sheet-back"; + public const string PwgRasterDocumentTypeSupported = "pwg-raster-document-type-supported"; + public const string QueuedJobCount = "queued-job-count"; + public const string SidesDefault = "sides-default"; + public const string SidesSupported = "sides-supported"; + public const string UrfSupported = "urf-supported"; + public const string UriSecuritySupported = "uri-security-supported"; + public const string UriAuthenticationSupported = "uri-authentication-supported"; + public const string WhichJobsSupported = "which-jobs-supported"; + public const string PreferredChunkSizeKOctets = "preferred-chunk-size-k-octets"; + + // Attribute extensions + // See reference: https://tools.ietf.org/html/rfc8011#section-7.2 + public const string MicrosoftPageOrderDefault = "microsoft-page-order-default"; + public const string MicrosoftPageOrderSupported = "microsoft-page-order-supported"; + public const string MicrosoftUniversalPrintConnectorAppVersion = "microsoft-universal-print-connector-app-version"; + public const string MicrosoftUniversalPrintConnectorOperatingSystem = "microsoft-universal-print-connector-operating-system"; + public const string MicrosoftUniversalPrintConnectorId = "microsoft-universal-print-connector-id"; + public const string MicrosoftUniversalPrinterDriverName = "microsoft-universal-printer-driver-name"; + public const string MicrosoftUniversalPrinterDriverVersion = "microsoft-universal-printer-driver-version"; + public const string MicrosoftUniversalPrintDocumentFormatSupportedViaConversion = "microsoft-universal-print-document-format-supported-via-conversion"; + + // smi attributes + public const string PullPrintEnabledWithOEMJobRelease = "smi311-universal-print-anywhere-enabled"; + + // Custom Windows extensions + public const string PrintDeviceCapabilities = "print-device-capabilites"; // Universal Print connector sends printer's PDC. + public const string PrintDeviceResources = "print-device-resources"; + + // CUPS Printer Attributes. (Optional for Mopria 2.0 support) + // Ref: Mopria 2.0 spec Section 4.14 + // https://microsoft.sharepoint.com/:b:/t/STACKTeam-CoreNetworkingMobileConnectivityPeripheralsStackSe/EYP81I7FD95JiQdMtD14FlEBMHbF1pbe_1aYO9kPVN-w-w?e=rJxA7L + // CUPS spec: https://www.cups.org/doc/spec-ipp.html + public const string MarkerColors = "marker-colors"; + public const string MarkerHighLevels = "marker-high-levels"; + public const string MarkerLevels = "marker-levels"; + public const string MarkerLowLevels = "marker-low-levels"; + public const string MarkerNames = "marker-names"; + public const string MarkerTypes = "marker-types"; + + // Ref: Mopria 2.0 Section 4.5.1 + // https://microsoft.sharepoint.com/:b:/t/STACKTeam-CoreNetworkingMobileConnectivityPeripheralsStackSe/EYP81I7FD95JiQdMtD14FlEBMHbF1pbe_1aYO9kPVN-w-w?e=rJxA7L + public const string PrintWFDS = "print_wfds"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/PrinterEvents.cs b/BadgeReleaseDemo/IppLibrary/PrinterEvents.cs new file mode 100644 index 0000000..094cbf8 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/PrinterEvents.cs @@ -0,0 +1,18 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +namespace BadgeReleaseDemo.IppLibrary +{ + public class PrinterEvents + { + public const string PrinterStateChanged = "printer-state-changed"; + public const string PrinterRestarted = "printer-restarted"; + public const string PrinterShutdown = "printer-shutdown"; + public const string PrinterStopped = "printer-stopped"; + public const string PrinterConfigChanged = "printer-config-changed"; + public const string PrinterMediaChanged = "printer-media-changed"; + public const string PrinterFinishingsChanged = "printer-finishings-changed"; + public const string PrinterQueueorderChanged = "printer-queue-order-changed"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs b/BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs new file mode 100644 index 0000000..9aad39e --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs @@ -0,0 +1,81 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Text; + + /// + /// The raw handling of IPP encoding. + /// + public abstract class RawIppEncodingBase : IDisposable + { + /// + /// Gets or sets the major version number. + /// + public sbyte MajorVersionNumber { get; set; } + + /// + /// Gets or sets the minor version number. + /// + public sbyte MinorVersionNumber { get; set; } + + /// + /// Gets or sets the attribute group list. + /// + public List BaseAttributeGroups { get; set; } = new List(); + + /// + /// Gets or sets the print data payload. + /// + public Stream Data { get; set; } + + /// + /// Serialize to string. + /// + /// Serialized string. + public override string ToString() + { + var sb = new StringBuilder(); + + sb.AppendFormat(CultureInfo.InvariantCulture, "Version: {0}.{1}\n", this.MajorVersionNumber, this.MinorVersionNumber); + sb.Append("Attributes:\n"); + + foreach (IppAttributeGroup group in this.BaseAttributeGroups) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\n", group); + } + + // Note that the stream length will be equal to the stream position for chunked requests, not the total length of the stream + if (this.Data != null && this.Data.CanSeek) + { + sb.AppendFormat(CultureInfo.InvariantCulture, "Data: {0} bytes", this.Data.Length); + } + + return sb.ToString(); + } + + /// + /// Dispose. + /// + public virtual void Dispose() => this.Data?.Dispose(); + + /// + /// Suport IPP version 1.2. + /// + public void CheckVersion() + { + if (this.MinorVersionNumber != (sbyte)IppMajorVersion.Version1 && this.MajorVersionNumber != (sbyte)IppMajorVersion.Version2) + { + throw new IPPException(BadgeReleaseDemo.IppLibrary.StatusCode.ServerErrorVersionNotSupported, FormattableString.Invariant($"IPP version {this.MajorVersionNumber}.{this.MinorVersionNumber} not supported.")); + } + } + } +} diff --git a/BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs b/BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs new file mode 100644 index 0000000..1ed6f0d --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs @@ -0,0 +1,16 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + public static class RequestedAttributes + { + public const string All = "all"; + public const string JobTemplate = "job-template"; + public const string JobDescription = "job-description"; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs b/BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs new file mode 100644 index 0000000..a0b43ae --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs @@ -0,0 +1,15 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +// ReSharper disable once CheckNamespace +namespace BadgeReleaseDemo.IppLibrary.Common +{ + public static class RequiredAttributes + { + public const string AttributesCharset = OperationAttributes.AttributesCharset; + public const string AttributesNaturalLanguage = OperationAttributes.AttributesNaturalLanguage; + } +} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs b/BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs new file mode 100644 index 0000000..4d16271 --- /dev/null +++ b/BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs @@ -0,0 +1,21 @@ +//----------------------------------------------------------------------- +// +// Copyright (c) Microsoft. All rights reserved. +// +//----------------------------------------------------------------------- + +namespace BadgeReleaseDemo.IppLibrary +{ + using System; + + /// + /// ZeroByteReadException is expected to be thrown when the initial read from the stream returns 0 bytes + /// + public class ZeroByteReadException : Exception + { + public ZeroByteReadException(string message) + : base(message) + { + } + } +} diff --git a/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs b/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs new file mode 100644 index 0000000..f0d3b67 --- /dev/null +++ b/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs @@ -0,0 +1,315 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.Diagnostics; +using System.Net.Http.Headers; +using BadgeReleaseDemo.Helpers; +using BadgeReleaseDemo.IppLibrary; +using BadgeReleaseDemo.IppLibrary.Common; + +namespace BadgeReleaseDemo.IppOperations; + +/// +/// Performs IPP INFRA operations as a printer: Get-Jobs, Fetch-Job, +/// Acknowledge-Job, Fetch-Document, and Update-Job-Status. +/// Uses the IppLibrary for IPP request building and serialization. +/// +public class PrinterIppClient +{ + private readonly string ippServiceBaseUrl; + private readonly string ippServicePrinterPath; + private readonly string badgesApiPath; + private readonly HttpClient httpClient; + + public PrinterIppClient(string ippServiceBaseUrl, string ippServicePrinterPath, string badgesApiPath) + { + this.ippServiceBaseUrl = ippServiceBaseUrl.TrimEnd('/'); + this.ippServicePrinterPath = ippServicePrinterPath; + this.badgesApiPath = badgesApiPath; + httpClient = new HttpClient { Timeout = TimeSpan.FromMinutes(5) }; + } + + /// + /// Calls the IPPService BadgesController to resolve a badge ID to a user. + /// GET /api/v1.0/badges/{badgeId} + /// Returns (badgeId, userUri, userId) or null if not found. + /// + public async Task<(string BadgeId, string UserUri, string? UserId)?> ResolveBadgeAsync( + string printerToken, string badgeId) + { + using var request = new HttpRequestMessage(HttpMethod.Get, + $"{ippServiceBaseUrl}{badgesApiPath}/{badgeId}"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", printerToken); + + var response = await httpClient.SendAsync(request); + var body = await response.Content.ReadAsStringAsync(); + + if (response.StatusCode == System.Net.HttpStatusCode.NotFound) + { + return null; + } + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Badge resolution failed: {response.StatusCode} - {body}"); + } + + var doc = System.Text.Json.JsonSerializer.Deserialize(body); + var resolvedBadgeId = doc.GetProperty("badgeId").GetString()!; + var userUri = doc.GetProperty("userURI").GetString()!; + string? userId = doc.TryGetProperty("userId", out var uidProp) ? uidProp.GetString() : null; + + return (resolvedBadgeId, userUri, userId); + } + + /// + /// Sends Get-Jobs IPP request as the printer to find fetchable jobs for a user. + /// Returns list of (jobId, jobUri) tuples. + /// + public async Task> GetJobsAsync( + string printerToken, string printerId, string requestingUserUri) + { + var ippHost = new Uri(ippServiceBaseUrl).Host; + var factory = IppFactoryHelper.CreateIppRequestFactory( + ippHost, printerId, string.Empty, requestingUserUri); + + var requestedAttributes = new List + { + new IppAttribute(RequestedAttributes.All) + }; + + var ippRequest = await factory.CreateGetJobsRequestAsync( + requestId: 1, + jobType: "fetchable", + requestingUserName: string.Empty, + requestingUserUri: requestingUserUri, + printerUri: $"ipps://{ippHost}/printers/{printerId}", + outputDeviceUuid: printerId, + requestedAttributes: requestedAttributes); + + var ippResponse = await SendIppRequestAsync(printerToken, ippRequest); + + if (ippResponse.StatusCode != StatusCode.SuccessfulOk) + { + ConsoleHelper.WriteWarning($"Get-Jobs returned status: {ippResponse.StatusCode}"); + return new List<(int, string)>(); + } + + var jobs = new List<(int JobId, string JobUri)>(); + var jobGroups = ippResponse.LookupAttributeGroup(Tag.JobAttributes); + + foreach (var group in jobGroups) + { + int jobId = 0; + string jobUri = string.Empty; + + if (group.Attributes.TryGetValue(JobAttributes.JobId, out var jobIdAttr)) + { + jobId = jobIdAttr.FirstValue.GetNativeValue(); + } + + if (group.Attributes.TryGetValue(JobAttributes.JobUri, out var jobUriAttr)) + { + jobUri = jobUriAttr.FirstValue.GetNativeValue() ?? string.Empty; + } + + if (jobId > 0) + { + jobs.Add((jobId, jobUri)); + } + } + + return jobs; + } + + /// + /// Sends Fetch-Job IPP request to get job metadata. + /// + public async Task FetchJobAsync( + string printerToken, string printerId, int jobId, string requestingUserUri) + { + var ippHost = new Uri(ippServiceBaseUrl).Host; + var factory = IppFactoryHelper.CreateIppRequestFactory( + ippHost, printerId, string.Empty, requestingUserUri); + + var ippRequest = await factory.CreateFetchJobRequestAsync( + requestId: 2, + outputDeviceUuid: printerId, + jobId: jobId); + + return await SendIppRequestAsync(printerToken, ippRequest); + } + + /// + /// Sends Acknowledge-Job IPP request to confirm receipt of the job. + /// + public async Task AcknowledgeJobAsync( + string printerToken, string printerId, int jobId, string requestingUserUri) + { + var ippHost = new Uri(ippServiceBaseUrl).Host; + var factory = IppFactoryHelper.CreateIppRequestFactory( + ippHost, printerId, string.Empty, requestingUserUri); + + var ippRequest = await factory.CreateAcknowledgeJobRequestAsync( + requestId: 3, + outputDeviceUuid: printerId, + jobId: jobId, + fetchStatusCode: StatusCode.Undefined, + fetchStatusMessage: "Badge release demo - job acknowledged"); + + var response = await SendIppRequestAsync(printerToken, ippRequest); + return response.StatusCode; + } + + /// + /// Sends Fetch-Document IPP request to download the print document. + /// Returns the document payload bytes. + /// + public async Task FetchDocumentAsync( + string printerToken, string printerId, int jobId, string requestingUserUri) + { + var ippHost = new Uri(ippServiceBaseUrl).Host; + var factory = IppFactoryHelper.CreateIppRequestFactory( + ippHost, printerId, string.Empty, requestingUserUri); + + var ippRequest = await factory.CreateFetchDocumentRequestAsync( + requestId: 4, + outputDeviceUuid: printerId, + jobId: jobId, + documentNumber: 1); + + var response = await SendIppRequestAsync(printerToken, ippRequest); + + if (response.StatusCode != StatusCode.SuccessfulOk) + { + ConsoleHelper.WriteError($"Fetch-Document failed: {response.StatusCode}"); + return null; + } + + if (response.Data == null) + { + ConsoleHelper.WriteError("Fetch-Document response contained no document data."); + return null; + } + + using var ms = new MemoryStream(); + response.Data.Seek(0, SeekOrigin.Begin); + await response.Data.CopyToAsync(ms); + return ms.ToArray(); + } + + /// + /// Sends Update-Job-Status to mark the job as completed. + /// + public async Task UpdateJobStatusAsync( + string printerToken, string printerId, int jobId) + { + var ippHost = new Uri(ippServiceBaseUrl).Host; + var factory = IppFactoryHelper.CreateIppRequestFactory( + ippHost, printerId, string.Empty, string.Empty); + + var jobAttributeGroup = new IppAttributeGroup(Tag.JobAttributes); + jobAttributeGroup.AddAttribute( + new IppAttribute(JobAttributes.OutputDeviceJobState, + IppValue.CreateEnumValue((int)JobState.Completed))); + jobAttributeGroup.AddAttribute( + new IppAttribute(JobAttributes.JobId, + IppValue.CreateIntegerValue(jobId))); + + var ippRequest = await factory.CreateUpdateJobStatusRequestAsync( + requestId: 5, + outputDeviceUuid: printerId, + optionalJobAttributes: jobAttributeGroup); + + var response = await SendIppRequestAsync(printerToken, ippRequest); + return response.StatusCode; + } + + /// + /// Saves document bytes to a local file and opens it with the default viewer. + /// Returns the saved file path for cleanup. + /// + public static string SaveAndOpenDocument(byte[] documentData, string fileName = "PrintedDocument.pdf") + { + var outputPath = Path.Combine(Environment.CurrentDirectory, fileName); + File.WriteAllBytes(outputPath, documentData); + ConsoleHelper.WriteInfo($"Document saved to: {outputPath}"); + + try + { + Process.Start(new ProcessStartInfo + { + FileName = outputPath, + UseShellExecute = true + }); + ConsoleHelper.WriteInfo("Opening document with default viewer..."); + } + catch (Exception ex) + { + ConsoleHelper.WriteWarning($"Could not open document automatically: {ex.Message}"); + ConsoleHelper.WriteInfo($"Please open manually: {outputPath}"); + } + + return outputPath; + } + + /// + /// Serializes and sends an IPP request over HTTP, returns the parsed IPP response. + /// + private async Task SendIppRequestAsync(string accessToken, IppRequest ippRequest) + { + var printerEndpoint = $"{ippServiceBaseUrl}{ippServicePrinterPath}"; + + var serializedData = ippRequest.Serialize(); + serializedData.Seek(0, SeekOrigin.Begin); + var dataBuffer = new byte[serializedData.Length]; + await serializedData.ReadAsync(dataBuffer, 0, (int)serializedData.Length); + + using var request = new HttpRequestMessage(HttpMethod.Post, printerEndpoint); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + request.Headers.Add("User-Agent", "BadgeReleaseDemo/1.0"); + request.Content = new ByteArrayContent(dataBuffer); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/ipp"); + + var response = await httpClient.SendAsync(request); + + if (!response.IsSuccessStatusCode) + { + var errorBody = await response.Content.ReadAsStringAsync(); + ConsoleHelper.WriteError($"IPP HTTP {(int)response.StatusCode}: {errorBody}"); + + if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + throw new UnauthorizedAccessException($"Printer token expired or invalid: {errorBody}"); + } + + throw new HttpRequestException( + $"IPP request failed: {(int)response.StatusCode} {response.StatusCode} - {errorBody}"); + } + + // Copy to MemoryStream so it's seekable (required for IppResponse.CreateAsync to populate Data) + var networkStream = await response.Content.ReadAsStreamAsync(); + var stream = new MemoryStream(); + await networkStream.CopyToAsync(stream); + stream.Seek(0, SeekOrigin.Begin); + var cts = new CancellationTokenSource(); + var ippResponse = await IppResponse.CreateAsync(stream, true, cts.Token); + + if (ippResponse.StatusCode != StatusCode.SuccessfulOk) + { + ConsoleHelper.WriteWarning($"IPP response status: {ippResponse.StatusCode}"); + LogResponseHeaders(response); + } + + return ippResponse; + } + + private static void LogResponseHeaders(HttpResponseMessage response) + { + foreach (var header in response.Headers) + { + ConsoleHelper.WriteKeyValue(header.Key, string.Join(", ", header.Value)); + } + } +} diff --git a/BadgeReleaseDemo/Program.cs b/BadgeReleaseDemo/Program.cs new file mode 100644 index 0000000..9d46d15 --- /dev/null +++ b/BadgeReleaseDemo/Program.cs @@ -0,0 +1,386 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.Text.Json; +using BadgeReleaseDemo.Auth; +using BadgeReleaseDemo.GraphApi; +using BadgeReleaseDemo.Helpers; +using BadgeReleaseDemo.IppOperations; + +namespace BadgeReleaseDemo; + +/// +/// Badge Release Demo — demonstrates the Universal Print Badge Release API lifecycle. +/// +/// Flow: +/// 1. Sign in as Printer Admin +/// 2. Register a virtual printer +/// 3. Share the printer +/// 4. Create a badge collection and add a badge +/// 5. Submit a PDF print job +/// 6. Simulate badge scan → resolve badge → IPP fetch → open document → complete job +/// +public class Program +{ + public static async Task Main(string[] args) + { + Console.OutputEncoding = System.Text.Encoding.UTF8; + + ConsoleHelper.WriteHeader("🏷️ Universal Print — Badge Release Demo"); + + // Load configuration + var config = LoadConfiguration(); + var appId = config.GetProperty("AppId").GetString()!; + var tenantId = config.TryGetProperty("Tenant", out var tid) ? tid.GetString() ?? string.Empty : string.Empty; + var graphBaseUrl = config.GetProperty("GraphBaseUrl").GetString()!; + var graphPrintBaseUrl = config.GetProperty("GraphPrintBaseUrl").GetString()!; + var registrationBaseUrl = config.GetProperty("RegistrationBaseUrl").GetString()!; + var ippServiceBaseUrl = config.GetProperty("IppServiceBaseUrl").GetString()!; + var ippServicePrinterPath = config.GetProperty("IppServicePrinterPath").GetString()!; + var badgesApiPath = config.GetProperty("BadgesApiPath").GetString()!; + + if (appId == "YOUR_APP_ID_HERE" || tenantId == "YOUR_TENANT_HERE") + { + ConsoleHelper.WriteError("Please set your App ID and Tenant in appsettings.json before running this demo."); + return; + } + + // Initialize services + var auth = new AuthHelper(appId, tenantId, graphBaseUrl); + var printerReg = new PrinterRegistration(registrationBaseUrl); + var printerShare = new PrinterSharing(graphBaseUrl); + var badgeMgmt = new BadgeManagement(graphPrintBaseUrl); + var jobSubmission = new PrintJobSubmission(graphBaseUrl); + var ippClient = new PrinterIppClient(ippServiceBaseUrl, ippServicePrinterPath, badgesApiPath); + + + string printerId= string.Empty; + string shareId = string.Empty; + string createdBadgeId = string.Empty; + string? savedDocumentPath = null; + + try + { + // ═══════════════════════════════════════════════════════════ + // Step 1: Sign in as Printer Admin + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🔑", "Signing in as Printer Administrator..."); + var upn = await auth.SignInUserAsync(); + ConsoleHelper.WriteSuccess($"Signed in as: {upn}"); + + // ═══════════════════════════════════════════════════════════ + // Step 2: Register a virtual printer + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🖨️", "Registering a virtual printer..."); + var keyPair = CryptoHelper.GenerateKeyPair(); + var csr = CryptoHelper.GenerateCsr(keyPair); + var transportKey = CryptoHelper.GetTransportKey(keyPair); + + var token = await auth.GetUserTokenAsync(); + var printerName = $"BadgeReleaseDemo-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; + var regResult = await printerReg.RegisterPrinterAsync( + token, printerName, csr, transportKey); + + printerId = regResult.PrinterId; + ConsoleHelper.WriteSuccess($"Printer registered: {printerName}"); + ConsoleHelper.WriteKeyValue("Printer ID", printerId); + + // Store printer credentials in memory + auth.SetPrinterCredentials(regResult, keyPair); + + // ═══════════════════════════════════════════════════════════ + // Step 3: Share the printer + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🖨️", "Creating printer share..."); + var graphToken = await auth.GetGraphTokenAsync(); + shareId = await printerShare.CreateShareAsync(graphToken, printerId, printerName); + ConsoleHelper.WriteSuccess("Printer shared with all users."); + ConsoleHelper.WriteKeyValue("Share ID", shareId); + + // ═══════════════════════════════════════════════════════════ + // Step 4: Create badge collection (if needed) + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🏷️", "Creating badge collection..."); + token = await auth.GetUserTokenAsync(); + var collectionId = await badgeMgmt.CreateBadgeCollectionAsync(token); + ConsoleHelper.WriteSuccess($"Badge collection ready (ID: {collectionId})."); + + // ═══════════════════════════════════════════════════════════ + // Step 5: Prompt for badge ID and create badge + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🏷️", "Badge registration"); + ConsoleHelper.WriteInfo($"The signed-in user is: {auth.UserUpn}"); + var badgeId = ConsoleHelper.Prompt("Enter a badge ID to associate with this user"); + + if (string.IsNullOrWhiteSpace(badgeId)) + { + ConsoleHelper.WriteError("Badge ID cannot be empty."); + return; + } + + // ═══════════════════════════════════════════════════════════ + // Step 6: Add badge + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🏷️", $"Adding badge '{badgeId}' → {auth.UserUpn}"); + token = await auth.GetUserTokenAsync(); + await badgeMgmt.AddBadgeAsync(token, badgeId, auth.UserUpn); + createdBadgeId = badgeId; + ConsoleHelper.WriteSuccess($"Badge '{badgeId}' mapped to {auth.UserUpn}."); + + // ═══════════════════════════════════════════════════════════ + // Step 7: Enable badge release on the printer via Graph + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🖨️", "Configuring printer for badge release..."); + graphToken = await auth.GetGraphTokenAsync(); + await printerShare.EnableBadgeReleaseAsync(graphToken, printerId); + ConsoleHelper.WriteSuccess("Badge release enabled on printer."); + + // ═══════════════════════════════════════════════════════════ + // Step 8: Submit a PDF print job + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("📄", "Submitting print job..."); + + var pdfPath = ConsoleHelper.Prompt("Enter the path to a PDF file to print"); + if (string.IsNullOrWhiteSpace(pdfPath)) + { + ConsoleHelper.WriteError("PDF path cannot be empty."); + return; + } + + pdfPath = pdfPath.Trim('"'); // Remove quotes if user dragged file into console + if (!File.Exists(pdfPath)) + { + ConsoleHelper.WriteError($"File not found: {pdfPath}"); + return; + } + + graphToken = await auth.GetGraphTokenAsync(); + var (jobId, documentId) = await jobSubmission.CreateJobAsync( + graphToken, shareId, "Badge Release Demo Job"); + ConsoleHelper.WriteKeyValue("Job ID", jobId); + ConsoleHelper.WriteKeyValue("Document ID", documentId); + + // Upload the PDF + ConsoleHelper.WriteProgress("Uploading document..."); + var pdfData = await File.ReadAllBytesAsync(pdfPath); + var uploadUrl = await jobSubmission.CreateUploadSessionAsync(graphToken, shareId, jobId, documentId, pdfData.Length); + await jobSubmission.UploadDocumentAsync(graphToken, uploadUrl, pdfData); + + // Start the job + await jobSubmission.StartJobAsync(graphToken, shareId, jobId); + ConsoleHelper.WriteSuccess("Print job submitted and started."); + + // ═══════════════════════════════════════════════════════════ + // Step 9: Acquire printer device token + simulate badge scan + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🏷️", "Simulating badge scan at the printer..."); + ConsoleHelper.WriteInfo("Imagine you are walking up to the printer and scanning your badge."); + ConsoleHelper.WriteProgress("Acquiring printer device token..."); + var printerToken = await auth.GetPrinterTokenAsync(); + ConsoleHelper.WriteSuccess("Printer authenticated."); + + // Badge scan retry loop + string? resolvedUserUri = null; + int resolvedJobId = 0; + + while (true) + { + var scannedBadgeId = ConsoleHelper.Prompt("Scan badge (enter badge ID)"); + if (string.IsNullOrWhiteSpace(scannedBadgeId)) + { + ConsoleHelper.WriteError("Badge ID cannot be empty. Try again."); + continue; + } + + // ═══════════════════════════════════════════════════════ + // Step 9: Resolve badge via IPPService BadgesController + // ═══════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🔍", $"Resolving badge '{scannedBadgeId}'..."); + try + { + var badgeResult = await ippClient.ResolveBadgeAsync(printerToken, scannedBadgeId); + + if (badgeResult == null) + { + ConsoleHelper.WriteError($"Badge '{scannedBadgeId}' not found. Try again."); + continue; + } + + resolvedUserUri = badgeResult.Value.UserUri; + ConsoleHelper.WriteSuccess($"Badge resolved!"); + ConsoleHelper.WriteKeyValue("Badge ID", badgeResult.Value.BadgeId); + ConsoleHelper.WriteKeyValue("User URI", resolvedUserUri); + ConsoleHelper.WriteKeyValue("User ID", badgeResult.Value.UserId); + break; + } + catch (Exception ex) + { + ConsoleHelper.WriteError($"Badge resolution failed: {ex.Message}"); + ConsoleHelper.WriteInfo("Try scanning again."); + continue; + } + } + + // ═══════════════════════════════════════════════════════════ + // Step 10: Get-Jobs as printer with requesting-user-uri + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🖨️", "Printer: Getting fetchable jobs..."); + var jobs = await ippClient.GetJobsAsync(printerToken, printerId, resolvedUserUri!); + + if (jobs.Count == 0) + { + ConsoleHelper.WriteWarning("No fetchable jobs found for this user."); + ConsoleHelper.WriteInfo("The job may not be ready yet. In production, the printer would poll."); + return; + } + + resolvedJobId = jobs[0].JobId; + ConsoleHelper.WriteSuccess($"Found {jobs.Count} fetchable job(s)."); + ConsoleHelper.WriteKeyValue("Fetching Job ID", resolvedJobId.ToString()); + + // ═══════════════════════════════════════════════════════════ + // Step 11: Fetch-Job (get job metadata) + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🖨️", "Printer: Fetching job metadata..."); + var fetchJobResponse = await ippClient.FetchJobAsync( + printerToken, printerId, resolvedJobId, resolvedUserUri!); + + if (fetchJobResponse.StatusCode != BadgeReleaseDemo.IppLibrary.StatusCode.SuccessfulOk) + { + ConsoleHelper.WriteError($"Fetch-Job failed: {fetchJobResponse.StatusCode}"); + return; + } + + ConsoleHelper.WriteSuccess("Job metadata received."); + + // ═══════════════════════════════════════════════════════════ + // Step 12: Acknowledge-Job + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("🖨️", "Printer: Acknowledging job..."); + var ackStatus = await ippClient.AcknowledgeJobAsync( + printerToken, printerId, resolvedJobId, resolvedUserUri!); + + if (ackStatus != BadgeReleaseDemo.IppLibrary.StatusCode.SuccessfulOk) + { + ConsoleHelper.WriteError($"Acknowledge-Job failed: {ackStatus}"); + return; + } + + ConsoleHelper.WriteSuccess("Job acknowledged."); + + // ═══════════════════════════════════════════════════════════ + // Step 13: Fetch-Document (download PDF) + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("📄", "Printer: Downloading document..."); + var documentData = await ippClient.FetchDocumentAsync( + printerToken, printerId, resolvedJobId, resolvedUserUri!); + + if (documentData == null || documentData.Length == 0) + { + ConsoleHelper.WriteError("Failed to download document."); + return; + } + + ConsoleHelper.WriteSuccess($"Document downloaded ({documentData.Length} bytes)."); + + // Save and open the document + savedDocumentPath = PrinterIppClient.SaveAndOpenDocument(documentData); + + // ═══════════════════════════════════════════════════════════ + // Step 14: Update-Job-Status → Completed + // ═══════════════════════════════════════════════════════════ + ConsoleHelper.WriteStep("✅", "Printer: Marking job as completed..."); + var completeStatus = await ippClient.UpdateJobStatusAsync( + printerToken, printerId, resolvedJobId); + + if (completeStatus != BadgeReleaseDemo.IppLibrary.StatusCode.SuccessfulOk) + { + ConsoleHelper.WriteError($"Update-Job-Status failed: {completeStatus}"); + return; + } + + ConsoleHelper.WriteSuccess("Job marked as completed! 🎉"); + + ConsoleHelper.WriteHeader("🎉 Demo Complete!"); + ConsoleHelper.WriteInfo("The Badge Release flow completed successfully."); + ConsoleHelper.WriteInfo("Press any key to exit."); + Console.ReadKey(); + } + catch (Exception ex) + { + ConsoleHelper.WriteError($"Demo failed: {ex.Message}"); + ConsoleHelper.WriteInfo(ex.StackTrace ?? string.Empty); + } + finally + { + // Auto-cleanup: delete badge, share, printer, and downloaded document + if (!string.IsNullOrEmpty(shareId) || !string.IsNullOrEmpty(printerId) + || !string.IsNullOrEmpty(createdBadgeId) || savedDocumentPath != null) + { + ConsoleHelper.WriteHeader("🧹 Cleaning up demo resources..."); + + try + { + if (savedDocumentPath != null && File.Exists(savedDocumentPath)) + { + try + { + ConsoleHelper.WriteProgress("Deleting downloaded document..."); + File.Delete(savedDocumentPath); + ConsoleHelper.WriteSuccess("Document deleted."); + } + catch (IOException) + { + ConsoleHelper.WriteWarning($"Could not delete document (may be open in another app): {savedDocumentPath}"); + } + } + + if (!string.IsNullOrEmpty(createdBadgeId)) + { + ConsoleHelper.WriteProgress($"Deleting badge '{createdBadgeId}'..."); + var printToken = await auth.GetUserTokenAsync(); + await badgeMgmt.DeleteBadgeAsync(printToken, createdBadgeId); + ConsoleHelper.WriteSuccess("Badge deleted."); + } + + var graphToken = await auth.GetGraphTokenAsync(); + + if (!string.IsNullOrEmpty(shareId)) + { + ConsoleHelper.WriteProgress($"Deleting share {shareId}..."); + await printerShare.DeleteShareAsync(graphToken, shareId); + ConsoleHelper.WriteSuccess("Share deleted."); + } + + if (!string.IsNullOrEmpty(printerId)) + { + ConsoleHelper.WriteProgress($"Deleting printer {printerId}..."); + await printerShare.DeletePrinterAsync(graphToken, printerId); + ConsoleHelper.WriteSuccess("Printer deleted."); + } + } + catch (Exception cleanupEx) + { + ConsoleHelper.WriteWarning($"Cleanup failed: {cleanupEx.Message}"); + ConsoleHelper.WriteInfo("You may need to clean up manually in the Azure portal."); + ConsoleHelper.WriteKeyValue("Printer ID", printerId); + ConsoleHelper.WriteKeyValue("Share ID", shareId); + } + } + } + } + + private static JsonElement LoadConfiguration() + { + var configPath = Path.Combine(AppContext.BaseDirectory, "appsettings.json"); + + if (!File.Exists(configPath)) + { + throw new FileNotFoundException("appsettings.json not found. Make sure it's in the output directory.", configPath); + } + + var json = File.ReadAllText(configPath); + return JsonSerializer.Deserialize(json); + } +} diff --git a/BadgeReleaseDemo/README.md b/BadgeReleaseDemo/README.md new file mode 100644 index 0000000..b6db777 --- /dev/null +++ b/BadgeReleaseDemo/README.md @@ -0,0 +1,219 @@ +# Badge Release Demo + +An interactive console application that demonstrates the full [Universal Print](https://learn.microsoft.com/universal-print/) badge release lifecycle — from printer registration and badge setup to badge-swipe-triggered job release. + +## What Is Badge Release? + +Secure Release lets users send print jobs to a cloud queue and release them only after authenticating at the printer. This prevents uncollected printouts and ensures only the intended recipient picks up their documents. Badge Release is a form of Secure Release that is typically done by tapping an NFC badge or RFID card. + +Typically, Badge Release requires third-party storage of the mappings from users' badge IDs to their identities. The printer looks up the badge ID in this third-party storage, then uses the user's identity to fetch their jobs. With Universal Print's Badge Management APIs, badge-to-user mappings are managed and stored within the Universal Print service. The printer calls Universal Print to resolve a badge ID to a user identity, then fetches that user's queued jobs. + +> **⚠️ Private Preview:** The Badge Release feature is currently in **Private Preview** and is only enabled for specific tenants. If you are an OEM interested in joining the Private Preview, please reach out to the Universal Print team or [Microsoft Support](https://support.microsoft.com). + +## What This Demo Does + +The app walks through the complete lifecycle interactively: + +| Step | What happens | APIs used | +|------|-------------|-----------| +| 1. **Sign in** | Authenticate as a Printer Administrator | MSAL interactive auth | +| 2. **Register printer** | Create a virtual printer with an in-memory certificate | `POST register.print.microsoft.com/api/v1.0/register` | +| 3. **Share printer** | Make the printer available to all users | `POST graph.microsoft.com/v1.0/print/shares` | +| 4. **Create badge collection** | Provision a badge collection for the tenant (idempotent) | `POST graph.print.microsoft.com/v1.0/print/badgeCollections` | +| 5. **Add badge** | Map a user-provided badge ID to the signed-in user | `POST graph.print.microsoft.com/v1.0/print/badgeCollections/{id}/badges` | +| 6. **Enable badge release** | Configure the printer to require badge authentication | `PATCH graph.microsoft.com/v1.0/print/printers/{id}` | +| 7. **Submit print job** | Upload a PDF and start a print job on the shared printer | Graph Print Job APIs | +| 8. **Acquire printer token** | Obtain a device token for the printer via JWT-bearer flow | `POST {deviceTokenUrl}` | +| 9. **Resolve badge** | Simulate a badge tap — resolve the badge ID to a user via Universal Print | `GET print.print.microsoft.com/api/v1.0/badges/{badgeId}` | +| 10. **Get-Jobs** | Find fetchable jobs for the resolved user (IPP) | IPP Get-Jobs | +| 11. **Fetch-Job** | Retrieve job metadata (IPP) | IPP Fetch-Job | +| 12. **Acknowledge-Job** | Confirm receipt of the job (IPP) | IPP Acknowledge-Job | +| 13. **Fetch-Document** | Download the print document (IPP) | IPP Fetch-Document | +| 14. **Complete job** | Mark the job as completed (IPP) | IPP Update-Job-Status | +| 15. **Clean up** | Delete badge, share, printer, and local files | Graph + Badge APIs | + +## Prerequisites + +- [.NET 8.0 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) or later +- An Entra ID (Azure AD) app registration — see [App Registration](#app-registration) below +- A user account with the **Printer Administrator** directory role +- A PDF file to print during the demo + +## App Registration + +Register an application in the [Azure portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) (Entra ID → App registrations → New registration). + +### Platform Configuration + +Add a **Mobile and desktop applications** platform with redirect URI: + +``` +http://localhost +``` + +### Delegated Permissions (Microsoft Graph) + +These permissions are consented by the user at sign-in: + +| Permission | Used For | +|---|---| +| `PrinterShare.ReadWrite.All` | Creating and deleting printer shares | +| `Printer.FullControl.All` | Enabling badge release on the printer, deleting the printer during cleanup | +| `PrintJob.ReadWrite.All` | Submitting print jobs and uploading documents | + +### Delegated Permissions (Universal Print Service) + +| Permission | Used For | +|---|---| +| `Printers.Create` | Registering a virtual printer via the registration service | +| `PrintBadges.ReadWrite` | Creating, updating, and deleting badges and badge collections | + +### Application Permissions (Universal Print Service) + +These permissions are granted to the app itself (not delegated) and appear as `roles` in the printer's device token. **Admin consent is required** — a Global Administrator or Privileged Role Administrator must grant these. + +| Permission | Used For | +|---|---| +| `Printers.Read` | IPP Get-Jobs (required alongside PrintJob scopes) | +| `PrintJob.Read` | Reading fetchable jobs from the printer | +| `PrintJob.ReadWriteBasic` | Fetch-Job, Acknowledge-Job, Fetch-Document | +| `PrintBadges.Read` | Resolving badge IDs to users via the Badges API | + +> **Note:** `PrintBadges.Read` is specifically required for the badge resolution step. Without it, the printer will receive a `403 Forbidden` when calling the Badge API. See [Badge API documentation](https://learn.microsoft.com/universal-print/fundamentals/universal-print-badge-release) for details. + +## Configuration + +Edit `appsettings.json`: + +```json +{ + "AppId": "YOUR_APP_ID_HERE", + "Tenant": "YOUR_TENANT_HERE" +} +``` + +| Setting | Description | Example | +|---------|-------------|---------| +| `AppId` | Your Entra ID app registration client ID (GUID) | `a1b2c3d4-e5f6-7890-abcd-ef1234567890` | +| `Tenant` | Your tenant domain or GUID | `contoso.onmicrosoft.com` or a tenant GUID | + +The remaining settings point to commercial production Universal Print endpoints. For government cloud environments, see below. + +### Government Cloud + +For US Government cloud environments, update the service URLs in `appsettings.json`: + +| Setting | GCC | GCCH | DoD | +|---------|-----|------|-----| +| `GraphBaseUrl` | `https://graph.microsoft.com/v1.0` | `https://graph.microsoft.us/v1.0` | `https://dod-graph.microsoft.us/v1.0` | +| `GraphPrintBaseUrl` | `https://gcc-graph.print.azure.us/v1.0` | `https://graph.print.azure.us/v1.0` | `https://graph.print-dod.azure.us/v1.0` | +| `RegistrationBaseUrl` | `https://gcc-register.print.azure.us` | `https://register.print.azure.us` | `https://register.print-dod.azure.us` | +| `IppServiceBaseUrl` | `https://gcc-print.print.azure.us` | `https://print.print.azure.us` | `https://print.print-dod.azure.us` | + +## Build & Run + +1. Update `appsettings.json` with your Entra ID **TenantId** and **ClientId** (from the app registration above). +2. Build and run: + +```powershell +dotnet build +dotnet run +``` + +The app will walk you through each step interactively, prompting for a badge ID and PDF file path. At the end, all created cloud resources (printer, share, badge) are automatically cleaned up. + +## Project Structure + +``` +BadgeReleaseDemo/ +├── Program.cs # Main orchestration — runs the 15-step flow +├── appsettings.json # App ID, tenant, and service endpoints +│ +├── Auth/ +│ └── AuthHelper.cs # MSAL interactive auth + JWT-bearer device token flow +│ +├── GraphApi/ +│ ├── PrinterRegistration.cs # Printer registration via register.print.microsoft.com +│ ├── PrinterSharing.cs # Share CRUD + enable badge release via Graph +│ ├── BadgeManagement.cs # Badge collection + badge CRUD via graph.print.microsoft.com +│ └── PrintJobSubmission.cs # Job creation, document upload, job start via Graph +│ +├── IppOperations/ +│ └── PrinterIppClient.cs # IPP INFRA operations + Badge REST API call +│ +├── IppLibrary/ # IPP protocol serialization library (vendored) +│ ├── IPPRequest.cs # IPP request building and serialization +│ ├── IPPResponse.cs # IPP response parsing +│ ├── IPPAttribute.cs # IPP attribute types and encoding +│ └── ... (28 files) # Constants, datatypes, encoding helpers +│ +├── Helpers/ +│ ├── ConsoleHelper.cs # Colored console output helpers +│ └── CryptoHelper.cs # RSA keypair + CSR generation (BouncyCastle) +│ +└── Resources/ + └── SampleDocument.pdf # Default test PDF (or supply your own) +``` + +## Authentication Flows + +The demo uses three distinct token audiences: + +| Token | Audience | How Acquired | Used For | +|-------|----------|-------------|----------| +| **User print token** | `print.print.microsoft.com` | MSAL interactive (delegated) | Printer registration, badge management | +| **User graph token** | `graph.microsoft.com` | MSAL interactive (delegated) | Sharing, job submission, enable badge release | +| **Printer device token** | Dynamic (from registration) | JWT-bearer flow with printer certificate | Badge resolution, all IPP operations | + +### Printer Device Token Flow + +The printer authenticates using a certificate-based JWT-bearer flow: + +1. **`srv_challenge`** — POST to the device token URL to get a nonce +2. **Create JWT** — Sign a JWT with the printer's private key (includes nonce, resource, client_id) +3. **Exchange** — POST `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer` with the signed JWT +4. The returned access token contains the application permissions (`Printers.Read`, `PrintJob.Read`, etc.) as roles + +## Badge API Reference + +The Badge API is a REST endpoint on the Universal Print IPP Service: + +``` +GET https://print.print.microsoft.com/api/v1.0/badges/{badgeId} +Authorization: Bearer {printer-device-token} +``` + +**Success response (200 OK):** +```json +{ + "badgeId": "123", + "userURI": "mailto:john@contoso.com", + "userId": "a3f7b0aa-9f48-4f6f-a95f-0123456789ab" +} +``` + +The `userURI` (a `mailto:` URI) is then passed as the `requesting-user-uri` attribute in subsequent IPP operations (Get-Jobs, Fetch-Job, Fetch-Document) to retrieve that user's queued jobs. + +**Error responses:** + +| Status | Meaning | +|--------|---------| +| `400` | Missing or empty badge ID | +| `401` | Invalid or expired device token | +| `403` | Missing `PrintBadges.Read` permission, or the Badge Release feature is not enabled for your tenant | +| `404` | Badge ID not found | +| `500` | Server error | + +## Troubleshooting + +| Symptom | Likely Cause | Fix | +|---------|-------------|-----| +| `403 Forbidden` on badge resolution | Missing `PrintBadges.Read` app permission, or tenant not enrolled in the Badge Release preview | Grant the permission and admin-consent it in the Azure portal. If the feature is not enabled, see the note below. | +| `401 Unauthorized` on IPP operations | Printer device token expired | The demo acquires a fresh token; if it persists, re-run | +| No fetchable jobs found | Job not yet processed by the service | Wait a few seconds and retry; in production, printers poll | +| `ServerErrorInternalError` on Update-Job-Status | Job may already be in a terminal state | Check the correlation headers in the console output and investigate server-side | +| Cleanup fails to delete PDF | File locked by PDF viewer (e.g., Adobe) | Close the viewer, then delete manually | + +## License + +Copyright (c) Microsoft Corporation. All rights reserved. diff --git a/BadgeReleaseDemo/Resources/SampleDocument.pdf b/BadgeReleaseDemo/Resources/SampleDocument.pdf new file mode 100644 index 0000000000000000000000000000000000000000..12029052cc8e0c8ee794b047a049f688bffbebc9 GIT binary patch literal 688 zcmZXST~ER=6o&8nE8ZItz0j^>o01qVP>G2e3Ct_wMPUb+8LMfFQGdOs9VmuoOSaSJ zyyvqwxf!3S^GqltF!MKYbp=^|y%XfM_PQ}E$cbK&L(0Gu!=a$I3SjvE@6d7bzOEcz z`T>7s6|-$U)PZBTId%+TEHT=}k&jf>dSpKFk}zMW6c>Xu>b0IRhb#nuoRTwJyWj*v z=3~PO@k1exz;MW+526RRee?%vSAGrE&jR4QCA|@&QpmAFnxR|@l_a>}x2(j;NLMSu zlp4~Ga6=n⁢VO>K87G4ldWV13wy`oJVCA+fAr$X)OGj@Z8oPWSwp>u{GQOi&rdu z9@THl(C=MpyUvHg_9b+qie<~$cJAZkf>A+^IM43pj+zhh^+F#s;?vSnaA!sGdr;(g zKgSY(wgKWT+CN) Date: Wed, 8 Apr 2026 15:40:55 -0700 Subject: [PATCH 2/7] Address PR #14 review comments - Fix x5c JWT header to use array format per RFC 7515 - Fix AuthHelper XML doc: interactive auth, not device code flow - Add defensive check for empty documents array in PrintJobSubmission - Rename PascalCase local variable to camelCase in PrinterRegistration - Fix NotifyUserData attribute string (remove extra suffix) - Implement proper polling for badge collection provisioning (up to 10 min) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- BadgeReleaseDemo/Auth/AuthHelper.cs | 4 +- BadgeReleaseDemo/GraphApi/BadgeManagement.cs | 39 +++++++++++++++++-- .../GraphApi/PrintJobSubmission.cs | 7 +++- .../GraphApi/PrinterRegistration.cs | 6 +-- .../IppLibrary/NotifyAttributes.cs | 2 +- 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/BadgeReleaseDemo/Auth/AuthHelper.cs b/BadgeReleaseDemo/Auth/AuthHelper.cs index 0a0e722..61872db 100644 --- a/BadgeReleaseDemo/Auth/AuthHelper.cs +++ b/BadgeReleaseDemo/Auth/AuthHelper.cs @@ -47,7 +47,7 @@ public AuthHelper(string appId, string tenantId, string graphBaseUrl) } /// - /// Signs in the user interactively using device code flow. + /// Signs in the user using interactive browser-based authentication. /// public async Task SignInUserAsync() { @@ -224,7 +224,7 @@ private string CreateDeviceJwt(string nonce) var header = new JwtHeader(signingCredentials) { - { "x5c", certBase64 }, + { "x5c", new[] { certBase64 } }, }; // Use the app's client_id and the standard native client redirect URI diff --git a/BadgeReleaseDemo/GraphApi/BadgeManagement.cs b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs index 4594c6a..d0e00ea 100644 --- a/BadgeReleaseDemo/GraphApi/BadgeManagement.cs +++ b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs @@ -50,16 +50,49 @@ public async Task CreateBadgeCollectionAsync(string accessToken) ConsoleHelper.WriteInfo("Badge collection creation initiated."); - // Poll the operation if it's 202 Accepted + // Poll until the badge collection is provisioned (can take up to 10 minutes) if (response.StatusCode == HttpStatusCode.Accepted) { - ConsoleHelper.WriteProgress("Waiting for badge collection to be provisioned..."); - await Task.Delay(3000); + ConsoleHelper.WriteProgress("Waiting for badge collection to be provisioned (this can take up to 10 minutes)..."); + await WaitForBadgeCollectionProvisioningAsync(accessToken); } return SingleCollectionId; } + private async Task WaitForBadgeCollectionProvisioningAsync(string accessToken) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + const int maxAttempts = 60; + const int delayMilliseconds = 10000; + + for (var attempt = 1; attempt <= maxAttempts; attempt++) + { + var response = await httpClient.GetAsync($"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}"); + + if (response.IsSuccessStatusCode) + { + return; + } + + var responseBody = await response.Content.ReadAsStringAsync(); + + if (response.StatusCode != HttpStatusCode.NotFound) + { + throw new HttpRequestException( + $"Failed while waiting for badge collection provisioning: {response.StatusCode} - {responseBody}"); + } + + if (attempt < maxAttempts) + { + await Task.Delay(delayMilliseconds); + } + } + + throw new TimeoutException("Timed out waiting for badge collection provisioning to complete."); + } + /// /// Adds a badge to the collection with the given badge ID and user UPN. /// diff --git a/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs b/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs index 5326fd7..6669acc 100644 --- a/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs +++ b/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs @@ -65,8 +65,13 @@ public PrintJobSubmission(string graphBaseUrl) var jobDoc = JsonSerializer.Deserialize(responseBody); var jobId = jobDoc.GetProperty("id").GetString()!; - // Extract document ID from the first document in the response + // The Graph API auto-creates a document when creating a print job var documents = jobDoc.GetProperty("documents"); + if (documents.GetArrayLength() == 0) + { + throw new InvalidOperationException("Expected at least one document in the print job response."); + } + var documentId = documents[0].GetProperty("id").GetString()!; return (jobId, documentId); diff --git a/BadgeReleaseDemo/GraphApi/PrinterRegistration.cs b/BadgeReleaseDemo/GraphApi/PrinterRegistration.cs index 589a585..b96043b 100644 --- a/BadgeReleaseDemo/GraphApi/PrinterRegistration.cs +++ b/BadgeReleaseDemo/GraphApi/PrinterRegistration.cs @@ -117,15 +117,15 @@ public async Task RegisterPrinterAsync( printerRedirectUri = redirectProp.GetString() ?? string.Empty; } - var PrintServiceResourceId = string.Empty; + var printServiceResourceId = string.Empty; if (printerInfo.TryGetProperty("mcp_svc_resource_id", out var resourceProp)) { - PrintServiceResourceId = resourceProp.GetString() ?? string.Empty; + printServiceResourceId = resourceProp.GetString() ?? string.Empty; } return new PrinterRegistrationResult( printerId, certPem, deviceTokenUrl, - printerClientId, printerRedirectUri, PrintServiceResourceId); + printerClientId, printerRedirectUri, printServiceResourceId); } else if (statusResp.StatusCode == HttpStatusCode.Accepted) { diff --git a/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs b/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs index 6459bc8..4fa16de 100644 --- a/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs +++ b/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs @@ -37,7 +37,7 @@ public static class NotifyAttributes public const string NotifySubscriptionIds = "notify-subscription-ids"; public const string NotifyText = "notify-text"; public const string NotifyTimeInterval = "notify-time-interval"; - public const string NotifyUserData = "notify-user-data Subscription"; + public const string NotifyUserData = "notify-user-data"; public const string NotifyWait = "notify-wait"; public const string NotifyIppGet = "ippget"; } From 8743e59e19882ee3cde9d6d41f2f120b500b80dd Mon Sep 17 00:00:00 2001 From: Aditya Keswani Date: Tue, 7 Jul 2026 17:01:51 -0700 Subject: [PATCH 3/7] Use actual badge collection IDs instead of hardcoded 0 --- BadgeReleaseDemo/GraphApi/BadgeManagement.cs | 84 ++++++++++++++------ BadgeReleaseDemo/Program.cs | 11 +-- 2 files changed, 66 insertions(+), 29 deletions(-) diff --git a/BadgeReleaseDemo/GraphApi/BadgeManagement.cs b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs index d0e00ea..981bc6b 100644 --- a/BadgeReleaseDemo/GraphApi/BadgeManagement.cs +++ b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs @@ -15,7 +15,6 @@ namespace BadgeReleaseDemo.GraphApi; /// public class BadgeManagement { - private const string SingleCollectionId = "0"; private readonly string graphBaseUrl; private readonly HttpClient httpClient; @@ -27,7 +26,7 @@ public BadgeManagement(string graphBaseUrl) /// /// Creates a badge collection. Handles 409 Conflict if it already exists. - /// Returns the collection ID (always "0"). + /// Returns the actual collection ID from the service. /// public async Task CreateBadgeCollectionAsync(string accessToken) { @@ -40,7 +39,7 @@ public async Task CreateBadgeCollectionAsync(string accessToken) if (response.StatusCode == HttpStatusCode.Conflict) { ConsoleHelper.WriteInfo("Badge collection already exists (this is OK)."); - return SingleCollectionId; + return await GetBadgeCollectionIdAsync(accessToken); } if (!response.IsSuccessStatusCode && response.StatusCode != HttpStatusCode.Accepted) @@ -54,13 +53,13 @@ public async Task CreateBadgeCollectionAsync(string accessToken) if (response.StatusCode == HttpStatusCode.Accepted) { ConsoleHelper.WriteProgress("Waiting for badge collection to be provisioned (this can take up to 10 minutes)..."); - await WaitForBadgeCollectionProvisioningAsync(accessToken); + return await WaitForBadgeCollectionProvisioningAsync(accessToken); } - return SingleCollectionId; + return await GetBadgeCollectionIdAsync(accessToken); } - private async Task WaitForBadgeCollectionProvisioningAsync(string accessToken) + private async Task WaitForBadgeCollectionProvisioningAsync(string accessToken) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -69,19 +68,11 @@ private async Task WaitForBadgeCollectionProvisioningAsync(string accessToken) for (var attempt = 1; attempt <= maxAttempts; attempt++) { - var response = await httpClient.GetAsync($"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}"); + var collectionId = await TryGetBadgeCollectionIdAsync(accessToken); - if (response.IsSuccessStatusCode) + if (!string.IsNullOrEmpty(collectionId)) { - return; - } - - var responseBody = await response.Content.ReadAsStringAsync(); - - if (response.StatusCode != HttpStatusCode.NotFound) - { - throw new HttpRequestException( - $"Failed while waiting for badge collection provisioning: {response.StatusCode} - {responseBody}"); + return collectionId; } if (attempt < maxAttempts) @@ -93,10 +84,55 @@ private async Task WaitForBadgeCollectionProvisioningAsync(string accessToken) throw new TimeoutException("Timed out waiting for badge collection provisioning to complete."); } + private async Task GetBadgeCollectionIdAsync(string accessToken) + { + var collectionId = await TryGetBadgeCollectionIdAsync(accessToken); + return collectionId ?? throw new InvalidOperationException("No badge collection ID was returned by the service."); + } + + private async Task TryGetBadgeCollectionIdAsync(string accessToken) + { + httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var response = await httpClient.GetAsync($"{graphBaseUrl}/print/badgeCollections"); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (response.StatusCode == HttpStatusCode.NotFound) + { + return null; + } + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException( + $"Failed to list badge collections: {response.StatusCode} - {responseBody}"); + } + + var listDoc = JsonSerializer.Deserialize(responseBody); + if (!listDoc.TryGetProperty("value", out var collections) || collections.ValueKind != JsonValueKind.Array) + { + throw new InvalidOperationException("Badge collections response did not include a valid value array."); + } + + foreach (var collection in collections.EnumerateArray()) + { + if (collection.TryGetProperty("id", out var idProperty)) + { + var collectionId = idProperty.GetString(); + if (!string.IsNullOrWhiteSpace(collectionId)) + { + return collectionId; + } + } + } + + return null; + } + /// /// Adds a badge to the collection with the given badge ID and user UPN. /// - public async Task AddBadgeAsync(string accessToken, string badgeId, string upn) + public async Task AddBadgeAsync(string accessToken, string collectionId, string badgeId, string upn) { httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); @@ -110,14 +146,14 @@ public async Task AddBadgeAsync(string accessToken, string badgeId, string upn) var content = new StringContent(json, Encoding.UTF8, "application/json"); var response = await httpClient.PostAsync( - $"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}/badges", + $"{graphBaseUrl}/print/badgeCollections/{collectionId}/badges", content); var responseBody = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.Conflict) { ConsoleHelper.WriteInfo($"Badge '{badgeId}' already exists. Updating..."); - await UpdateBadgeAsync(accessToken, badgeId, upn); + await UpdateBadgeAsync(accessToken, collectionId, badgeId, upn); return; } @@ -130,12 +166,12 @@ public async Task AddBadgeAsync(string accessToken, string badgeId, string upn) /// /// Updates an existing badge's UPN. /// - private async Task UpdateBadgeAsync(string accessToken, string badgeId, string upn) + private async Task UpdateBadgeAsync(string accessToken, string collectionId, string badgeId, string upn) { var requestBody = new { upn }; var json = JsonSerializer.Serialize(requestBody); var request = new HttpRequestMessage(HttpMethod.Patch, - $"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}/badges/{badgeId}") + $"{graphBaseUrl}/print/badgeCollections/{collectionId}/badges/{badgeId}") { Content = new StringContent(json, Encoding.UTF8, "application/json") }; @@ -153,10 +189,10 @@ private async Task UpdateBadgeAsync(string accessToken, string badgeId, string u /// /// Deletes a badge from the collection. /// - public async Task DeleteBadgeAsync(string accessToken, string badgeId) + public async Task DeleteBadgeAsync(string accessToken, string collectionId, string badgeId) { using var request = new HttpRequestMessage(HttpMethod.Delete, - $"{graphBaseUrl}/print/badgeCollections/{SingleCollectionId}/badges/{badgeId}"); + $"{graphBaseUrl}/print/badgeCollections/{collectionId}/badges/{badgeId}"); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); var response = await httpClient.SendAsync(request); diff --git a/BadgeReleaseDemo/Program.cs b/BadgeReleaseDemo/Program.cs index 9d46d15..eaba330 100644 --- a/BadgeReleaseDemo/Program.cs +++ b/BadgeReleaseDemo/Program.cs @@ -57,6 +57,7 @@ public static async Task Main(string[] args) string printerId= string.Empty; string shareId = string.Empty; + string badgeCollectionId = string.Empty; string createdBadgeId = string.Empty; string? savedDocumentPath = null; @@ -103,8 +104,8 @@ public static async Task Main(string[] args) // ═══════════════════════════════════════════════════════════ ConsoleHelper.WriteStep("🏷️", "Creating badge collection..."); token = await auth.GetUserTokenAsync(); - var collectionId = await badgeMgmt.CreateBadgeCollectionAsync(token); - ConsoleHelper.WriteSuccess($"Badge collection ready (ID: {collectionId})."); + badgeCollectionId = await badgeMgmt.CreateBadgeCollectionAsync(token); + ConsoleHelper.WriteSuccess($"Badge collection ready (ID: {badgeCollectionId})."); // ═══════════════════════════════════════════════════════════ // Step 5: Prompt for badge ID and create badge @@ -124,7 +125,7 @@ public static async Task Main(string[] args) // ═══════════════════════════════════════════════════════════ ConsoleHelper.WriteStep("🏷️", $"Adding badge '{badgeId}' → {auth.UserUpn}"); token = await auth.GetUserTokenAsync(); - await badgeMgmt.AddBadgeAsync(token, badgeId, auth.UserUpn); + await badgeMgmt.AddBadgeAsync(token, badgeCollectionId, badgeId, auth.UserUpn); createdBadgeId = badgeId; ConsoleHelper.WriteSuccess($"Badge '{badgeId}' mapped to {auth.UserUpn}."); @@ -336,11 +337,11 @@ public static async Task Main(string[] args) } } - if (!string.IsNullOrEmpty(createdBadgeId)) + if (!string.IsNullOrEmpty(createdBadgeId) && !string.IsNullOrEmpty(badgeCollectionId)) { ConsoleHelper.WriteProgress($"Deleting badge '{createdBadgeId}'..."); var printToken = await auth.GetUserTokenAsync(); - await badgeMgmt.DeleteBadgeAsync(printToken, createdBadgeId); + await badgeMgmt.DeleteBadgeAsync(printToken, badgeCollectionId, createdBadgeId); ConsoleHelper.WriteSuccess("Badge deleted."); } From 8019e81977a7516ac910f01c9618d5195e7fbc3e Mon Sep 17 00:00:00 2001 From: Adi Keswani Date: Thu, 9 Jul 2026 15:54:34 -0700 Subject: [PATCH 4/7] Replace IppLibrary with minimal IPP implementation Switch BadgeReleaseDemo to a minimal custom IPP implementation, including Fetch-Document request/response fixes, and update settings for print-next endpoints and app credentials. Add gitignore entries to avoid committing build/debug artifacts. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- BadgeReleaseDemo/.gitignore | 9 + BadgeReleaseDemo/IppOperations/MinimalIpp.cs | 556 ++++++++++++++++++ .../IppOperations/PrinterIppClient.cs | 171 +++--- BadgeReleaseDemo/Program.cs | 24 +- BadgeReleaseDemo/appsettings.json | 10 +- 5 files changed, 654 insertions(+), 116 deletions(-) create mode 100644 BadgeReleaseDemo/.gitignore create mode 100644 BadgeReleaseDemo/IppOperations/MinimalIpp.cs diff --git a/BadgeReleaseDemo/.gitignore b/BadgeReleaseDemo/.gitignore new file mode 100644 index 0000000..098b6a3 --- /dev/null +++ b/BadgeReleaseDemo/.gitignore @@ -0,0 +1,9 @@ +bin/ +obj/ + +# Debug/comparison artifacts +*.bin +*.txt +compare.csx +program-comparison-snippet.txt +IppOperations/IppRequestComparison.cs diff --git a/BadgeReleaseDemo/IppOperations/MinimalIpp.cs b/BadgeReleaseDemo/IppOperations/MinimalIpp.cs new file mode 100644 index 0000000..ccfe4a7 --- /dev/null +++ b/BadgeReleaseDemo/IppOperations/MinimalIpp.cs @@ -0,0 +1,556 @@ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// + +using System.Globalization; +using System.Text; + +namespace BadgeReleaseDemo.IppOperations; + +/// +/// Minimal, auditable IPP (Internet Printing Protocol) implementation for Universal Print. +/// Implements only the 5 operations needed: Get-Jobs, Fetch-Job, Acknowledge-Job, Fetch-Document, Update-Job-Status. +/// Based on RFC 8011 (IPP/1.1) with Microsoft-specific extensions for Badge Release. +/// +/// This is NOT a general-purpose IPP library. It is custom, minimal code with the narrowest possible +/// attack surface for Badge Release Demo operations. +/// +public static class MinimalIpp +{ + // IPP Protocol Constants (RFC 8011) + private const byte IPP_VERSION_MAJOR = 2; + private const byte IPP_VERSION_MINOR = 0; + + // Operation codes (RFC 8011 + Microsoft extensions) + private const ushort OP_GET_JOBS = 0x000a; + private const ushort OP_FETCH_JOB = 0x0043; + private const ushort OP_ACKNOWLEDGE_JOB = 0x0041; + private const ushort OP_FETCH_DOCUMENT = 0x0042; + private const ushort OP_UPDATE_JOB_STATUS = 0x0045; + + // Attribute group tags + private const byte TAG_OPERATION_ATTRIBUTES = 0x01; + private const byte TAG_PRINTER_ATTRIBUTES = 0x04; + private const byte TAG_JOB_ATTRIBUTES = 0x02; + private const byte TAG_END_OF_ATTRIBUTES = 0x03; + + // Attribute value tags + private const byte TAG_INTEGER = 0x21; + private const byte TAG_BOOLEAN = 0x22; + private const byte TAG_ENUM = 0x23; + private const byte TAG_STRING = 0x30; + private const byte TAG_KEYWORD = 0x44; + private const byte TAG_URI = 0x45; + private const byte TAG_NAME_WITHOUT_LANGUAGE = 0x42; + private const byte TAG_TEXT_WITHOUT_LANGUAGE = 0x41; + + // Microsoft custom tags (used in Universal Print IPP extensions) + private const byte TAG_MS_CHARSET = 0x47; // Custom: attributes-charset, output-device-uuid + private const byte TAG_MS_LANGUAGE = 0x48; // Custom: attributes-natural-language + + /// + /// Builds a Get-Jobs IPP request to query fetchable jobs for a user. + /// Operation code: 0x000a (Get-Jobs) + /// Attribute order must match IppLibrary for compatibility with Universal Print service. + /// + public static byte[] BuildGetJobsRequest( + ushort requestId, string printerUri, string jobType, string requestingUserUri, string outputDeviceUuid = "") + { + using var stream = new MemoryStream(); + + // IPP Header + WriteIppHeader(stream, requestId, OP_GET_JOBS); + + // Operation Attributes Group + stream.WriteByte(TAG_OPERATION_ATTRIBUTES); + + // Must start with charset and language (required by RFC 8011) + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "attributes-charset", "UTF-8"); + WriteAttributeWithTag(stream, TAG_MS_LANGUAGE, "attributes-natural-language", "en-us"); + + // Add requesting-user-uri (can be empty but attribute should be present) + if (!string.IsNullOrEmpty(requestingUserUri)) + { + WriteAttributeWithTag(stream, TAG_URI, "requesting-user-uri", requestingUserUri); + } + + // Add my-jobs attribute (boolean) AFTER requesting-user-uri when it's provided + if (!string.IsNullOrEmpty(requestingUserUri)) + { + WriteAttributeWithTag(stream, TAG_BOOLEAN, "my-jobs", "true"); + } + + // Add printer-uri + WriteAttributeWithTag(stream, TAG_URI, "printer-uri", printerUri); + + // Add output-device-uuid if provided + if (!string.IsNullOrEmpty(outputDeviceUuid)) + { + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "output-device-uuid", $"urn:uuid:{outputDeviceUuid}"); + } + + // Add which-jobs attribute (keyword) AFTER printer-uri/output-device-uuid + if (!string.IsNullOrEmpty(jobType)) + { + WriteAttributeWithTag(stream, TAG_KEYWORD, "which-jobs", jobType); + } + + // Requested attributes (always add) + WriteAttributeWithTag(stream, TAG_KEYWORD, "requested-attributes", "all"); + + // End of attributes + stream.WriteByte(TAG_END_OF_ATTRIBUTES); + + return stream.ToArray(); + } + + /// + /// Writes an attribute with a specific value tag. + /// + private static void WriteAttributeWithTag(MemoryStream stream, byte valueTag, string name, string value) + { + // Special handling for boolean values + if (valueTag == TAG_BOOLEAN) + { + WriteBooleanAttribute(stream, name, value == "true" || value == "1"); + return; + } + + stream.WriteByte(valueTag); + WriteString(stream, name); + WriteString(stream, value); + } + + private static void WriteBooleanAttribute(MemoryStream stream, string name, bool value) + { + stream.WriteByte(TAG_BOOLEAN); + WriteString(stream, name); + WriteUInt16BigEndian(stream, 1); // length is always 1 for boolean + stream.WriteByte(value ? (byte)0x01 : (byte)0x00); + } + /// + /// Builds a Fetch-Job IPP request to retrieve job metadata. + /// Operation code: 0x0029 (Fetch-Job) + /// + public static byte[] BuildFetchJobRequest( + ushort requestId, string printerUri, int jobId, string outputDeviceUuid = "", string requestingUserUri = "") + { + using var stream = new MemoryStream(); + + WriteIppHeader(stream, requestId, OP_FETCH_JOB); + + stream.WriteByte(TAG_OPERATION_ATTRIBUTES); + + // Mandatory attributes + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "attributes-charset", "UTF-8"); + WriteAttributeWithTag(stream, TAG_MS_LANGUAGE, "attributes-natural-language", "en-us"); + + // Add requesting-user-uri if provided + if (!string.IsNullOrEmpty(requestingUserUri)) + { + WriteAttributeWithTag(stream, TAG_URI, "requesting-user-uri", requestingUserUri); + } + + // Printer URI + WriteAttributeWithTag(stream, TAG_URI, "printer-uri", printerUri); + + // Add output-device-uuid if provided + if (!string.IsNullOrEmpty(outputDeviceUuid)) + { + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "output-device-uuid", $"urn:uuid:{outputDeviceUuid}"); + } + + // Job ID + WriteIntegerAttribute(stream, "job-id", jobId); + + stream.WriteByte(TAG_END_OF_ATTRIBUTES); + + return stream.ToArray(); + } + + /// + /// Builds an Acknowledge-Job IPP request to confirm job receipt. + /// Operation code: 0x0041 (AcknowledgeJob) + /// + public static byte[] BuildAcknowledgeJobRequest( + ushort requestId, string printerUri, int jobId, string statusMessage, string outputDeviceUuid = "", string requestingUserUri = "") + { + using var stream = new MemoryStream(); + + WriteIppHeader(stream, requestId, OP_ACKNOWLEDGE_JOB); + + stream.WriteByte(TAG_OPERATION_ATTRIBUTES); + + // Mandatory attributes + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "attributes-charset", "UTF-8"); + WriteAttributeWithTag(stream, TAG_MS_LANGUAGE, "attributes-natural-language", "en-us"); + + // Add requesting-user-uri if provided + if (!string.IsNullOrEmpty(requestingUserUri)) + { + WriteAttributeWithTag(stream, TAG_URI, "requesting-user-uri", requestingUserUri); + } + + // Printer URI + WriteAttributeWithTag(stream, TAG_URI, "printer-uri", printerUri); + + // Add output-device-uuid if provided + if (!string.IsNullOrEmpty(outputDeviceUuid)) + { + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "output-device-uuid", $"urn:uuid:{outputDeviceUuid}"); + } + + // Job ID + WriteIntegerAttribute(stream, "job-id", jobId); + + // Status message (if provided) + if (!string.IsNullOrEmpty(statusMessage)) + { + WriteStringAttribute(stream, "status-message", statusMessage); + } + + stream.WriteByte(TAG_END_OF_ATTRIBUTES); + + return stream.ToArray(); + } + + /// + /// Builds a Fetch-Document IPP request to download the print document. + /// Operation code: 0x0042 (Fetch-Document) + /// + public static byte[] BuildFetchDocumentRequest( + ushort requestId, string printerUri, int jobId, int documentNumber, string outputDeviceUuid = "", string requestingUserUri = "", string jobUri = "") + { + using var stream = new MemoryStream(); + + WriteIppHeader(stream, requestId, OP_FETCH_DOCUMENT); + + stream.WriteByte(TAG_OPERATION_ATTRIBUTES); + + // Mandatory attributes + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "attributes-charset", "UTF-8"); + WriteAttributeWithTag(stream, TAG_MS_LANGUAGE, "attributes-natural-language", "en-us"); + + // Requesting user URI (matches other working operations) + if (!string.IsNullOrEmpty(requestingUserUri)) + { + WriteAttributeWithTag(stream, TAG_URI, "requesting-user-uri", requestingUserUri); + } + + // Printer URI + WriteAttributeWithTag(stream, TAG_URI, "printer-uri", printerUri); + + // Output device UUID (matches other working operations) + if (!string.IsNullOrEmpty(outputDeviceUuid)) + { + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "output-device-uuid", $"urn:uuid:{outputDeviceUuid}"); + } + + // Job URI (some services require it explicitly for Fetch-Document) + if (!string.IsNullOrEmpty(jobUri)) + { + WriteAttributeWithTag(stream, TAG_URI, "job-uri", jobUri); + } + + // Job ID and document number + WriteIntegerAttribute(stream, "job-id", jobId); + WriteIntegerAttribute(stream, "document-number", documentNumber); + + stream.WriteByte(TAG_END_OF_ATTRIBUTES); + + return stream.ToArray(); + } + + /// + /// Builds an Update-Job-Status IPP request to mark a job as completed. + /// Operation code: 0x0045 (UpdateActiveJobs) — Microsoft extension + /// + public static byte[] BuildUpdateJobStatusRequest( + ushort requestId, string printerUri, int jobId, int jobState, string outputDeviceUuid = "", string requestingUserUri = "") + { + using var stream = new MemoryStream(); + + WriteIppHeader(stream, requestId, OP_UPDATE_JOB_STATUS); + + stream.WriteByte(TAG_OPERATION_ATTRIBUTES); + + // Mandatory attributes + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "attributes-charset", "UTF-8"); + WriteAttributeWithTag(stream, TAG_MS_LANGUAGE, "attributes-natural-language", "en-us"); + + // Requesting user URI (matches other working operations) + if (!string.IsNullOrEmpty(requestingUserUri)) + { + WriteAttributeWithTag(stream, TAG_URI, "requesting-user-uri", requestingUserUri); + } + + // Printer URI + WriteAttributeWithTag(stream, TAG_URI, "printer-uri", printerUri); + + // Output device UUID (matches other working operations) + if (!string.IsNullOrEmpty(outputDeviceUuid)) + { + WriteAttributeWithTag(stream, TAG_MS_CHARSET, "output-device-uuid", $"urn:uuid:{outputDeviceUuid}"); + } + + // Job ID and job state + WriteIntegerAttribute(stream, "job-id", jobId); + WriteIntegerAttribute(stream, "job-state", jobState); + + stream.WriteByte(TAG_END_OF_ATTRIBUTES); + + return stream.ToArray(); + } + + /// + /// Parses an IPP response and extracts status code and job attributes. + /// Returns (statusCode, jobAttributes) where jobAttributes is a collection of dictionaries per job. + /// + public static (ushort StatusCode, List> JobAttributes) ParseGetJobsResponse(byte[] responseData) + { + using var stream = new MemoryStream(responseData); + using var reader = new BinaryReader(stream); + + // IPP Header (4 bytes + 4 bytes + 4 bytes = 12 bytes minimum) + byte versionMajor = reader.ReadByte(); + byte versionMinor = reader.ReadByte(); + ushort statusCode = ReadUInt16BigEndian(reader); + uint requestId = ReadUInt32BigEndian(reader); + + var jobs = new List>(); + + // Parse attribute groups + while (stream.Position < stream.Length) + { + byte tag = reader.ReadByte(); + + if (tag == TAG_END_OF_ATTRIBUTES) + break; + + if (tag == TAG_JOB_ATTRIBUTES) + { + var jobAttrs = ParseAttributeGroup(reader); + jobs.Add(jobAttrs); + } + else + { + // Skip unknown attribute group + SkipAttributeGroup(reader); + } + } + + return (statusCode, jobs); + } + + /// + /// Parses an IPP response for Fetch-Job and extracts job attributes. + /// + public static (ushort StatusCode, Dictionary JobAttributes, MemoryStream? DocumentData) ParseFetchJobResponse(byte[] responseData) + { + using var stream = new MemoryStream(responseData); + using var reader = new BinaryReader(stream); + + byte versionMajor = reader.ReadByte(); + byte versionMinor = reader.ReadByte(); + ushort statusCode = ReadUInt16BigEndian(reader); + uint requestId = ReadUInt32BigEndian(reader); + + var jobAttrs = new Dictionary(); + + // Parse attribute groups + while (stream.Position < stream.Length) + { + byte tag = reader.ReadByte(); + + if (tag == TAG_END_OF_ATTRIBUTES) + break; + + if (tag == TAG_JOB_ATTRIBUTES) + { + jobAttrs = ParseAttributeGroup(reader); + } + else + { + SkipAttributeGroup(reader); + } + } + + // Remaining bytes are the document data (if any) + byte[] documentData = reader.ReadBytes((int)(stream.Length - stream.Position)); + var docStream = documentData.Length > 0 ? new MemoryStream(documentData) : null; + + return (statusCode, jobAttrs, docStream); + } + + /// + /// Parses an IPP response and extracts status code. + /// + public static ushort ParseStatusCodeResponse(byte[] responseData) + { + if (responseData.Length < 8) + return 0; + + return (ushort)((responseData[2] << 8) | responseData[3]); + } + + // ---- Private Helpers ---- + + private static void WriteIppHeader(MemoryStream stream, ushort requestId, ushort operationCode) + { + stream.WriteByte(IPP_VERSION_MAJOR); + stream.WriteByte(IPP_VERSION_MINOR); + WriteUInt16BigEndian(stream, operationCode); + WriteUInt32BigEndian(stream, requestId); + } + + private static void WriteKeywordAttribute(MemoryStream stream, string name, string value) + { + stream.WriteByte(TAG_KEYWORD); + WriteString(stream, name); + WriteString(stream, value); + } + + private static void WriteStringAttribute(MemoryStream stream, string name, string value) + { + stream.WriteByte(TAG_TEXT_WITHOUT_LANGUAGE); + WriteString(stream, name); + WriteString(stream, value); + } + + private static void WriteIntegerAttribute(MemoryStream stream, string name, int value) + { + stream.WriteByte(TAG_INTEGER); + WriteString(stream, name); + WriteUInt16BigEndian(stream, 4); // 4 bytes for integer value + WriteInt32BigEndian(stream, value); + } + + private static void WriteEnumAttribute(MemoryStream stream, string name, int value) + { + stream.WriteByte(TAG_ENUM); + WriteString(stream, name); + WriteUInt16BigEndian(stream, 4); // 4 bytes for enum value + WriteInt32BigEndian(stream, value); + } + + private static void WriteString(MemoryStream stream, string value) + { + byte[] bytes = Encoding.UTF8.GetBytes(value); + WriteUInt16BigEndian(stream, (ushort)bytes.Length); + stream.Write(bytes, 0, bytes.Length); + } + + private static Dictionary ParseAttributeGroup(BinaryReader reader) + { + var attrs = new Dictionary(); + + while (reader.BaseStream.Position < reader.BaseStream.Length) + { + byte tag = reader.ReadByte(); + + if (IsDelimiterTag(tag)) + { + reader.BaseStream.Seek(-1, SeekOrigin.Current); + break; + } + + string name = ReadString(reader); + object value = ReadValue(reader, tag); + + attrs[name] = value; + } + + return attrs; + } + + private static void SkipAttributeGroup(BinaryReader reader) + { + while (reader.BaseStream.Position < reader.BaseStream.Length) + { + byte tag = reader.ReadByte(); + + if (IsDelimiterTag(tag)) + { + reader.BaseStream.Seek(-1, SeekOrigin.Current); + break; + } + + string name = ReadString(reader); + SkipValue(reader); + } + } + + private static string ReadString(BinaryReader reader) + { + ushort length = ReadUInt16BigEndian(reader); + byte[] bytes = reader.ReadBytes(length); + return Encoding.UTF8.GetString(bytes); + } + + private static object ReadValue(BinaryReader reader, byte tag) + { + ushort length = ReadUInt16BigEndian(reader); + + return tag switch + { + TAG_INTEGER => ReadInt32BigEndian(reader), + TAG_BOOLEAN => reader.ReadByte() != 0, + TAG_ENUM => ReadInt32BigEndian(reader), + TAG_KEYWORD or TAG_NAME_WITHOUT_LANGUAGE or TAG_TEXT_WITHOUT_LANGUAGE or TAG_URI or TAG_STRING => + Encoding.UTF8.GetString(reader.ReadBytes(length)), + _ => reader.ReadBytes(length) + }; + } + + private static void SkipValue(BinaryReader reader) + { + ushort length = ReadUInt16BigEndian(reader); + reader.ReadBytes(length); + } + + private static bool IsDelimiterTag(byte tag) + { + // IPP delimiter tags are in the low range (0x01-0x0F): operation/job/printer/document groups and end tag. + return tag <= 0x0F; + } + + private static ushort ReadUInt16BigEndian(BinaryReader reader) + { + byte[] bytes = reader.ReadBytes(2); + return (ushort)((bytes[0] << 8) | bytes[1]); + } + + private static void WriteUInt16BigEndian(MemoryStream stream, ushort value) + { + stream.WriteByte((byte)((value >> 8) & 0xFF)); + stream.WriteByte((byte)(value & 0xFF)); + } + + private static uint ReadUInt32BigEndian(BinaryReader reader) + { + byte[] bytes = reader.ReadBytes(4); + return (uint)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]); + } + + private static void WriteUInt32BigEndian(MemoryStream stream, uint value) + { + stream.WriteByte((byte)((value >> 24) & 0xFF)); + stream.WriteByte((byte)((value >> 16) & 0xFF)); + stream.WriteByte((byte)((value >> 8) & 0xFF)); + stream.WriteByte((byte)(value & 0xFF)); + } + + private static int ReadInt32BigEndian(BinaryReader reader) + { + byte[] bytes = reader.ReadBytes(4); + return (int)((bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | bytes[3]); + } + + private static void WriteInt32BigEndian(MemoryStream stream, int value) + { + stream.WriteByte((byte)((value >> 24) & 0xFF)); + stream.WriteByte((byte)((value >> 16) & 0xFF)); + stream.WriteByte((byte)((value >> 8) & 0xFF)); + stream.WriteByte((byte)(value & 0xFF)); + } +} diff --git a/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs b/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs index f0d3b67..3104f81 100644 --- a/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs +++ b/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs @@ -5,18 +5,18 @@ using System.Diagnostics; using System.Net.Http.Headers; using BadgeReleaseDemo.Helpers; -using BadgeReleaseDemo.IppLibrary; -using BadgeReleaseDemo.IppLibrary.Common; namespace BadgeReleaseDemo.IppOperations; /// /// Performs IPP INFRA operations as a printer: Get-Jobs, Fetch-Job, /// Acknowledge-Job, Fetch-Document, and Update-Job-Status. -/// Uses the IppLibrary for IPP request building and serialization. +/// Uses the minimal custom IPP implementation for a focused, auditable approach. /// public class PrinterIppClient { + private const int JOB_STATE_COMPLETED = 9; + private readonly string ippServiceBaseUrl; private readonly string ippServicePrinterPath; private readonly string badgesApiPath; @@ -71,47 +71,40 @@ public PrinterIppClient(string ippServiceBaseUrl, string ippServicePrinterPath, string printerToken, string printerId, string requestingUserUri) { var ippHost = new Uri(ippServiceBaseUrl).Host; - var factory = IppFactoryHelper.CreateIppRequestFactory( - ippHost, printerId, string.Empty, requestingUserUri); - - var requestedAttributes = new List - { - new IppAttribute(RequestedAttributes.All) - }; + var printerUri = $"ipps://{ippHost}/printers/{printerId}"; - var ippRequest = await factory.CreateGetJobsRequestAsync( + var ippRequest = MinimalIpp.BuildGetJobsRequest( requestId: 1, + printerUri: printerUri, jobType: "fetchable", - requestingUserName: string.Empty, requestingUserUri: requestingUserUri, - printerUri: $"ipps://{ippHost}/printers/{printerId}", - outputDeviceUuid: printerId, - requestedAttributes: requestedAttributes); + outputDeviceUuid: printerId); - var ippResponse = await SendIppRequestAsync(printerToken, ippRequest); + var responseData = await SendIppRequestAsync(printerToken, ippRequest); + + var (statusCode, jobAttributes) = MinimalIpp.ParseGetJobsResponse(responseData); - if (ippResponse.StatusCode != StatusCode.SuccessfulOk) + if (statusCode != 0x0000) // 0x0000 = successful-ok { - ConsoleHelper.WriteWarning($"Get-Jobs returned status: {ippResponse.StatusCode}"); + ConsoleHelper.WriteWarning($"Get-Jobs returned status: {statusCode:X4}"); return new List<(int, string)>(); } var jobs = new List<(int JobId, string JobUri)>(); - var jobGroups = ippResponse.LookupAttributeGroup(Tag.JobAttributes); - foreach (var group in jobGroups) + foreach (var jobAttrs in jobAttributes) { int jobId = 0; string jobUri = string.Empty; - if (group.Attributes.TryGetValue(JobAttributes.JobId, out var jobIdAttr)) + if (jobAttrs.TryGetValue("job-id", out var jobIdObj) && jobIdObj is int id) { - jobId = jobIdAttr.FirstValue.GetNativeValue(); + jobId = id; } - if (group.Attributes.TryGetValue(JobAttributes.JobUri, out var jobUriAttr)) + if (jobAttrs.TryGetValue("job-uri", out var jobUriObj) && jobUriObj is string uri) { - jobUri = jobUriAttr.FirstValue.GetNativeValue() ?? string.Empty; + jobUri = uri; } if (jobId > 0) @@ -126,40 +119,42 @@ public PrinterIppClient(string ippServiceBaseUrl, string ippServicePrinterPath, /// /// Sends Fetch-Job IPP request to get job metadata. /// - public async Task FetchJobAsync( + public async Task<(ushort StatusCode, Dictionary JobAttributes, MemoryStream? DocumentData)> FetchJobAsync( string printerToken, string printerId, int jobId, string requestingUserUri) { var ippHost = new Uri(ippServiceBaseUrl).Host; - var factory = IppFactoryHelper.CreateIppRequestFactory( - ippHost, printerId, string.Empty, requestingUserUri); + var printerUri = $"ipps://{ippHost}/printers/{printerId}"; - var ippRequest = await factory.CreateFetchJobRequestAsync( + var ippRequest = MinimalIpp.BuildFetchJobRequest( requestId: 2, + printerUri: printerUri, + jobId: jobId, outputDeviceUuid: printerId, - jobId: jobId); + requestingUserUri: requestingUserUri); - return await SendIppRequestAsync(printerToken, ippRequest); + var responseData = await SendIppRequestAsync(printerToken, ippRequest); + return MinimalIpp.ParseFetchJobResponse(responseData); } /// /// Sends Acknowledge-Job IPP request to confirm receipt of the job. /// - public async Task AcknowledgeJobAsync( + public async Task AcknowledgeJobAsync( string printerToken, string printerId, int jobId, string requestingUserUri) { var ippHost = new Uri(ippServiceBaseUrl).Host; - var factory = IppFactoryHelper.CreateIppRequestFactory( - ippHost, printerId, string.Empty, requestingUserUri); + var printerUri = $"ipps://{ippHost}/printers/{printerId}"; - var ippRequest = await factory.CreateAcknowledgeJobRequestAsync( + var ippRequest = MinimalIpp.BuildAcknowledgeJobRequest( requestId: 3, - outputDeviceUuid: printerId, + printerUri: printerUri, jobId: jobId, - fetchStatusCode: StatusCode.Undefined, - fetchStatusMessage: "Badge release demo - job acknowledged"); + statusMessage: "Badge release demo - job acknowledged", + outputDeviceUuid: printerId, + requestingUserUri: requestingUserUri); - var response = await SendIppRequestAsync(printerToken, ippRequest); - return response.StatusCode; + var responseData = await SendIppRequestAsync(printerToken, ippRequest); + return MinimalIpp.ParseStatusCodeResponse(responseData); } /// @@ -167,63 +162,62 @@ public async Task AcknowledgeJobAsync( /// Returns the document payload bytes. /// public async Task FetchDocumentAsync( - string printerToken, string printerId, int jobId, string requestingUserUri) + string printerToken, string printerId, int jobId, string requestingUserUri, string jobUri = "") { var ippHost = new Uri(ippServiceBaseUrl).Host; - var factory = IppFactoryHelper.CreateIppRequestFactory( - ippHost, printerId, string.Empty, requestingUserUri); - - var ippRequest = await factory.CreateFetchDocumentRequestAsync( + var printerUri = $"ipps://{ippHost}/printers/{printerId}"; + var ippRequest = MinimalIpp.BuildFetchDocumentRequest( requestId: 4, - outputDeviceUuid: printerId, + printerUri: printerUri, jobId: jobId, - documentNumber: 1); + documentNumber: 1, + outputDeviceUuid: printerId, + requestingUserUri: requestingUserUri, + jobUri: jobUri); - var response = await SendIppRequestAsync(printerToken, ippRequest); + var responseData = await SendIppRequestAsync(printerToken, ippRequest); + var (statusCode, _, docStream) = MinimalIpp.ParseFetchJobResponse(responseData); - if (response.StatusCode != StatusCode.SuccessfulOk) + if (statusCode != 0x0000) { - ConsoleHelper.WriteError($"Fetch-Document failed: {response.StatusCode}"); + ConsoleHelper.WriteError($"Fetch-Document failed: {statusCode:X4}"); return null; } - if (response.Data == null) + if (docStream == null) { ConsoleHelper.WriteError("Fetch-Document response contained no document data."); return null; } - using var ms = new MemoryStream(); - response.Data.Seek(0, SeekOrigin.Begin); - await response.Data.CopyToAsync(ms); - return ms.ToArray(); + using (docStream) + { + docStream.Seek(0, SeekOrigin.Begin); + using var ms = new MemoryStream(); + await docStream.CopyToAsync(ms); + return ms.ToArray(); + } } /// /// Sends Update-Job-Status to mark the job as completed. /// - public async Task UpdateJobStatusAsync( - string printerToken, string printerId, int jobId) + public async Task UpdateJobStatusAsync( + string printerToken, string printerId, int jobId, string requestingUserUri) { var ippHost = new Uri(ippServiceBaseUrl).Host; - var factory = IppFactoryHelper.CreateIppRequestFactory( - ippHost, printerId, string.Empty, string.Empty); - - var jobAttributeGroup = new IppAttributeGroup(Tag.JobAttributes); - jobAttributeGroup.AddAttribute( - new IppAttribute(JobAttributes.OutputDeviceJobState, - IppValue.CreateEnumValue((int)JobState.Completed))); - jobAttributeGroup.AddAttribute( - new IppAttribute(JobAttributes.JobId, - IppValue.CreateIntegerValue(jobId))); - - var ippRequest = await factory.CreateUpdateJobStatusRequestAsync( + var printerUri = $"ipps://{ippHost}/printers/{printerId}"; + + var ippRequest = MinimalIpp.BuildUpdateJobStatusRequest( requestId: 5, + printerUri: printerUri, + jobId: jobId, + jobState: JOB_STATE_COMPLETED, outputDeviceUuid: printerId, - optionalJobAttributes: jobAttributeGroup); + requestingUserUri: requestingUserUri); - var response = await SendIppRequestAsync(printerToken, ippRequest); - return response.StatusCode; + var responseData = await SendIppRequestAsync(printerToken, ippRequest); + return MinimalIpp.ParseStatusCodeResponse(responseData); } /// @@ -255,21 +249,16 @@ public static string SaveAndOpenDocument(byte[] documentData, string fileName = } /// - /// Serializes and sends an IPP request over HTTP, returns the parsed IPP response. + /// Sends a minimal IPP request over HTTP, returns the raw response bytes. /// - private async Task SendIppRequestAsync(string accessToken, IppRequest ippRequest) + private async Task SendIppRequestAsync(string accessToken, byte[] ippRequest) { var printerEndpoint = $"{ippServiceBaseUrl}{ippServicePrinterPath}"; - var serializedData = ippRequest.Serialize(); - serializedData.Seek(0, SeekOrigin.Begin); - var dataBuffer = new byte[serializedData.Length]; - await serializedData.ReadAsync(dataBuffer, 0, (int)serializedData.Length); - using var request = new HttpRequestMessage(HttpMethod.Post, printerEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); request.Headers.Add("User-Agent", "BadgeReleaseDemo/1.0"); - request.Content = new ByteArrayContent(dataBuffer); + request.Content = new ByteArrayContent(ippRequest); request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/ipp"); var response = await httpClient.SendAsync(request); @@ -288,28 +277,6 @@ private async Task SendIppRequestAsync(string accessToken, IppReque $"IPP request failed: {(int)response.StatusCode} {response.StatusCode} - {errorBody}"); } - // Copy to MemoryStream so it's seekable (required for IppResponse.CreateAsync to populate Data) - var networkStream = await response.Content.ReadAsStreamAsync(); - var stream = new MemoryStream(); - await networkStream.CopyToAsync(stream); - stream.Seek(0, SeekOrigin.Begin); - var cts = new CancellationTokenSource(); - var ippResponse = await IppResponse.CreateAsync(stream, true, cts.Token); - - if (ippResponse.StatusCode != StatusCode.SuccessfulOk) - { - ConsoleHelper.WriteWarning($"IPP response status: {ippResponse.StatusCode}"); - LogResponseHeaders(response); - } - - return ippResponse; - } - - private static void LogResponseHeaders(HttpResponseMessage response) - { - foreach (var header in response.Headers) - { - ConsoleHelper.WriteKeyValue(header.Key, string.Join(", ", header.Value)); - } + return await response.Content.ReadAsByteArrayAsync(); } } diff --git a/BadgeReleaseDemo/Program.cs b/BadgeReleaseDemo/Program.cs index eaba330..6587ac6 100644 --- a/BadgeReleaseDemo/Program.cs +++ b/BadgeReleaseDemo/Program.cs @@ -184,6 +184,7 @@ public static async Task Main(string[] args) // Badge scan retry loop string? resolvedUserUri = null; int resolvedJobId = 0; + string resolvedJobUri = string.Empty; while (true) { @@ -237,19 +238,24 @@ public static async Task Main(string[] args) } resolvedJobId = jobs[0].JobId; + resolvedJobUri = jobs[0].JobUri; ConsoleHelper.WriteSuccess($"Found {jobs.Count} fetchable job(s)."); ConsoleHelper.WriteKeyValue("Fetching Job ID", resolvedJobId.ToString()); + if (!string.IsNullOrEmpty(resolvedJobUri)) + { + ConsoleHelper.WriteKeyValue("Fetching Job URI", resolvedJobUri); + } // ═══════════════════════════════════════════════════════════ // Step 11: Fetch-Job (get job metadata) // ═══════════════════════════════════════════════════════════ ConsoleHelper.WriteStep("🖨️", "Printer: Fetching job metadata..."); - var fetchJobResponse = await ippClient.FetchJobAsync( + var (fetchJobStatusCode, fetchJobAttrs, fetchJobDocStream) = await ippClient.FetchJobAsync( printerToken, printerId, resolvedJobId, resolvedUserUri!); - if (fetchJobResponse.StatusCode != BadgeReleaseDemo.IppLibrary.StatusCode.SuccessfulOk) + if (fetchJobStatusCode != 0x0000) // 0x0000 = successful-ok { - ConsoleHelper.WriteError($"Fetch-Job failed: {fetchJobResponse.StatusCode}"); + ConsoleHelper.WriteError($"Fetch-Job failed: {fetchJobStatusCode:X4}"); return; } @@ -262,9 +268,9 @@ public static async Task Main(string[] args) var ackStatus = await ippClient.AcknowledgeJobAsync( printerToken, printerId, resolvedJobId, resolvedUserUri!); - if (ackStatus != BadgeReleaseDemo.IppLibrary.StatusCode.SuccessfulOk) + if (ackStatus != 0x0000) // 0x0000 = successful-ok { - ConsoleHelper.WriteError($"Acknowledge-Job failed: {ackStatus}"); + ConsoleHelper.WriteError($"Acknowledge-Job failed: {ackStatus:X4}"); return; } @@ -275,7 +281,7 @@ public static async Task Main(string[] args) // ═══════════════════════════════════════════════════════════ ConsoleHelper.WriteStep("📄", "Printer: Downloading document..."); var documentData = await ippClient.FetchDocumentAsync( - printerToken, printerId, resolvedJobId, resolvedUserUri!); + printerToken, printerId, resolvedJobId, resolvedUserUri!, resolvedJobUri); if (documentData == null || documentData.Length == 0) { @@ -293,11 +299,11 @@ public static async Task Main(string[] args) // ═══════════════════════════════════════════════════════════ ConsoleHelper.WriteStep("✅", "Printer: Marking job as completed..."); var completeStatus = await ippClient.UpdateJobStatusAsync( - printerToken, printerId, resolvedJobId); + printerToken, printerId, resolvedJobId, resolvedUserUri!); - if (completeStatus != BadgeReleaseDemo.IppLibrary.StatusCode.SuccessfulOk) + if (completeStatus != 0x0000) // 0x0000 = successful-ok { - ConsoleHelper.WriteError($"Update-Job-Status failed: {completeStatus}"); + ConsoleHelper.WriteError($"Update-Job-Status failed: {completeStatus:X4}"); return; } diff --git a/BadgeReleaseDemo/appsettings.json b/BadgeReleaseDemo/appsettings.json index 82df14f..9229f41 100644 --- a/BadgeReleaseDemo/appsettings.json +++ b/BadgeReleaseDemo/appsettings.json @@ -1,10 +1,10 @@ { - "AppId": "YOUR_APP_ID_HERE", - "Tenant": "YOUR_TENANT_HERE", + "AppId": "eb6526b2-528e-49df-ab52-6bc5adaf0628", + "Tenant": "6923dc21-4f0f-42e5-800e-ef1e8ba0b218", "GraphBaseUrl": "https://graph.microsoft.com/v1.0", - "GraphPrintBaseUrl": "https://graph.print.microsoft.com/v1.0", - "RegistrationBaseUrl": "https://register.print.microsoft.com", - "IppServiceBaseUrl": "https://print.print.microsoft.com", + "GraphPrintBaseUrl": "https://graph.print-next.microsoft.com/v1.0", + "RegistrationBaseUrl": "https://register.print-next.microsoft.com", + "IppServiceBaseUrl": "https://print.print-next.microsoft.com", "IppServicePrinterPath": "/printers", "BadgesApiPath": "/api/v1.0/badges" } From c33d9fa083c2f714cccc9f80e75dbda1d83858e0 Mon Sep 17 00:00:00 2001 From: Adi Keswani Date: Thu, 9 Jul 2026 15:56:39 -0700 Subject: [PATCH 5/7] Remove unused IppLibrary sources Delete the legacy IppLibrary directory now that BadgeReleaseDemo uses the minimal custom IPP implementation end to end. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- BadgeReleaseDemo/IppLibrary/Constants.cs | 40 - .../IppLibrary/DocumentAttributes.cs | 64 - BadgeReleaseDemo/IppLibrary/Helpers.cs | 861 ---------- .../IppLibrary/IIppRequestFactory.cs | 284 ---- BadgeReleaseDemo/IppLibrary/IPPAttribute.cs | 1514 ----------------- .../IppLibrary/IPPAttributeGroup.cs | 187 -- BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs | 1183 ------------- BadgeReleaseDemo/IppLibrary/IPPEncoding.cs | 198 --- BadgeReleaseDemo/IppLibrary/IPPException.cs | 59 - .../IppLibrary/IPPFactoryHelper.cs | 40 - BadgeReleaseDemo/IppLibrary/IPPInputStream.cs | 183 -- BadgeReleaseDemo/IppLibrary/IPPJob.cs | 227 --- .../IppLibrary/IPPMemberAttribute.cs | 154 -- BadgeReleaseDemo/IppLibrary/IPPRequest.cs | 352 ---- .../IppLibrary/IPPRequestFactory.cs | 892 ---------- BadgeReleaseDemo/IppLibrary/IPPResponse.cs | 396 ----- BadgeReleaseDemo/IppLibrary/IPPTemplates.cs | 512 ------ BadgeReleaseDemo/IppLibrary/IPPValue.cs | 951 ----------- BadgeReleaseDemo/IppLibrary/JobAttributes.cs | 214 --- BadgeReleaseDemo/IppLibrary/JobEvents.cs | 17 - .../IppLibrary/NotifyAttributes.cs | 44 - .../IppLibrary/OperationAttributes.cs | 60 - .../IppLibrary/PrinterAttributes.cs | 217 --- BadgeReleaseDemo/IppLibrary/PrinterEvents.cs | 18 - .../IppLibrary/RawIppEncodingBase.cs | 81 - .../IppLibrary/RequestedAttributes.cs | 16 - .../IppLibrary/RequiredAttributes.cs | 15 - .../IppLibrary/ZeroByteReadException.cs | 21 - 28 files changed, 8800 deletions(-) delete mode 100644 BadgeReleaseDemo/IppLibrary/Constants.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/Helpers.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPAttribute.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPEncoding.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPException.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPInputStream.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPJob.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPRequest.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPResponse.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPTemplates.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/IPPValue.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/JobAttributes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/JobEvents.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/OperationAttributes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/PrinterEvents.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs delete mode 100644 BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs diff --git a/BadgeReleaseDemo/IppLibrary/Constants.cs b/BadgeReleaseDemo/IppLibrary/Constants.cs deleted file mode 100644 index 732f765..0000000 --- a/BadgeReleaseDemo/IppLibrary/Constants.cs +++ /dev/null @@ -1,40 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - public static class Constants - { - public const string IppV10 = "1.0"; - public const string IppV11 = "1.1"; - public const string IppV20 = "2.0"; - public const string MopriaDiscoveryV10 = "1.0"; - public const string ConfigurationRegistryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\CloudPrint\EnterpriseCloudPrintService"; - public const string LocalSpoolerLayer = "localspl"; - public const string CharSet = "utf-8"; - public const string DocumentFormatPdf = "application/pdf"; - public const string DocumentFormatPwgRaster = "image/pwg-raster"; - public const string DocumentFormatPclm = "application/PCLm"; - public const string DocumentFormatOxps = "application/oxps"; - public const string DocumentFormatUrf = "image/urf"; - public const string DocumentFormatOctetStream = "application/octet-stream"; - public const string UnsupportedAttribute = "unsupported"; - public const string CompressionNone = "none"; - public const string JobsCompleted = "completed"; // Value for WhichJobs attribute. - public const string JobsNotCompleted = "not-completed"; // Value for WhichJobs attribute. - public const string JobFetchable = "fetchable"; // Value for WhichJobs attribute (IPP-INFRA). - public const string Attempted = "attempted"; - public const string NotAttempted = "not-attempted"; - public const int ShortStringLength = 127; // 127 octets maximum for text (127), name (127) attributes (RFC 2911 4.1.1) - public const int MaxTextLength = 1023; // 1023 octets maximum for text (MAX) attributes (RFC 2911 4.1.1) - public const int MaxNameLength = 255; // 255 octets maximum for name (MAX) attributes (RFC 2911 4.1.2) - public const int MaxUriLength = 1023; // 1023 octets maximum for uri attributes (RFC 2911 4.1.5) - public const int MaxKeywordLength = 255; // 255 characters max for keywords (RFC 2911 4.1.3) - public const int MaxMimeTypeLength = 255; // 255 characters max for mime types (RFC 2911 4.1.9) - public const int PrintSchemaVersion = 1; // PrintSchema version - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs b/BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs deleted file mode 100644 index f3c54a7..0000000 --- a/BadgeReleaseDemo/IppLibrary/DocumentAttributes.cs +++ /dev/null @@ -1,64 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - public static class DocumentAttributes - { - public const string Copies = "copies"; - public const string CoverBack = "cover-back"; - public const string CoverFront = "cover-front"; - public const string DateTimeAtCompleted = "date-time-at-completed"; - public const string DocumentJobId = "document-job-id"; - public const string DocumentNaturalLanguage = "document-natural-language"; - public const string DocumentPrinterUri = "document-printer-uri"; - public const string DocumentState = "document-state"; - public const string DocumentStateMessage = "document-state-message"; - public const string DocumentStateReasons = "document-state-reasons"; - public const string DocumentUuid = "document-uuid"; - public const string FeedOrientation = "feed-orientation"; - public const string Finishings = "finishings"; - public const string FinishingsCol = "finishings-col"; - public const string ImpressionsCompleted = "impressions-completed"; - public const string LastDocument = "last-document"; - public const string Media = "media"; - public const string MediaBackCoating = "media-back-coating"; - public const string MediaBottomMargin = "media-bottom-margin"; - public const string MediaCol = "media-col"; - public const string MediaColor = "media-color"; - public const string MediaGrain = "media-grain"; - public const string MediaFrontCoating = "media-front-coating"; - public const string MediaHoleCount = "media-hole-count"; - public const string MediaInfo = "media-info"; - public const string MediaKey = "media-key"; - public const string MediaLeftMargin = "media-left-margin"; - public const string MediaOrderCount = "media-order-count"; - public const string MediaPreprinted = "media-pre-printed"; - public const string MediaRecycled = "media-recycled"; - public const string MediaRightMargin = "media-right-margin"; - public const string MediaSize = "media-size"; - public const string MediaSizeName = "media-size-name"; - public const string MediaSource = "media-source"; - public const string MediaThickness = "media-thickness"; - public const string MediaTooth = "media-tooth"; - public const string MediaTopMargin = "media-top-margin"; - public const string MediaType = "media-type"; - public const string MediaWeightMetric = "media-weight-metric"; - public const string OrientationRequested = "orientation-requested"; - public const string OutputBin = "output-bin"; - public const string Overrides = "overrides"; - public const string PageRanges = "page-ranges"; - public const string PrintColorMode = "print-color-mode"; - public const string PrintContentOptimize = "print-content-optimize"; - public const string PrintRenderingIntent = "print-rendering-intent"; - public const string PrintQuality = "print-quality"; - public const string PrinterResolution = "printer-resolution"; - public const string Sides = "sides"; - public const string XDimension = "x-dimension"; - public const string YDimension = "y-dimension"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/Helpers.cs b/BadgeReleaseDemo/IppLibrary/Helpers.cs deleted file mode 100644 index 7c271d2..0000000 --- a/BadgeReleaseDemo/IppLibrary/Helpers.cs +++ /dev/null @@ -1,861 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using BadgeReleaseDemo.IppLibrary.Common; - - public static class Helpers - { - /// - /// Used in the job-uri path. - /// Example: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 - /// - private const string JobUriSegment = "jobs"; - - /// - /// Used in the printer-uri and job-uri path. - /// Example: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 - /// - private const string PrinterSegment = "printers"; - - /// - /// URN URI Scheme - /// - private const string UrnScheme = "urn"; - - /// - /// Prefix for UUID URIs - /// - private const string UuidPrefix = "uuid:"; - - public static void WriteNetworkShort(Stream s, short shortValue) - { - s.WriteByte((byte)(shortValue >> 8)); - s.WriteByte((byte)shortValue); - } - - public static void WriteNetworkInteger(Stream s, int intValue) - { - s.WriteByte((byte)(intValue >> 24)); - s.WriteByte((byte)((intValue & 0x00ff0000) >> 16)); - s.WriteByte((byte)((intValue & 0x0000ff00) >> 8)); - s.WriteByte((byte)intValue); - } - - public static void WriteAsciiString(Stream s, string stringValue) - { - byte[] arr = Encoding.ASCII.GetBytes(stringValue); - MemoryStream ms = new MemoryStream(arr); - ms.CopyTo(s); - } - - public static byte[] StringToUTF8ByteArray(string stringValue) - { - if (stringValue == null) - { - stringValue = string.Empty; - } - - return Encoding.UTF8.GetBytes(stringValue); - } - - public static string UTF8ByteArrayToString(byte[] byteArray) - { - return Encoding.UTF8.GetString(byteArray); - } - - public static string UTF8ByteArrayToString(byte[] byteArray, int offset, int length) - { - if (offset < 0 || length < 0 || offset + length > byteArray.Length) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Buffer overflow reading a string."); - } - - return Encoding.UTF8.GetString(byteArray, offset, length); - } - - /// - /// Unlike the above, this is more or less correct, though we may want to check for non-ASCII chars in the incoming string. - /// - /// The string to convert to byte array. - /// Byte array representation of the string. - public static byte[] StringToASCIIByteArray(string stringValue) - { - return Encoding.ASCII.GetBytes(stringValue); - } - - /// - /// See comments above - /// - /// The byte array. - /// String of byte array. - public static string ASCIIByteArrayToString(byte[] byteArray) - { - return Encoding.ASCII.GetString(byteArray); - } - - /// - /// Recover a string from a byte array. - /// - /// The byte array. - /// Offset to the beginning of the string. - /// Length of string. - /// ASCII string of the byte array. - public static string ASCIIByteArrayToString(byte[] byteArray, int offset, int length) - { - if (offset < 0 || length < 0 || offset + length > byteArray.Length) - { - throw new IPPException(StatusCode.ClientErrorNotPossible, "Buffer overflow reading a string."); - } - - var sb = new StringBuilder(); - for (var i = 0; i < length; i++) - { - sb.Append((char)byteArray[i + offset]); - } - - return sb.ToString(); - } - - public static bool ByteArrayToBool(byte[] b) - { - return b[0] != 0; - } - - public static short ByteArrayToShort(byte[] b) - { - return (short)(ushort)((b[0] << 8) | b[1]); - } - - public static byte[] ShortToByteArray(short x) - { - var b = new byte[2]; - b[0] = (byte)(x >> 8); - b[1] = (byte)x; - return b; - } - - public static int ByteArrayToInteger(byte[] byteArray) - { - return (byteArray[0] << 24) | (byteArray[1] << 16) | (byteArray[2] << 8) | byteArray[3]; - } - - public static Tuple ByteArrayToIntegerRange(byte[] byteArray) - { - var lower = (byteArray[0] << 24) | (byteArray[1] << 16) | (byteArray[2] << 8) | byteArray[3]; - var upper = (byteArray[4] << 24) | (byteArray[5] << 16) | (byteArray[6] << 8) | byteArray[7]; - return new Tuple(lower, upper); - } - - /// - /// Converts an integer to a byte array - /// - /// The integer value. - /// Byte array representation of an integer. - public static byte[] IntegerToByteArray(int intValue) - { - byte[] b = new byte[4]; - b[0] = (byte)(intValue >> 24); - b[1] = (byte)((intValue & 0x00ff0000) >> 16); - b[2] = (byte)((intValue & 0x0000ff00) >> 8); - b[3] = (byte)intValue; - - return b; - } - - /// - /// Converts a range of integer to a byte array. - /// - /// Lower bound of the integer values. - /// Upper bound of the integer values. - /// Byte array representation of an integer range. - public static byte[] IntegerRangeToByteArray(int lower, int upper) - { - if (lower > upper) - { - throw new IPPException(StatusCode.ClientErrorNotPossible, "Incorrect integer range."); - } - - var lowerBound = IntegerToByteArray(lower); - var upperBound = IntegerToByteArray(upper); - return lowerBound.Concat(upperBound).ToArray(); - } - - /// - /// Converts .Net DateTime to a byte array according to https://tools.ietf.org/html/rfc2579 page 18. - /// - /// DateTime value, always in UTC. - /// Byte array representation of a date time object. - public static byte[] DateTimeToByteArray(DateTime dateTime) - { - var byteArray = new[] - { - (byte)(dateTime.Year >> 8), - (byte)dateTime.Year, - (byte)dateTime.Month, - (byte)dateTime.Day, - (byte)dateTime.Hour, - (byte)dateTime.Minute, - (byte)dateTime.Second, - (byte)(dateTime.Millisecond / 100), - (byte)'+', - (byte)0, - (byte)0 - }; - - return byteArray; - } - - /// - /// Converts IPP resolution (https://tools.ietf.org/html/rfc8011#section-5.1.16) to byte array. - /// - /// The x dimension. - /// The y dimension. - /// The unit of the dimensions. - /// Byte array representation of resolution dimensions. - public static byte[] ResolutionToByteArray(int x, int y, sbyte units) - { - var xStream = IntegerToByteArray(x); - var yStream = IntegerToByteArray(y); - var unitStream = new byte[] { (byte)units }; - return xStream.Concat(yStream).Concat(unitStream).ToArray(); - } - - /// - /// Converts a byte array IPP resolution (https://tools.ietf.org/html/rfc8011#section-5.1.16). - /// - /// IPP resolution. - /// Resolution dimension of the byte array. - public static Tuple ByteArrayToResolution(byte[] b) - { - var x = (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]; - var y = (b[4] << 24) | (b[5] << 16) | (b[6] << 8) | b[7]; - var units = (sbyte)b[8]; - return new Tuple(x, y, units); - } - - /// - /// Converts a string to IPP string with natural language (e.g., TextWithNaturalLanguage and NameWithNaturalLanguage). - /// - /// The plain string. - /// The natural language - /// Byte array of the string. - public static byte[] StringWithNaturalLanguageToByteArray(string theString, string naturalLanguage) - { - // Note: from the RFC: - // a. a SIGNED-SHORT which is the number of - // octets in the following field - // b. a value of type natural-language, - // c. a SIGNED-SHORT which is the number of - // octets in the following field, - // d. a value of type textWithoutLanguage. - var langLength = ShortToByteArray((short)naturalLanguage.Length); - var langValue = StringToASCIIByteArray(naturalLanguage); - var stringValue = StringToUTF8ByteArray(theString); - /* - * https://tools.ietf.org/html/rfc8010#section-3 - * 3.9. (Attribute) "value" - +----------------------+--------------------------------------------+ - | Syntax of Attribute | Encoding | - | Value | | - +----------------------+--------------------------------------------+ - | textWithoutLanguage, | LOCALIZED-STRING | - | nameWithoutLanguage | | - +----------------------+--------------------------------------------+ - | textWithLanguage | OCTET-STRING consisting of four fields: a | - | | SIGNED-SHORT, which is the number of | - | | octets in the following field; a value of | - | | type natural-language; a SIGNED-SHORT, | - | | which is the number of octets in the | - | | following field; and a value of type | - | | textWithoutLanguage. The length of a | - | | textWithLanguage value MUST be 4 + the | - | | value of field a + the value of field c. | - +----------------------+--------------------------------------------+ - */ - - // NOTE: the SIGNED-SHORT of field c is the number of octets which is LOCALIZED-STRING - var stringLength = ShortToByteArray((short)stringValue.Length); - return langLength.Concat(langValue).Concat(stringLength).Concat(stringValue).ToArray(); - } - - /// - /// Converts a byte array of IPP string with natural language to string and the natural language parts. - /// - /// The IPP stream. - /// String of the byte array. - public static Tuple ByteArrayToStringWithNaturalLanguage(byte[] b) - { - // Note: from the RFC: - // a. a SIGNED-SHORT which is the number of - // octets in the following field - // b. a value of type natural-language, - // c. a SIGNED-SHORT which is the number of - // octets in the following field, - // d. a value of type textWithoutLanguage. - var langLength = (short)(ushort)((b[0] << 8) | b[1]); - var langValue = ASCIIByteArrayToString(b, 2, langLength); - - var languageByteCount = 2 + langLength; - var stringLength = (short)(ushort)(b[languageByteCount] << 8 | b[languageByteCount + 1]); - var stringValue = UTF8ByteArrayToString(b, languageByteCount + 2, stringLength); - - return new Tuple(stringValue, langValue); - } - - /// - /// Converts a byte array to .Net DateTime according to https://tools.ietf.org/html/rfc2579 page 18. - /// - /// IPP DateTime array. - /// Date time object from a byte array. - public static DateTime ByteArrayToDateTime(byte[] dateTimeArray) - { - var year = (dateTimeArray[0] << 8) | dateTimeArray[1]; - var month = dateTimeArray[2]; - var day = dateTimeArray[3]; - var hour = dateTimeArray[4]; - var minute = dateTimeArray[5]; - var second = dateTimeArray[6]; - - // Before version 4.0.1, the 8th octet (dateTimeArray[7]) was used to store centisecond instead of deci second. - // To support the clients sending centiseconds in place of deciseconds, a modulo 1000 is added. - var milliSecond = (dateTimeArray[7] * 100) % 1000; - - return new System.DateTime(year, month, day, hour, minute, second, milliSecond, DateTimeKind.Local); - } - - /// - /// Converts a boolValue to a byte array - /// - /// The boolean value. - /// Byte array of a boolean. - public static byte[] BoolToByteArray(bool boolValue) - { - byte[] b = new byte[1]; - b[0] = (byte)(boolValue ? 1 : 0); - - return b; - } - - /// - /// Example jobUri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 - /// Returned value: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b - /// - /// The uri of a job. - /// The uri of the printer. - public static Uri GetPrinterUriFromJobUri(Uri jobUri) - { - // Example: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 - // Authority: "localhost:44336" - // Segments: [0] "/" - // [1] "printers/" - // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" - // [3] "jobs/" - // [4] "388" - var printerUri = Helpers.CreateUri(jobUri.GetLeftPart(UriPartial.Authority)); - printerUri = Helpers.CreateUri(printerUri, jobUri.Segments[1] + jobUri.Segments[2]); - return printerUri; - } - - /// - /// Example: - /// baseUrl: https://print.print.microsoft.com - /// printerId: 810a8958-13f4-4044-a09b-a372e8990a6b. - /// jobId: 388 - /// Returned value: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b/Jobs/388 - /// - /// The base url. - /// The printer id. - /// The job id. - /// A uri of a job. - public static Uri CreateJobUri(Uri baseUrl, string printerId, int jobId) - { - // Job uri must be "ipps". - var jobUriBuilder = new UriBuilder(baseUrl) { Scheme = "ipps" }; - jobUriBuilder.Path += PrinterSegment + '/' + printerId + '/' + JobUriSegment + '/' + jobId.ToString(CultureInfo.InvariantCulture); - return jobUriBuilder.Uri; - } - - /// - /// Example: - /// baseUrl: https://print.print.microsoft.com - /// printerId: 810a8958-13f4-4044-a09b-a372e8990a6b - /// Returned value: ipps://print.print.microsoft.com/printers/810a8958-13f4-4044-a09b-a372e8990a6b - /// - /// The base url. - /// The printer id. - /// Uri of the printer. - public static Uri CreatePrinterUri(Uri baseUrl, string printerId) - { - // Printer uri must be "ipps". - var printerUriBuilder = new UriBuilder(baseUrl) { Scheme = "ipps" }; - printerUriBuilder.Path += PrinterSegment + '/' + printerId; - return printerUriBuilder.Uri; - } - - /// - /// Returns the Printer UUID as a URN URI string given the printerId. - /// For example, urn:uuid:810a8958-13f4-4044-a09b-a372e8990a6b - /// - /// The printer id. - /// UUID URN URI string - public static string CreatePrinterUuidUri(string printerId) - { - return $"{UrnScheme}:{UuidPrefix}{printerId}"; - } - - /// - /// Example: - /// jobUri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 - /// returned value: 388 - /// To avoid returning an invalid job ID (e.g., -1), caller must have jobUri to call this function. - /// - /// Uri of a job. - /// Job id - public static int GetJobIdFromJobUri(Uri jobUri) - { - // Example: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b/jobs/388 - // Segments: [0] "/" - // [1] "printers/" - // [2] "8b518e16-2d2b-11e8-b467-0ed5f89f718b" - // [3] "jobs/" - // [4] "388" - var jobId = int.Parse(jobUri.Segments[4].TrimEnd('/'), CultureInfo.CurrentCulture); - return jobId; - } - - /// - /// Return the output-device-uuid from its uri format. - /// Example: - /// outputDeviceUuidUri: urn:uuid:01234567-89AB-CDEF-FEDC-BA9876543210 - /// Return: 01234567-89AB-CDEF-FEDC-BA9876543210 - /// - /// The uuid URN. - /// The uuid string. - public static string GetOutputDeviceUuidFromUri(string outputDeviceUuidUri) - { - Uri uuidUri; - - try - { - uuidUri = new Uri(outputDeviceUuidUri); - } - catch - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "The output-device-uuid must be a uri."); - } - - if (string.Compare(uuidUri.Scheme, UrnScheme, StringComparison.OrdinalIgnoreCase) != 0) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "The output-device-uuid must be of URN scheme."); - } - - if (!CultureInfo.InvariantCulture.CompareInfo.IsPrefix(uuidUri.LocalPath, UuidPrefix, CompareOptions.IgnoreCase)) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "The output-device-uuid must be of format urn:uuid:."); - } - - return uuidUri.LocalPath.Substring(UuidPrefix.Length); - } - - /// - /// Example printer URL: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b - /// This function returns: "8b518e16-2d2b-11e8-b467-0ed5f89f718b". - /// - /// Printer URL. This is typically obtained from printer-uri attribute. - /// The printer id that is extracted from printerUrl. - public static string GetPrinterIdFromPrinterUri(Uri printerUri) - { - // Example: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b - // Segments: [0] "/" - // [1] "printers/" - // [2] "8b518e16-2d2b-11e8-b467-0ed5f89f718b" - // printer ID is in segment 2 and may have trailing '/'. - if (printerUri == null) - { - return string.Empty; - } - - Helpers.IsValidPrinterUri(printerUri); - return printerUri.Segments[2].TrimEnd('/'); - } - - /// - /// Example of job uri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388. - /// This function returns: 810a8958-13f4-4044-a09b-a372e8990a6b - /// - /// Valid job uri. This is typically obtained from job-uri attribute. - /// The printer id that is extracted from the jobUri. - public static string GetPrinterIdFromJobUri(Uri jobUri) - { - // Example: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 - // Segments: [0] "/" - // [1] "printers" - // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" - // [3] "jobs/" - // [4] "388" - // Printer id is segment 2 and may have trailing '/'. - if (jobUri == null) - { - return string.Empty; - } - - Helpers.IsValidJobUri(jobUri); - return jobUri.Segments[2].TrimEnd('/'); - } - - /// - /// Retrieve the system uptime. RFC2911 4.3.14.4 specifies job-printer-up-time as an integer (giving us 68 years of resolution) - /// - /// System up time in integer. - public static int GetSystemUptime(DateTime printerCreationTimeUtc) - { - var nowTime = DateTime.UtcNow; - var upTime = nowTime - printerCreationTimeUtc; - return (int)upTime.TotalSeconds; // double to int, max printer life time is 68 years. - } - - /// - /// Retrieve the culture installed on the print server. This returns GetSystemDefaultUILanguage() - /// - /// Natural language string. - public static string GetNaturalLanguage() - { - return CultureInfo.InstalledUICulture.Name.ToLower(CultureInfo.CurrentCulture); - } - - /// - /// Copy attribute value from either group 1, 2 or 3 to the target attribute group. - /// - /// The name of the attribute. - /// The destination to copy the attribute to. - /// Attribute group 1. - /// Attribute group 2. - /// Attribute group 3. - /// True if the attribute already exists in or is inserted to destinationGroup. - public static bool CopyAttribute( - string attributeName, - IppAttributeGroup destinationGroup, - IppAttributeGroup group1, - IppAttributeGroup group2 = null, - IppAttributeGroup group3 = null) - { - // No duplicate needed. - if (destinationGroup.Attributes.ContainsKey(attributeName)) - { - return true; - } - - if (group1 != null && group1.Attributes.ContainsKey(attributeName)) - { - destinationGroup.AddAttribute(group1.Attributes[attributeName]); - return true; - } - else if (group2 != null && group2.Attributes.ContainsKey(attributeName)) - { - destinationGroup.AddAttribute(group2.Attributes[attributeName]); - return true; - } - else if (group3 != null && group3.Attributes.ContainsKey(attributeName)) - { - destinationGroup.AddAttribute(group3.Attributes[attributeName]); - return true; - } - - return false; - } - - /// - /// Returns the attribute value from either group 1, 2 or 3 to the target attribute group. - /// - /// The name of the attribute. - /// Attribute group 1. - /// Attribute group 2. - /// Attribute group 3. - /// Returns the attribute value if found, null otherwise. - public static IppAttribute GetAttribute( - string attributeName, - IppAttributeGroup group1, - IppAttributeGroup group2 = null, - IppAttributeGroup group3 = null) - { - if (group1 != null && group1.Attributes.ContainsKey(attributeName)) - { - return group1.Attributes[attributeName]; - } - else if (group2 != null && group2.Attributes.ContainsKey(attributeName)) - { - return group2.Attributes[attributeName]; - } - else if (group3 != null && group3.Attributes.ContainsKey(attributeName)) - { - return group3.Attributes[attributeName]; - } - - return null; - } - - /// - /// Return true if date time is default value. - /// - public static bool IsDefaultDateTime(DateTime dateTime) - { - var isDefault = dateTime == DateTime.MinValue.ToUniversalTime(); - return isDefault; - } - - /// - /// Default date time. - /// - public static DateTime GetDefaultDateTime() - { - var defaultTime = DateTime.MinValue.ToUniversalTime(); - return defaultTime; - } - - public static string GetDefaultDateTimeString() - { - var defaultTime = DateTime.MinValue.ToUniversalTime().ToString("o", CultureInfo.InvariantCulture); - return defaultTime; - } - - /// - /// Return current time in UTC. - /// - public static DateTime GetCurrentTimeUtc() - { - return DateTime.UtcNow; - } - - /// - /// Return current time in UTC. - /// - public static string GetCurrentTimeUtcString() - { - return DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture); - } - - /// - /// Parse dateTimeString to DateTime. Return default time if unsuccessfull (e.g., for older data). - /// - public static DateTime ParseDateTime(string dateTimeString) - { - // See https://docs.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings?view=netframework-4.7.2 for the "o" format. - // Example dateTimeString: "2018-12-10T06:58:21.0075545Z". - if (!DateTime.TryParse(dateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out DateTime dateTimeToReturn)) - { - dateTimeToReturn = GetDefaultDateTime(); - } - - return dateTimeToReturn; - } - - /// - /// Calculate the numbers of seconds from time1 to time2. - /// - public static int CalculateSecondsSince(DateTime time1, DateTime time2) - { - var timeSpan = time2 - time1; - return (int)timeSpan.TotalSeconds; - } - - /// - /// Returns true if tag is for an attribute group. - /// - public static bool IsBeginAttributeGroupTag(Tag tag) - { - return (tag < Tag.Unsupported && tag >= Tag.Reserved) && tag != Tag.EndOfAttributes; - } - - /// - /// Returns true if tag is for an attribute. - /// - public static bool IsValueTag(Tag tag) - { - return tag >= Tag.Unsupported; - } - - /// - /// Returns true if tag is for a begin collection attribute tag. - /// https://tools.ietf.org/html/rfc8010#section-3.1.6 - /// - public static bool IsBeginCollectionValueTag(Tag tag) - { - return tag == Tag.BegCollection; - } - - /// - /// Returns true if tag is for member attribute tag. - /// - public static bool IsMemberAttrNameTag(Tag tag) - { - return tag == Tag.MemberAttrName; - } - - /// - /// Create a new uri object. - /// - /// The string representation of the uri. - /// Newly created uri object. - public static Uri CreateUri(string uriString) - { - try - { - return new Uri(uriString); - } - catch - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid uri: " + uriString); - } - } - - /// - /// Create a new uri object. - /// - /// Base uri. - /// Additional segments. - /// Newly created uri object. - public static Uri CreateUri(Uri uri, string segments) - { - try - { - return new Uri(uri, segments); - } - catch - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid uri: " + uri.ToString() + ". segments: " + segments); - } - } - - /// - /// Throw if the printerUri is invalid. - /// - /// Printer uri, usually from printer-uri attribute. - public static void IsValidPrinterUri(Uri printerUri) - { - const string printersSegment = "printers/"; - - // Example of correct printer-uri: ipps://localhost:44336/printers/8b518e16-2d2b-11e8-b467-0ed5f89f718b. - // Segments: [0] "/" - // [1] "printers/" - // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" - if (printerUri.Segments.Length != 3 || - string.Compare(printerUri.Segments[1], printersSegment, StringComparison.OrdinalIgnoreCase) != 0) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid printer-uri: " + printerUri.ToString()); - } - } - - /// - /// Throw if jobUri is not valid. - /// - /// The uri of a job. - public static void IsValidJobUri(Uri jobUri) - { - const string printersSegment = "printers/"; - const string jobUriSegment = "jobs/"; - - // Example of correct job-uri: ipps://localhost:44336/printers/810a8958-13f4-4044-a09b-a372e8990a6b/jobs/388 - // Segments: [0] "/" - // [1] "printers/" - // [2] "810a8958-13f4-4044-a09b-a372e8990a6b/" - // [3] "jobs/" - // [4] "388" - if (jobUri.Segments.Length != 5 || - string.Compare(jobUri.Segments[1], printersSegment, StringComparison.OrdinalIgnoreCase) != 0 || - string.Compare(jobUri.Segments[3], jobUriSegment, StringComparison.OrdinalIgnoreCase) != 0) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid job-uri: " + jobUri.ToString()); - } - } - - /// - /// See comments above - /// - /// array - /// string representation of b - public static string ByteArrayToAsciiString(byte[] array) - { - StringBuilder sb = new StringBuilder(); - - for (int i = 0; i < array.Length; i++) - { - sb.Append((char)array[i]); - } - - return sb.ToString(); - } - - /// - /// Retrieve the system uptime. RFC2911 4.3.14.4 specifies job-printer-up-time as an integer (giving us 68 years of resolution) - /// - /// time as integer - public static int GetSystemUptime() - { - return System.Environment.TickCount; - } - - public static IppAttributeGroup CreateOperationAttributes() - { - var operationAttributes = new IppAttributeGroup(Tag.OperationAttributes); - - // Add required operation attributes - operationAttributes.AddAttribute(new IppAttribute(OperationAttributes.AttributesCharset, IppValue.CreateCharsetValue(IppEncoding.CharSet))); - operationAttributes.AddAttribute(new IppAttribute(OperationAttributes.AttributesNaturalLanguage, IppValue.CreateNaturalLanguageValue(Helpers.GetNaturalLanguage()))); - return operationAttributes; - } - - public static IppAttributeGroup CreateSubscriptionAttributes() - { - var subscriptionAttributes = new IppAttributeGroup(Tag.SubscriptionAttributes); - - // Add required operation attributes - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyCharset, IppValue.CreateCharsetValue(IppEncoding.CharSet))); - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyNaturalLanguage, IppValue.CreateNaturalLanguageValue(Helpers.GetNaturalLanguage()))); - return subscriptionAttributes; - } - - public static IppAttributeGroup CreateEventAttributes(int subscriptionId, string notifySubscribedEvent, int sequenceNumber, string notifyText) - { - var subscriptionAttributes = new IppAttributeGroup(Tag.EventNotificationAttributes); - - // Add required operation attributes - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyCharset, IppValue.CreateCharsetValue(IppEncoding.CharSet))); - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyNaturalLanguage, IppValue.CreateNaturalLanguageValue(Helpers.GetNaturalLanguage()))); - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifySubscriptionId, IppValue.CreateIntegerValue(subscriptionId))); - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifySubscribedEvent, IppValue.CreateKeywordValue(notifySubscribedEvent))); - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifySequenceNumber, IppValue.CreateIntegerValue(sequenceNumber))); - subscriptionAttributes.AddAttribute(new IppAttribute(NotifyAttributes.NotifyText, IppValue.CreateNameWithoutLanguageValue(notifyText))); - return subscriptionAttributes; - } - - // See https://ftp.pwg.org/pub/pwg/ipp/registrations/apple-printer-firmware-20190724.txt - public static byte[] FirmwareStringVersionToFirmwareVersion(string version) - { - var versionBytes = new List() { 0 }; - foreach (var c in version) - { - if (byte.TryParse(c.ToString(), out var b)) - { - versionBytes.Add(b); - } - else - { - versionBytes.Add(0); - } - } - - return versionBytes.ToArray(); - } - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs b/BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs deleted file mode 100644 index 698d034..0000000 --- a/BadgeReleaseDemo/IppLibrary/IIppRequestFactory.cs +++ /dev/null @@ -1,284 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System.Collections.Generic; - using System.IO; - using System.Threading.Tasks; - - public interface IIppRequestFactory - { - /// - /// Create Cancel-Job request. - /// - /// - /// - /// - /// - /// - /// - Task CreateCancelJobRequestAsync(int requestId, string requestingUserName, string requestingUserUri, - int jobId, string jobUri); - - /// - /// Create Get-Printer-Attributes request. - /// - /// - /// - /// - /// - /// - /// - Task CreateGetPrinterAttributesRequestAsync( - int requestId, string requestingUserName, string requestingUserUri, List extraOperationAttributes, List requestedAttributes); - - /// - /// Create Get-Output-Device-Attributes request. - /// - /// - /// - /// - /// - /// - /// - Task CreateGetOutputDeviceAttributesRequestAsync( - int requestId, string requestingUserName, string requestingUserUri, List extraOperationAttributes, List requestedAttributes); - - /// - /// Create Get-Printer-Attributes request. - /// - /// - /// - /// - /// - /// - Task CreateGetSupportedPrinterAttributesRequestAsync( - int requestId, string requestingUserName, string requestingUserUri, List requestedAttributes); - - /// - /// Create Get-Printer-Attributes request. - /// - /// - /// - /// - /// - /// - Task CreateSetPrinterAttributesRequestAsync( - int requestId, - string requestingUserName, - string requestingUserUri, - IEnumerable attributesToSet); - - /// - /// Create Get-Printer-Device-Capabilities request. - /// - /// - /// - /// - Task CreateGetPrinterDeviceCapabilitiesRequestAsync(int requestId, string requestingUserName, string requestingUserUri); - - /// - /// Create Validate-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.3). - /// - /// Request id of the IPP packet. - /// - /// - /// - /// - Task CreateValidateJobRequestAsync( - int requestId, - string jobName, - string documentName, - IppAttributeGroup extraOperationAttributes = null, - IppAttributeGroup jobTemplateAttributes = null); - - /// - /// Create Print-Job request. - /// - /// requestId - /// jobName - /// documentName - /// documentPayload - /// Attributes to be included in the operation attribute group. - /// The job template attributes to be included in the request. - /// representation - Task CreatePrintJobRequestAsync(int requestId, string jobName, string documentName, - Stream documentPayload, IppAttributeGroup extraOperationAttributes, IppAttributeGroup jobTemplateAttributes); - - /// - /// Create Create-Job request. - /// - /// requestId - /// jobName - /// The job template attributes to be included in the request. - /// representation - Task CreateCreateJobRequestAsync(int requestId, string jobName = null, IppAttributeGroup jobTemplateAttributes = null); - - /// - /// Create Send-Document request. - /// - /// requestId - /// lastDocument - /// jobUri - /// jobId - /// documentPayload - /// Attributes to be included in the operation attribute group. - /// The job template attributes to be included in the request. - /// representation - Task CreateSendDocumentRequestAsync( - int requestId, - bool lastDocument, - string jobUri = null, - int jobId = 0, - Stream documentPayload = null, - IppAttributeGroup extraOperationAttributes = null, - IppAttributeGroup jobTemplateAttributes = null); - - /// - /// Create Close-Job request. - /// - /// requestId - /// jobUri - /// jobId - /// representation - Task CreateCloseJobRequestAsync(int requestId, string jobUri = null, int jobId = 0); - - /// - /// Create Get-Job-Attributes request. - /// - /// requestId - /// jobUri - /// requestedAttributes - /// representation - Task CreateGetJobAttributesRequestAsync(int requestId, string jobUri, - List requestedAttributes); - - /// - /// Create Get-Jobs request. - /// - /// requestId - /// jobType - /// requestingUserName - /// requestingUserUri - /// printerUri - /// outputDeviceUuid - /// requestedAttributes - /// representation - Task CreateGetJobsRequestAsync(int requestId, - string jobType, - string requestingUserName, - string requestingUserUri, - string printerUri, - string outputDeviceUuid, - List requestedAttributes); - - /// - /// Create Acknowledge-Job request. - /// - /// requestId - /// outputDeviceUuid - /// jobId - /// fetchStatusCode - /// fetchStatusMessage - /// representation - Task CreateAcknowledgeJobRequestAsync(int requestId, string outputDeviceUuid, int jobId, - StatusCode fetchStatusCode, string fetchStatusMessage); - - /// - /// Create IPP-INFRA Acknowledge-Document request. - /// - /// requestId - /// outputDeviceUuid - /// jobId - /// documentNumber - /// fetchStatusCode - /// fetchStatusMessage - /// representation - Task CreateAcknowledgeDocumentRequestAsync(int requestId, string outputDeviceUuid, int jobId, - int documentNumber, StatusCode fetchStatusCode, string fetchStatusMessage); - - /// - /// Create Fetch-Job request. - /// - /// requestId - /// outputDeviceUuid - /// jobId - /// representation - Task CreateFetchJobRequestAsync(int requestId, string outputDeviceUuid, int jobId); - - /// - /// Create Fetch-Document request. - /// - /// requestId - /// outputDeviceUuid - /// jobId - /// documentNumber - /// representation - Task CreateFetchDocumentRequestAsync(int requestId, string outputDeviceUuid, int jobId, - int documentNumber); - - /// - /// Create Update-Output-Device-Attribute request. - /// - /// requestId - /// outputDeviceUuid - /// optionalPrinterAttributes - /// representation - Task CreateUpdateOutputDeviceAttributesRequestAsync(int requestId, string outputDeviceUuid, - IppAttributeGroup optionalPrinterAttributes); - - /// - /// Create Update-Active-Jobs request. - /// - /// requestId - /// outputDeviceUuid - /// optionalOperationAttributes - /// representation - Task CreateUpdateActiveJobsRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalOperationAttributes); - - /// - /// Create Update-Document-Status request. - /// - /// representation - IppRequest CreateUpdateDocumentStatusRequest(); - - /// - /// Create Update-Job-Status request - /// - /// requestId - /// outputDeviceUuid - /// optionalJobAttributes - /// representation - Task CreateUpdateJobStatusRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalJobAttributes); - - /// - /// Creates Microsoft's custom Set-Print-Device-Capabilities request. - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The PDC stream. - /// representation - Task CreateSendPdcRequestAsync(int requestId, string outputDeviceUuid, Stream pdcData); - - /// - /// Creates Microsoft's custom Set-Print-Capabilities request. - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The PC stream. - /// representation - Task CreateSendPcRequestAsync(int requestId, string outputDeviceUuid, Stream pcData); - - /// - /// Creates a Subscriptions related request - /// - /// Request id of the IPP packet. - /// additional attributes - /// representation - Task CreateSubscriptionRelatedRequestAsync(Operation operation, int requestId, List optionalSubscriptionAttributeGroups); - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPAttribute.cs b/BadgeReleaseDemo/IppLibrary/IPPAttribute.cs deleted file mode 100644 index c86aa04..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPAttribute.cs +++ /dev/null @@ -1,1514 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using BadgeReleaseDemo.IppLibrary.Common; - - /// - /// Represents an IPP attribute (encoding documentation: https://tools.ietf.org/html/rfc8010#section-3.1.3). - /// - /// IppAttribute ::= AttributeName, Value { Value } - /// Value ::= SimpleIppValue - /// | CollectionIppAttributeValue - /// SimpleIppValue ::= Type, Value - /// CollectionIppAttributeValues ::= MemberAttribute { MemberAttribute } - /// MemberAttribute ::= AttributeName, Value { Value } - /// - /// - /// Each IppAttribute has the following members: - /// 1. attribute name. - /// 2. List of IppValues (One or more). - /// - /// An IppValue can be of two types: A simple IppAttribute Value, A CollectionIppAttributeValue. - /// - /// Simple IppAttribute value has the following members: - /// 1. Type - /// 2. Binary value. - /// - /// CollectionIppAtrrributeValue has the following members: - /// 1. List of member attributes. - /// - /// If an IppAttribute has simple values, it is considered a simple IppAttribute - /// If an IppAttribute has collectionIppAttributeValues, it is considered a Collection IppAttribute - /// - /// MemberAttribute: Each member attribute is similar to an IppAttribute. It can again be a simple IppAttribute or a CollectionIppAttribute. - /// Member attribute can have multiple values as well. - /// - /// Simple IPP attribute - /// | - /// + Attribute name - /// + Values[] - /// | - /// + type1 - value - /// + type2 - value - /// . - /// . - /// - /// Collection IPP attribute - /// | - /// | Attribute name - /// + Values[] - /// | - /// + -- Member attribute - /// | + member attribute value 1 - /// | + member attribute value 2 - /// | -- Member attribute - /// | - /// + -- Member attribute - /// | + member attribute value 1 - /// | + member attribute value 2 - /// | -- Member attribute - /// | - /// + -- Member attribute - /// | + member attribute value 1 - /// | + member attribute value 2 - /// | -- Member attribute - /// - /// - public class IppAttribute - { - /// - /// The values of the attribute. - /// - private readonly List values; - - /// - /// Initializes a new instance of the class with no values. - /// - public IppAttribute(string attributeName) - { - this.ValueName = attributeName; - this.values = new List(); - this.IsCollectionAttribute = false; - this.IsPii = this.IsPiiAttribute(); - } - - /// - /// Initializes a new instance of the class with a value. - /// - public IppAttribute(string attributeName, IppValue value) - : this(attributeName, new List { value }) - { - } - - /// - /// Initializes a new instance of the class with a value. - /// - public IppAttribute(string attributeName, List values) - : this(attributeName) - { - this.values = new List(values); - - // The values must be either all collection attributes or all regular values (thus a normal attribute). - int collectionValueCount = 0; - - foreach (var value in values) - { - if (value.IsCollectionAttributeValue()) - { - collectionValueCount++; - } - } - - if (collectionValueCount != 0 && collectionValueCount != values.Count) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"An IPP attribute values must be either all collection attributes or regular attributes.")); - } - - if (collectionValueCount != 0) - { - this.IsCollectionAttribute = true; - } - } - - /// - /// Gets or sets a value indicating whether this is a collection attribute. - /// - public bool IsCollectionAttribute { get; set; } = false; - - /// - /// Gets the name of the value, i.e., attribute name. - /// - public string ValueName { get; } - - /// - /// Gets the the first value. - /// - public IppValue FirstValue => this.values.Count != 0 ? this.values[0] : null; - - /// - /// Gets a value indicating whether there are additional values. - /// - public bool HasAdditionalValues => this.values.Count > 1; - - /// - /// Gets the values of the attribute. - /// - public IReadOnlyList Values => this.values.AsReadOnly(); - - /// - /// Gets a value indicating whether this is an empty attribute with no values and no collection attribute. - /// - private bool IsEmptyAttribute => this.values.Count == 0; - - /// - /// Gets a value indicating whether this attribute is considered a Pii attribute. - /// - private bool IsPii { get; } - - /// - /// Deserialize an IppAttribute. - /// - public static async Task DeserializeIppAttributeAsync(IPPInputStream input, CancellationToken cancellationToken) - { - var returnedTuple = await IppAttribute.DeserializeIppAttributeAsync(input, cancellationToken, Tag.Reserved, string.Empty); - return returnedTuple.Item1; - } - - /// - /// Deserialize an IppAttribute. - /// Return one attribute and the next attribute tag and name. - /// See: https://tools.ietf.org/html/rfc8010#section-3.1.6. - /// - public static async Task> DeserializeIppAttributeAsync( - IPPInputStream input, - CancellationToken cancellationToken, - Tag nextTag = Tag.Reserved, - string nextAttributeName = null) - { - Tag tag = nextTag == Tag.Reserved ? await input.ReadTagAsync(cancellationToken) : nextTag; - - string attributeName; - IppAttribute newAttribute = null; - - // Is there an attribute here? - if (!Helpers.IsValueTag(tag)) - { - // Next object is not an attribute. - return new Tuple(null, Tag.Reserved, string.Empty); - } - - // Parse the attribute name if nextAttributeName is not given. - if (string.IsNullOrEmpty(nextAttributeName)) - { - var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); - if (attributeNameLength <= 0) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, $"DeserializeIppAttributeAsync got name-length of {attributeNameLength} for first value of an attribute."); - } - - attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); - } - else - { - attributeName = nextAttributeName; - } - - // Handle collection attribute. - if (Helpers.IsBeginCollectionValueTag(tag)) - { - while (Helpers.IsBeginCollectionValueTag(tag)) - { - if (newAttribute == null) - { - // 2 bytes for value length. - var valueLength = await input.ReadNetworkShortAsync(cancellationToken); - - // The value-length of a collection attribute must be zero. - if (valueLength != 0) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"The value-length of a collection attribute must be zero, instead of {valueLength}")); - } - - var ippValue = await DeserializeCollectionAttributeValueAsync(input, attributeName, cancellationToken); - newAttribute = new IppAttribute(attributeName, ippValue); - } - else - { - // Expect either an additional value (the attribute name length is 0) or a new attribute (a new attribute name is parsed). - // If a new attribute name is parsed, quit the loop. Otherwise add the new value. - var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); - - if (attributeNameLength != 0) - { - attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); - - // Quit the loop, one full attribute is parsed. - break; - } - - // 2 bytes for value length. - var valueLength = await input.ReadNetworkShortAsync(cancellationToken); - - // The value-length of a collection attribute must be zero. - if (valueLength != 0) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"The value-length of a collection attribute must be zero, instead of {valueLength}")); - } - - // This is the case for 1setof CollectionAttribute. (Multi-valued collection attribute) - var additionalCollectionAttributeValue = await DeserializeCollectionAttributeValueAsync(input, string.Empty, cancellationToken); - newAttribute.AddAdditionalValue(additionalCollectionAttributeValue); - } - - attributeName = string.Empty; - tag = await input.ReadTagAsync(cancellationToken); - } - } - else - { - // Loop until 1 complete IppAttribute object is parsed. - while (Helpers.IsValueTag(tag)) - { - if (newAttribute == null) - { - // Parse attribute name. - if (string.IsNullOrEmpty(attributeName)) - { - var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); - if (attributeNameLength == 0) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "DeserializeIppAttributeAsync got name-length of 0 for first value of an attribute."); - } - - attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); - } - - newAttribute = new IppAttribute(attributeName); - attributeName = string.Empty; - } - else - { - // Expect either an additional value (the attribute name length is 0) or a new attribute (a new attribute name is parsed). - // If a new attribute name is parsed, quit the loop. Otherwise add the new value. - var attributeNameLength = await input.ReadNetworkShortAsync(cancellationToken); - - if (attributeNameLength != 0) - { - attributeName = await input.ReadStringAsync(attributeNameLength, cancellationToken); - - // Quit the loop, one full attribute is parsed. - break; - } - } - - // 2 bytes for value length. - var valueLength = await input.ReadNetworkShortAsync(cancellationToken); - - // Parse either attribute with one value or with additional value. - // https://tools.ietf.org/html/rfc8010#section-3.1.4. - var value = await input.ReadAsync(valueLength, cancellationToken); - - if (tag == Tag.TypeExtension) - { - // For extended types, the first 4 bytes of the value indicate the extended type as a big-endian signed integer. - // Ensure we read at least 4 bytes into the value. - if (valueLength < 4) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Expected at least 4 bytes in value data for extended type instead of {valueLength}.")); - } - - var extendedType = Helpers.ByteArrayToInteger(value); - tag = (Tag)extendedType; - } - - // An additional value for the attribue. - newAttribute.AddAdditionalValue(new IppValue(tag, value)); - - // Read the next delimiter. - tag = await input.ReadTagAsync(cancellationToken); - } - } - - // An attribute is formed. - var returnValue = new Tuple(newAttribute, tag, attributeName); - return returnValue; - } - - /// - /// Add additional value to the attribute. - /// - /// The value to add. - public void AddAdditionalValue(IppValue newValue) - { - // If first value, just add it. And determine whether this is a collection attribute. - if (this.IsEmptyAttribute) - { - this.values.Add(newValue); - - // If the value is a collection attribute value, then this is a collection attribute. - this.IsCollectionAttribute = newValue.IsCollectionAttributeValue(); - } - else - { - // Not first value. - // If adding a collection attribute value, this attribute must be a collection attribute. - // If adding a normal value, this attribute must not be a collection attribute. - if (newValue.IsCollectionAttributeValue() && !this.IsCollectionAttribute) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Collection attribute value cannot be added to a non collecton attribute {this.ValueName}.")); - } - else if (!newValue.IsCollectionAttributeValue() && this.IsCollectionAttribute) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Only collection attribute values can be added to a collecton attribute {this.ValueName}.")); - } - - this.values.Add(newValue); - } - } - - /// - /// Serialize this attribute. - /// - /// The output stream. - public void Serialize(Stream output) - { - // We write a single "attribute-with-one-value" field - // and then the "additional-value" fields if there are any - for (var i = 0; i < this.values.Count; i++) - { - if (i == 0) - { - this.values[i].Serialize(output, this.ValueName); - } - else - { - this.values[i].Serialize(output); - } - } - } - - /// - /// Serialize the attribute to string, value is masked if it's a Pii attribute. - /// - /// String representation of the attribute. - public override string ToString() - { - return this.ToString(false); - } - - /// - /// Non override version that allows Pii attributes to be included. - /// See IsPiiAttribute() for a list of attributes that are considered Pii. - /// - public string ToString(bool includePiiAttributes) - { - const string PiiMask = "*** PII masked ***"; - if (this.IsPii && !includePiiAttributes) - { - // Mask the value of Pii attributes. - return string.Format(CultureInfo.InvariantCulture, "Attribute {0}: {1}", this.ValueName, PiiMask); - } - - if (this.HasAdditionalValues) - { - var sb = new StringBuilder(); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Attribute {0} - Multiple Values:\n", this.ValueName); - - foreach (var value in this.values) - { - sb.Append(value.ToString()); - } - - return sb.ToString(); - } - else - { - return string.Format(CultureInfo.InvariantCulture, "Attribute {0}: {1}", this.ValueName, this.FirstValue != null ? this.FirstValue.ToString() : string.Empty); - } - } - - /// - /// Determines if this attribute is handled by UP. Will be false for custom attributes. - /// - public bool IsUPHandledUpdateOutputDeviceAttribute() - { - switch (this.ValueName) - { - case PrinterAttributes.CharsetConfigured: - case PrinterAttributes.CharsetSupported: - case PrinterAttributes.ColorModeDefault: - case PrinterAttributes.ColorModeSupported: - case PrinterAttributes.ColorSupported: - case PrinterAttributes.CompressionSupported: - case PrinterAttributes.CopiesDefault: - case PrinterAttributes.CopiesSupported: - case PrinterAttributes.DocumentAccessSupported: - case PrinterAttributes.DocumentFormatDefault: - case PrinterAttributes.DocumentFormatDetailsSupported: - case PrinterAttributes.DocumentFormatPreferred: - case PrinterAttributes.DocumentFormatSupported: - case PrinterAttributes.DocumentPasswordSupported: - case PrinterAttributes.FeedOrientationDefault: - case PrinterAttributes.FeedOrientationSupported: - case PrinterAttributes.FinishingsColDatabase: - case PrinterAttributes.FinishingsColDefault: - case PrinterAttributes.FinishingsColReady: - case PrinterAttributes.FinishingsColSupported: - case PrinterAttributes.FinishingsDefault: - case PrinterAttributes.FinishingsSupported: - case PrinterAttributes.GeneratedNaturalLanguageSupported: - case PrinterAttributes.IppFeaturesSupported: - case PrinterAttributes.IppVersionsSupported: - case PrinterAttributes.JobConstraintsSupported: - case PrinterAttributes.JobCreationAttributesSupported: - case PrinterAttributes.JobIdsSupported: - case PrinterAttributes.JobResolversSupported: - case PrinterAttributes.JobAccountIdDefault: - case PrinterAttributes.JobAccountIdSupported: - case PrinterAttributes.JobAccountingUserIdDefault: - case PrinterAttributes.JobAccountingUserIdSupported: - case PrinterAttributes.JobMandatoryAttributesSupported: - case PrinterAttributes.JobPasswordSupported: - case PrinterAttributes.JobPasswordEncryptionSupported: - case PrinterAttributes.JobPasswordLengthSupported: - case PrinterAttributes.JobReleaseActionDefault: - case PrinterAttributes.JobReleaseActionSupported: - case PrinterAttributes.JobSheetsDefault: - case PrinterAttributes.JobSheetsSupported: - case PrinterAttributes.LandscapeOrientationRequestedPreferred: - case PrinterAttributes.MarginsPreAppliedDefault: - case PrinterAttributes.MarginsPreAppliedSupported: - case PrinterAttributes.MediaBottomMarginSupported: - case PrinterAttributes.MediaLeftMarginSupported: - case PrinterAttributes.MediaRightMarginSupported: - case PrinterAttributes.MediaTopMarginSupported: - case PrinterAttributes.MediaColDatabase: - case PrinterAttributes.MediaColDefault: - case PrinterAttributes.MediaColReady: - case PrinterAttributes.MediaColSupported: - case PrinterAttributes.MediaDefault: - case PrinterAttributes.MediaReady: - case PrinterAttributes.MediaSizeSupported: - case PrinterAttributes.MediaSourceSupported: - case PrinterAttributes.MediaSupported: - case PrinterAttributes.MediaTypeSupported: - case PrinterAttributes.MediaColorSupported: - case PrinterAttributes.MicrosoftPageOrderDefault: - case PrinterAttributes.MicrosoftPageOrderSupported: - case PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion: - case PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem: - case PrinterAttributes.MicrosoftUniversalPrintConnectorId: - case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: - case PrinterAttributes.MicrosoftUniversalPrinterDriverName: - case PrinterAttributes.MicrosoftUniversalPrinterDriverVersion: - case PrinterAttributes.MopriaCertified: - case PrinterAttributes.MultipleDocumentHandlingDefault: - case PrinterAttributes.MultipleDocumentHandlingSupported: - case PrinterAttributes.MultipleDocumentJobsSupported: - case PrinterAttributes.MultipleOperationTimeout: - case PrinterAttributes.MultipleOperationTimeoutAction: - case PrinterAttributes.NaturalLanguageConfigured: - case PrinterAttributes.NumberUpDefault: - case PrinterAttributes.NumberUpSupported: - case PrinterAttributes.OperationsSupported: - case PrinterAttributes.OrientationRequestedDefault: - case PrinterAttributes.OrientationRequestedSupported: - case PrinterAttributes.OutputBinDefault: - case PrinterAttributes.OutputBinSupported: - case PrinterAttributes.OverridesSupported: - case PrinterAttributes.PageRangesSupported: - case PrinterAttributes.PagesPerMinute: - case PrinterAttributes.PagesPerMinuteColor: - case PrinterAttributes.PclmRasterBackSide: - case PrinterAttributes.PclmSourceResolutionSupported: - case PrinterAttributes.PclmStripHeightPreferred: - case PrinterAttributes.PclmStripHeightSupported: - case PrinterAttributes.PdlOverrideSupported: - case PrinterAttributes.PdfFitToPageDefault: - case PrinterAttributes.PdfFitToPageSupported: - case PrinterAttributes.PdfSizeConstraints: - case PrinterAttributes.PdfKOctetsSupported: - case PrinterAttributes.PdfVersionsSupported: - case PrinterAttributes.PrintContentOptimizeDefault: - case PrinterAttributes.PrintContentOptimizeSupported: - case PrinterAttributes.PresentationDirectionNumberUpDefault: - case PrinterAttributes.PresentationDirectionNumberUpSupported: - case PrinterAttributes.PrintDeviceCapabilities: - case PrinterAttributes.PrintQualityDefault: - case PrinterAttributes.PrintQualitySupported: - case PrinterAttributes.PrintRenderingIntentDefault: - case PrinterAttributes.PrintRenderingIntentSupported: - case PrinterAttributes.PrintScalingDefault: - case PrinterAttributes.PrintScalingSupported: - case PrinterAttributes.PrintWFDS: - case PrinterAttributes.PrinterAlert: - case PrinterAttributes.PrinterAlertDescription: - case PrinterAttributes.PrinterConfigChangeDateTime: - case PrinterAttributes.PrinterConfigChangeTime: - case PrinterAttributes.PrinterCurrentTime: - case PrinterAttributes.PrinterDeviceId: - case PrinterAttributes.PrinterFirmwareName: - case PrinterAttributes.PrinterFirmwarePatches: - case PrinterAttributes.PrinterFirmwareStringVersion: - case PrinterAttributes.PrinterFirmwareVersion: - case PrinterAttributes.PrinterGeoLocation: - case PrinterAttributes.PrinterGetAttributesSupported: - case PrinterAttributes.PrinterIccProfiles: - case PrinterAttributes.PrinterIcons: - case PrinterAttributes.PrinterInfo: - case PrinterAttributes.PrinterInputTray: - case PrinterAttributes.PrinterIsAcceptingJobs: - case PrinterAttributes.PrinterKind: - case PrinterAttributes.PrinterLocation: - case PrinterAttributes.PrinterMakeAndModel: - case PrinterAttributes.PrinterMoreInfo: - case PrinterAttributes.PrinterMoreInfoManufacturer: - case PrinterAttributes.PrinterName: - case PrinterAttributes.PrinterOrganization: - case PrinterAttributes.PrinterOrganizationalUnit: - case PrinterAttributes.PrinterOutputTray: - case PrinterAttributes.PrinterResolutionDefault: - case PrinterAttributes.PrinterResolutionSupported: - case PrinterAttributes.PrinterStaticResourceDirectoryUri: - case PrinterAttributes.PrinterStaticResourceKOctetsFree: - case PrinterAttributes.PrinterStaticResourceKOctetsSupported: - case PrinterAttributes.PrinterState: - case PrinterAttributes.PrinterStateMessage: - case PrinterAttributes.PrinterStateReasons: - case PrinterAttributes.PrinterStateChangeDateTime: - case PrinterAttributes.PrinterStateChangeTime: - case PrinterAttributes.PrinterSupply: - case PrinterAttributes.PrinterSupplyDescription: - case PrinterAttributes.PrinterSupplyInfoUri: - case PrinterAttributes.PrinterUpTime: - case PrinterAttributes.PrinterUriSupported: - case PrinterAttributes.PrinterUuid: - case PrinterAttributes.PwgRasterDocumentResolutionSupported: - case PrinterAttributes.PwgRasterDocumentSheetBack: - case PrinterAttributes.PwgRasterDocumentTypeSupported: - case PrinterAttributes.QueuedJobCount: - case PrinterAttributes.SidesDefault: - case PrinterAttributes.SidesSupported: - case PrinterAttributes.UrfSupported: - case PrinterAttributes.UriAuthenticationSupported: - case PrinterAttributes.UriSecuritySupported: - case PrinterAttributes.WhichJobsSupported: - case PrinterAttributes.PullPrintEnabledWithOEMJobRelease: - return true; - - // CUPS IPP Attributes (For Mopria 2.0) - case PrinterAttributes.MarkerColors: - case PrinterAttributes.MarkerHighLevels: - case PrinterAttributes.MarkerLevels: - case PrinterAttributes.MarkerLowLevels: - case PrinterAttributes.MarkerNames: - case PrinterAttributes.MarkerTypes: - return true; - default: - return false; - } - } - - /// - /// Validates the syntax of each attribute value, as discussed in RFC 3196 Section 3.1.2.1.5. - /// - /// - /// The length of each value is correct for the client-supplied syntax tag. - /// The syntax tag of each value is correct for the attribute. - /// The value is in the range specified for the attribute. - /// Multiple values are present only if the attribute supports multiple values. - /// - /// true if valid, false otherwise - public bool IsValidAttribute() - { - IList validTags = this.GetAttributeTags(this.ValueName); - foreach (var value in this.Values) - { - if (!validTags.Contains(value.ValueType)) - { - return false; - } - } - - if (!this.SupportsMultipleValues() && this.HasAdditionalValues) - { - return false; - } - - return true; - } - - /// - /// Filters this attribute's values to only those that the IPP Service supports. - /// - /// - /// For example, if the attribute name is 'printer-state-reasons' and the values are - /// 'none-report' and 'UNSUPPORTED-REPORT', then only 'none-report' is returned. - /// - /// supported values - public List GetSupportedValues() - { - return this.Values.Where(v => this.SupportsValue(v)).ToList(); - } - - /// - /// Filters this attribute's values to only those that the IPP Service does not support. - /// - /// - /// For example, if the attribute name is 'printer-state-reasons' and the values are - /// 'none-report' and 'UNSUPPORTED-REPORT', then only 'UNSUPPORTED-REPORT' is returned. - /// - /// unsupported values - public List GetUnsupportedValues() - { - return this.Values.Where(v => !this.SupportsValue(v)).ToList(); - } - - /// - /// Compare two attributes. - /// - public override bool Equals(object obj) - { - if (obj is IppAttribute other) - { - var isAttributeNameEqual = this.ValueName.Equals(other.ValueName, StringComparison.Ordinal); - var areValuesEqual = this.values.SequenceEqual(other.values); - return isAttributeNameEqual && areValuesEqual; - } - - return false; - } - - /// - /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. - /// Attribute comparison is used by test code. - /// - public override int GetHashCode() - { - return base.GetHashCode(); - } - - /// - /// Deserialize the input stream to a collection attribute. - /// - private static async Task DeserializeCollectionAttributeValueAsync(IPPInputStream input, string attributeName, CancellationToken cancellationToken, int collectionDepth = 0) - { - const int MaxCollectionDepthAllowed = 5; - - // Limit how many layers of collection of collection are allowed. - if (collectionDepth++ > MaxCollectionDepthAllowed) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Max collection depth of {MaxCollectionDepthAllowed} reached.")); - } - - var ippValue = IppValue.CreateCollectionAttributeValue(); - - IppMemberAttribute lastMemberAttribute = null; - - // Deserialize the member attributes for this collection attribute. - var tag = await input.ReadTagAsync(cancellationToken); - while (Helpers.IsMemberAttrNameTag(tag)) - { - // Parse 1 or more member attributes. https://tools.ietf.org/html/rfc8010#section-3.1.7 - // 2 bytes for name-length and it must be zero to signify that this is a member attribute. - var length = await input.ReadNetworkShortAsync(cancellationToken); - if (length != 0) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"The name-length of the member attribute must be zero, instead of {length}")); - } - - // 2 bytes for the value-length of the member-name. - length = await input.ReadNetworkShortAsync(cancellationToken); - var memberAttributeName = await input.ReadStringAsync(length, cancellationToken); - - // 1 byte for member-value tag. - var memberValueTag = await input.ReadTagAsync(cancellationToken); - - // 2 bytes for second name-length, value must be zero to signify it is a "member-attribute" contained in the collection. - length = await input.ReadNetworkShortAsync(cancellationToken); - if (length != 0) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"The second name-length of the member attribute must be zero, instead of {length}")); - } - - // 2 bytes for member-value-length, followed by the member-value. - length = await input.ReadNetworkShortAsync(cancellationToken); - IppValue memberIppValue = null; - - if (memberValueTag == Tag.BegCollection) - { - // A collection attribute. - if (length != 0) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"A member attribute that is a collection must have zero member-value-length instead of {length}.")); - } - - memberIppValue = await DeserializeCollectionAttributeValueAsync(input, memberAttributeName, cancellationToken, collectionDepth); - } - else - { - // A member attribute. Read the value and add it to the attribute. - var memberValue = await input.ReadAsync(length, cancellationToken); - memberIppValue = new IppValue(memberValueTag, memberValue); - } - - // If there is no member attribute name, then it is an additional value for a member attribute. - // This is the case for a member attribute with multiple values. - if (string.IsNullOrEmpty(memberAttributeName)) - { - if (lastMemberAttribute == null) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "A member attribute value should have a name or should be an additional value."); - } - - // Unnamed member attribute, hence adding this value to the last named member attribute. - lastMemberAttribute.AddAdditionalValue(memberIppValue); - } - else - { - // Named attribute value. Hence a new member attribute is being created. - lastMemberAttribute = new IppMemberAttribute(memberAttributeName, memberIppValue); - ippValue.AddMemberAttribute(lastMemberAttribute); - } - - // Read the next tag. - tag = await input.ReadTagAsync(cancellationToken); - } - - // Following the member attributes is the end collection tag. - if (tag != Tag.EndCollection) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Expect end-value-tag of a collection attribute. Received: {tag}")); - } - - // Followed by end-name-length field (2 bytes) and must be zero. - var endNameLength = await input.ReadNetworkShortAsync(cancellationToken); - if (endNameLength != 0) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Invalid value: {endNameLength}. The end-name-length of a collection attribute must be zero.")); - } - - // Followed by end-value-length field (2 bytes) and must be zero. - var endValueLength = await input.ReadNetworkShortAsync(cancellationToken); - if (endValueLength != 0) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Invalid value: {endValueLength}. The end-value-length of a collection attribute must be zero.")); - } - - return ippValue; - } - - /// - /// Returns the supported attribute syntax tags for the given attribute. - /// - /// The attribute - /// Returns supported atttribute syntax tags, or none if the attribute is unknown. - private List GetAttributeTags(string attributeName) - { - switch (attributeName) - { - // text - case PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion: - case PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem: - case PrinterAttributes.MicrosoftUniversalPrintConnectorId: - case PrinterAttributes.MicrosoftUniversalPrinterDriverName: - case PrinterAttributes.MicrosoftUniversalPrinterDriverVersion: - case PrinterAttributes.MopriaCertified: - case PrinterAttributes.PrinterAlertDescription: - case PrinterAttributes.PrinterInfo: - case PrinterAttributes.PrinterLocation: - case PrinterAttributes.PrinterMakeAndModel: - case PrinterAttributes.PrinterDeviceId: - case PrinterAttributes.PrinterStateMessage: - case PrinterAttributes.PrintDeviceCapabilities: - case PrinterAttributes.PrinterFirmwarePatches: - case PrinterAttributes.PrinterFirmwareStringVersion: - case PrinterAttributes.PrinterOrganization: - case PrinterAttributes.PrinterOrganizationalUnit: - case PrinterAttributes.PrinterSupplyDescription: - case JobAttributes.DetailedStatusMessage: - case JobAttributes.DocumentAccessError: - case JobAttributes.DocumentFormatVersionSupplied: - case JobAttributes.JobMessageFromOperator: - case JobAttributes.JobReleaseActionId: - case JobAttributes.JobStateMessage: - case JobAttributes.MediaInfo: - case JobAttributes.MicrosoftOutputDeviceJobStateMessage: - case JobAttributes.MicrosoftPrintTicketGenerationMappings: - case JobAttributes.OutputDeviceAssigned: - case JobAttributes.OutputDeviceJobStateMessage: - return new List { Tag.TextWithoutLanguage, Tag.TextWithLanguage }; - - // text without language - case PrinterAttributes.PrintWFDS: - return new List { Tag.TextWithoutLanguage }; - - // name - case PrinterAttributes.MarkerColors: - case PrinterAttributes.MarkerNames: - case PrinterAttributes.JobAccountIdDefault: - case PrinterAttributes.JobAccountingUserIdDefault: - case PrinterAttributes.PrinterFirmwareName: - case PrinterAttributes.PrinterName: - case JobAttributes.DocumentNameSupplied: - case JobAttributes.JobAccountId: - case JobAttributes.JobAccountingUserId: - case JobAttributes.JobName: - case JobAttributes.JobOriginatingUserName: - return new List { Tag.NameWithoutLanguage, Tag.NameWithLanguage }; - - // keyword - case PrinterAttributes.ColorModeDefault: - case PrinterAttributes.ColorModeSupported: - case PrinterAttributes.CompressionSupported: - case PrinterAttributes.DocumentAccessSupported: - case PrinterAttributes.DocumentFormatDetailsSupported: - case PrinterAttributes.FeedOrientationDefault: - case PrinterAttributes.FeedOrientationSupported: - case PrinterAttributes.FinishingsColSupported: - case PrinterAttributes.IppFeaturesSupported: - case PrinterAttributes.IppVersionsSupported: - case PrinterAttributes.JobCreationAttributesSupported: - case PrinterAttributes.MarkerTypes: - case PrinterAttributes.MediaColSupported: - case PrinterAttributes.MicrosoftPageOrderDefault: - case PrinterAttributes.MicrosoftPageOrderSupported: - case PrinterAttributes.MultipleDocumentHandlingDefault: - case PrinterAttributes.MultipleDocumentHandlingSupported: - case PrinterAttributes.MultipleOperationTimeoutAction: - case PrinterAttributes.OutputBinDefault: - case PrinterAttributes.OutputBinSupported: - case PrinterAttributes.OverridesSupported: - case PrinterAttributes.PclmRasterBackSide: - case PrinterAttributes.PdlOverrideSupported: - case PrinterAttributes.PdfVersionsSupported: - case PrinterAttributes.PresentationDirectionNumberUpDefault: - case PrinterAttributes.PresentationDirectionNumberUpSupported: - case PrinterAttributes.PrintScalingDefault: - case PrinterAttributes.PrintScalingSupported: - case PrinterAttributes.PrintContentOptimizeDefault: - case PrinterAttributes.PrintContentOptimizeSupported: - case PrinterAttributes.PrintRenderingIntentDefault: - case PrinterAttributes.PrintRenderingIntentSupported: - case PrinterAttributes.PrinterGetAttributesSupported: - case PrinterAttributes.PrinterKind: - case PrinterAttributes.PrinterStateReasons: - case PrinterAttributes.PwgRasterDocumentSheetBack: - case PrinterAttributes.PwgRasterDocumentTypeSupported: - case PrinterAttributes.JobReleaseActionDefault: - case PrinterAttributes.JobReleaseActionSupported: - case PrinterAttributes.SidesSupported: - case PrinterAttributes.SidesDefault: - case PrinterAttributes.UrfSupported: - case PrinterAttributes.UriSecuritySupported: - case PrinterAttributes.UriAuthenticationSupported: - case PrinterAttributes.WhichJobsSupported: - case JobAttributes.CompressionSupplied: - case JobAttributes.CoverType: - case JobAttributes.FeedOrientation: - case JobAttributes.JobMandatoryAttributes: - case JobAttributes.JobReleaseAction: - case JobAttributes.JobReleaseActionActual: - case JobAttributes.JobStateReasons: - case JobAttributes.MediaType: - case JobAttributes.OrientationRequested: - case JobAttributes.OutputBin: - case JobAttributes.OutputDeviceJobStateReasons: - case JobAttributes.PageOrderReceived: - case JobAttributes.PresentationDirectionNumberUp: - case JobAttributes.PrintColorMode: - case JobAttributes.PrintColorModeActual: - case JobAttributes.PrintScaling: - case JobAttributes.PrintContentOptimize: - case JobAttributes.PrintRenderingIntent: - case JobAttributes.Sides: - case JobAttributes.SidesActual: - return new List { Tag.Keyword }; - - // enum - case PrinterAttributes.FinishingsDefault: - case PrinterAttributes.FinishingsSupported: - case PrinterAttributes.LandscapeOrientationRequestedPreferred: - case PrinterAttributes.OperationsSupported: - case PrinterAttributes.OrientationRequestedSupported: - case PrinterAttributes.PrintQualityDefault: - case PrinterAttributes.PrintQualitySupported: - case PrinterAttributes.PrinterState: - case JobAttributes.Finishings: - case JobAttributes.JobState: - case JobAttributes.OutputDeviceJobState: - case JobAttributes.OutputDeviceJobStates: - case JobAttributes.PrinterQuality: - case JobAttributes.PrintQuality: - return new List { Tag.Enum }; - - // uri - case PrinterAttributes.PrinterIcons: - case PrinterAttributes.PrinterMoreInfo: - case PrinterAttributes.PrinterMoreInfoManufacturer: - case PrinterAttributes.PrinterStaticResourceDirectoryUri: - case PrinterAttributes.PrinterSupplyInfoUri: - case PrinterAttributes.PrinterUriSupported: - case PrinterAttributes.PrinterUuid: - case JobAttributes.JobMoreInfo: - case JobAttributes.JobOriginatingUserUri: - case JobAttributes.JobPrinterUri: - case JobAttributes.JobUuid: - case JobAttributes.JobUri: - return new List { Tag.Uri }; - - // charset - case PrinterAttributes.CharsetConfigured: - case PrinterAttributes.CharsetSupported: - return new List { Tag.Charset }; - - // naturalLanguage - case PrinterAttributes.GeneratedNaturalLanguageSupported: - case PrinterAttributes.NaturalLanguageConfigured: - return new List { Tag.NaturalLanguage }; - - // mimeMediaType - case PrinterAttributes.DocumentFormatDefault: - case PrinterAttributes.DocumentFormatSupported: - case PrinterAttributes.DocumentFormatPreferred: - case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: - case JobAttributes.DocumentFormatSupplied: - return new List { Tag.MimeMediaType }; - - // octetString - case PrinterAttributes.PrinterAlert: - case PrinterAttributes.PrinterFirmwareVersion: - case PrinterAttributes.PrinterInputTray: - case PrinterAttributes.PrinterOutputTray: - case PrinterAttributes.PrinterSupply: - return new List { Tag.OctetString }; - - // boolean - case PrinterAttributes.ColorSupported: - case PrinterAttributes.JobIdsSupported: - case PrinterAttributes.JobAccountIdSupported: - case PrinterAttributes.JobAccountingUserIdSupported: - case PrinterAttributes.JobMandatoryAttributesSupported: - case PrinterAttributes.MarginsPreAppliedDefault: - case PrinterAttributes.MarginsPreAppliedSupported: - case PrinterAttributes.MultipleDocumentJobsSupported: - case PrinterAttributes.PageRangesSupported: - case PrinterAttributes.PdfFitToPageDefault: - case PrinterAttributes.PdfFitToPageSupported: - case PrinterAttributes.PrinterIsAcceptingJobs: - case JobAttributes.IppAttributeFidelity: - case PrinterAttributes.PullPrintEnabledWithOEMJobRelease: - return new List { Tag.Boolean }; - - // integer - case PrinterAttributes.CopiesDefault: - case PrinterAttributes.DocumentPasswordSupported: - case PrinterAttributes.IppgetEventLife: - case PrinterAttributes.MediaBottomMarginSupported: - case PrinterAttributes.MediaLeftMarginSupported: - case PrinterAttributes.MediaRightMarginSupported: - case PrinterAttributes.MediaTopMarginSupported: - case PrinterAttributes.MultipleOperationTimeout: - case PrinterAttributes.JobPasswordSupported: - case PrinterAttributes.MarkerHighLevels: - case PrinterAttributes.MarkerLevels: - case PrinterAttributes.MarkerLowLevels: - case PrinterAttributes.NumberUpDefault: - case PrinterAttributes.PagesPerMinute: - case PrinterAttributes.PagesPerMinuteColor: - case PrinterAttributes.PclmStripHeightPreferred: - case PrinterAttributes.PclmStripHeightSupported: - case PrinterAttributes.PdfSizeConstraints: - case PrinterAttributes.PrinterConfigChangeTime: - case PrinterAttributes.PrinterStateChangeTime: - case PrinterAttributes.PrinterStaticResourceKOctetsFree: - case PrinterAttributes.PrinterStaticResourceKOctetsSupported: - case PrinterAttributes.PrinterUpTime: - case PrinterAttributes.QueuedJobCount: - case JobAttributes.Copies: - case JobAttributes.CopiesActual: - case JobAttributes.JobId: - case JobAttributes.JobImpressions: - case JobAttributes.JobImpressionsCompleted: - case JobAttributes.JobKOctets: - case JobAttributes.JobKOctetsCompleted: - case JobAttributes.JobKOctetsProcessed: - case JobAttributes.JobMediaSheets: - case JobAttributes.JobMediaSheetsCompleted: - case JobAttributes.JobPriority: - case JobAttributes.JobPrinterUpTime: - case JobAttributes.MediaBottomMargin: - case JobAttributes.MediaHoleCount: - case JobAttributes.MediaLeftMargin: - case JobAttributes.MediaOrderCount: - case JobAttributes.MediaThickness: - case JobAttributes.MediaTopMargin: - case JobAttributes.MediaWeightMetric: - case JobAttributes.MicrosoftJobFetchedTimeInSeconds: - case JobAttributes.MicrosoftJobPrintedTimeInSeconds: - case JobAttributes.MicrosoftJobProcessedTimeInSeconds: - case JobAttributes.MicrosoftJobSpoolerTimeInSeconds: - case JobAttributes.MicrosoftJobPrinterTimeInSeconds: - case JobAttributes.MicrosoftPdfToXpsJobConversionTimeInMilliseconds: - case JobAttributes.MicrosoftPrintTicketGenerationTimeInMilliseconds: - case JobAttributes.NumberOfDocuments: - case JobAttributes.NumberOfInterveningJobs: - case JobAttributes.NumberUp: - case JobAttributes.JobPagesCompleted: - case JobAttributes.TimeAtCreation: - case JobAttributes.XDimension: - case JobAttributes.YDimension: - return new List { Tag.Integer }; - - // integer | no-value - case JobAttributes.TimeAtCompleted: - case JobAttributes.TimeAtProcessing: - return new List { Tag.Integer, Tag.NoValue }; - - // rangeOfInteger - case PrinterAttributes.CopiesSupported: - case PrinterAttributes.PdfKOctetsSupported: - case PrinterAttributes.JobPasswordLengthSupported: - case JobAttributes.PageRanges: - return new List { Tag.RangeOfInteger }; - - // resolution - case PrinterAttributes.PclmSourceResolutionSupported: - case PrinterAttributes.PrinterResolutionDefault: - case PrinterAttributes.PrinterResolutionSupported: - case PrinterAttributes.PwgRasterDocumentResolutionSupported: - case JobAttributes.PrinterResolution: - return new List { Tag.Resolution }; - - // collection - case PrinterAttributes.FinishingsColDatabase: - case PrinterAttributes.FinishingsColReady: - case PrinterAttributes.JobConstraintsSupported: - case PrinterAttributes.JobResolversSupported: - case PrinterAttributes.MediaColDatabase: - case PrinterAttributes.MediaColDefault: - case PrinterAttributes.MediaSizeSupported: - case PrinterAttributes.PrinterIccProfiles: - case JobAttributes.MediaCol: - case JobAttributes.Overrides: - return new List { Tag.BegCollection, Tag.EndCollection }; - - // datetime - case PrinterAttributes.PrinterConfigChangeDateTime: - case PrinterAttributes.PrinterStateChangeDateTime: - case JobAttributes.DateTimeAtCreation: - return new List { Tag.DateTime }; - - // datetime | no-value - case JobAttributes.DateTimeAtCompleted: - case JobAttributes.DateTimeAtProcessing: - return new List { Tag.DateTime, Tag.NoValue }; - - // datetime | unknown - case PrinterAttributes.PrinterCurrentTime: - return new List { Tag.DateTime, Tag.Unknown }; - - // collection | no-value - case PrinterAttributes.FinishingsColDefault: - case JobAttributes.FinishingsCol: - case PrinterAttributes.MediaColReady: - return new List { Tag.BegCollection, Tag.EndCollection, Tag.NoValue }; - - // collection | integer - case JobAttributes.MediaSize: - return new List { Tag.BegCollection, Tag.EndCollection, Tag.Integer }; - - // no-value | enum - case PrinterAttributes.OrientationRequestedDefault: - return new List { Tag.NoValue, Tag.Enum }; - - // keyword | name - case PrinterAttributes.JobPasswordEncryptionSupported: - case PrinterAttributes.JobSheetsDefault: - case PrinterAttributes.JobSheetsSupported: - case PrinterAttributes.MediaSupported: - case PrinterAttributes.MediaSourceSupported: - case PrinterAttributes.MediaTypeSupported: - case PrinterAttributes.MediaColorSupported: - case JobAttributes.JobHoldUntil: - case JobAttributes.JobSheets: - case JobAttributes.Media: - case JobAttributes.MediaBackCoating: - case JobAttributes.MediaColor: - case JobAttributes.MediaGrain: - case JobAttributes.MediaFrontCoating: - case JobAttributes.MediaKey: - case JobAttributes.MediaPreprinted: - case JobAttributes.MediaRecycled: - case JobAttributes.MediaRightMargin: - case JobAttributes.MediaSizeName: - case JobAttributes.MediaSource: - case JobAttributes.MediaTooth: - case JobAttributes.MultipleDocumentHandling: - return new List { Tag.Keyword, Tag.NameWithoutLanguage, Tag.NameWithLanguage }; - - // integer | rangeOfInteger - case PrinterAttributes.NumberUpSupported: - return new List { Tag.Integer, Tag.RangeOfInteger }; - - // no-value | keyword | name - case PrinterAttributes.MediaDefault: - case PrinterAttributes.MediaReady: - return new List { Tag.NoValue, Tag.Keyword, Tag.NameWithoutLanguage, Tag.NameWithLanguage }; - - // uri | unknown - case PrinterAttributes.PrinterGeoLocation: - return new List { Tag.Uri, Tag.Unknown }; - - default: - return new List { }; - } - } - - private bool SupportsMultipleValues() - { - switch (this.ValueName) - { - case PrinterAttributes.CompressionSupported: - case PrinterAttributes.ColorModeSupported: - case PrinterAttributes.PrinterStateReasons: - case PrinterAttributes.CharsetSupported: - case PrinterAttributes.DocumentAccessSupported: - case PrinterAttributes.DocumentFormatDetailsSupported: - case PrinterAttributes.DocumentFormatSupported: - case PrinterAttributes.FeedOrientationSupported: - case PrinterAttributes.FinishingsColDatabase: - case PrinterAttributes.FinishingsColDefault: - case PrinterAttributes.FinishingsColReady: - case PrinterAttributes.FinishingsColSupported: - case PrinterAttributes.FinishingsDefault: - case PrinterAttributes.FinishingsSupported: - case PrinterAttributes.GeneratedNaturalLanguageSupported: - case PrinterAttributes.IppFeaturesSupported: - case PrinterAttributes.IppVersionsSupported: - case PrinterAttributes.JobConstraintsSupported: - case PrinterAttributes.JobCreationAttributesSupported: - case PrinterAttributes.JobResolversSupported: - case PrinterAttributes.JobPasswordEncryptionSupported: - case PrinterAttributes.JobReleaseActionSupported: - case PrinterAttributes.JobSheetsSupported: - case PrinterAttributes.MarkerColors: - case PrinterAttributes.MarkerHighLevels: - case PrinterAttributes.MarkerLevels: - case PrinterAttributes.MarkerLowLevels: - case PrinterAttributes.MarkerNames: - case PrinterAttributes.MarkerTypes: - case PrinterAttributes.MediaColDatabase: - case PrinterAttributes.MediaColReady: - case PrinterAttributes.MediaColSupported: - case PrinterAttributes.MediaColorSupported: - case PrinterAttributes.MediaBottomMarginSupported: - case PrinterAttributes.MediaLeftMarginSupported: - case PrinterAttributes.MediaRightMarginSupported: - case PrinterAttributes.MediaTopMarginSupported: - case PrinterAttributes.MediaReady: - case PrinterAttributes.MediaSupported: - case PrinterAttributes.MediaSizeSupported: - case PrinterAttributes.MediaSourceSupported: - case PrinterAttributes.MediaTypeSupported: - case PrinterAttributes.MicrosoftPageOrderSupported: - case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: - case PrinterAttributes.MultipleDocumentHandlingSupported: - case PrinterAttributes.NumberUpSupported: - case PrinterAttributes.OperationsSupported: - case PrinterAttributes.OrientationRequestedSupported: - case PrinterAttributes.OutputBinSupported: - case PrinterAttributes.OverridesSupported: - case PrinterAttributes.PclmSourceResolutionSupported: - case PrinterAttributes.PclmStripHeightSupported: - case PrinterAttributes.PclmStripHeightPreferred: - case PrinterAttributes.PresentationDirectionNumberUpSupported: - case PrinterAttributes.PdfVersionsSupported: - case PrinterAttributes.PrintContentOptimizeSupported: - case PrinterAttributes.PrintQualitySupported: - case PrinterAttributes.PrintRenderingIntentSupported: - case PrinterAttributes.PrintScalingSupported: - case PrinterAttributes.PrinterAlert: - case PrinterAttributes.PrinterAlertDescription: - case PrinterAttributes.PrinterFirmwareName: - case PrinterAttributes.PrinterFirmwareStringVersion: - case PrinterAttributes.PrinterFirmwarePatches: - case PrinterAttributes.PrinterFirmwareVersion: - case PrinterAttributes.PrinterGetAttributesSupported: - case PrinterAttributes.PrinterIccProfiles: - case PrinterAttributes.PrinterIcons: - case PrinterAttributes.PrinterInputTray: - case PrinterAttributes.PrinterKind: - case PrinterAttributes.PrinterOrganization: - case PrinterAttributes.PrinterOrganizationalUnit: - case PrinterAttributes.PrinterOutputTray: - case PrinterAttributes.PrinterResolutionSupported: - case PrinterAttributes.PrinterSupply: - case PrinterAttributes.PrinterSupplyDescription: - case PrinterAttributes.PrinterUriSupported: - case PrinterAttributes.PwgRasterDocumentResolutionSupported: - case PrinterAttributes.PwgRasterDocumentTypeSupported: - case PrinterAttributes.SidesSupported: - case PrinterAttributes.UrfSupported: - case PrinterAttributes.UriAuthenticationSupported: - case PrinterAttributes.UriSecuritySupported: - case PrinterAttributes.WhichJobsSupported: - case JobAttributes.Finishings: - case JobAttributes.FinishingsCol: - case JobAttributes.JobStateReasons: - case JobAttributes.MicrosoftPrintTicketGenerationMappings: - case JobAttributes.OutputDeviceJobStateReasons: - case JobAttributes.OutputDeviceJobStates: - case JobAttributes.Overrides: - case JobAttributes.PageRanges: - return true; - - default: - return false; - } - } - - private bool SupportsValue(IppValue value) - { - switch (this.ValueName) - { - case PrinterAttributes.PrinterState: - { - var printerState = (PrinterState)value.GetNativeValue(); - return Enum.IsDefined(typeof(PrinterState), printerState); - } - - case PrinterAttributes.PrinterStateReasons: - case PrinterAttributes.PrinterMakeAndModel: - case PrinterAttributes.PrinterMoreInfoManufacturer: - { - var textValue = value.GetNativeValue(); - return !string.IsNullOrEmpty(textValue); - } - - case PrinterAttributes.IppgetEventLife: - { - var intValue = value.GetNativeValue(); - return intValue >= 15; - } - - case PrinterAttributes.CharsetConfigured: - case PrinterAttributes.CharsetSupported: - case PrinterAttributes.ColorModeDefault: - case PrinterAttributes.ColorModeSupported: - case PrinterAttributes.ColorSupported: - case PrinterAttributes.CompressionSupported: - case PrinterAttributes.CopiesDefault: - case PrinterAttributes.CopiesSupported: - case PrinterAttributes.DocumentAccessSupported: - case PrinterAttributes.DocumentFormatDefault: - case PrinterAttributes.DocumentFormatDetailsSupported: - case PrinterAttributes.DocumentFormatPreferred: - case PrinterAttributes.DocumentFormatSupported: - case PrinterAttributes.DocumentPasswordSupported: - case PrinterAttributes.FeedOrientationDefault: - case PrinterAttributes.FeedOrientationSupported: - case PrinterAttributes.FinishingsColDatabase: - case PrinterAttributes.FinishingsColDefault: - case PrinterAttributes.FinishingsColReady: - case PrinterAttributes.FinishingsColSupported: - case PrinterAttributes.FinishingsDefault: - case PrinterAttributes.FinishingsSupported: - case PrinterAttributes.GeneratedNaturalLanguageSupported: - case PrinterAttributes.IppFeaturesSupported: - case PrinterAttributes.IppVersionsSupported: - case PrinterAttributes.JobConstraintsSupported: - case PrinterAttributes.JobCreationAttributesSupported: - case PrinterAttributes.JobIdsSupported: - case PrinterAttributes.JobResolversSupported: - case PrinterAttributes.JobAccountIdDefault: - case PrinterAttributes.JobAccountIdSupported: - case PrinterAttributes.JobAccountingUserIdDefault: - case PrinterAttributes.JobAccountingUserIdSupported: - case PrinterAttributes.JobMandatoryAttributesSupported: - case PrinterAttributes.JobPasswordSupported: - case PrinterAttributes.JobPasswordEncryptionSupported: - case PrinterAttributes.JobPasswordLengthSupported: - case PrinterAttributes.JobReleaseActionDefault: - case PrinterAttributes.JobReleaseActionSupported: - case PrinterAttributes.JobSheetsDefault: - case PrinterAttributes.JobSheetsSupported: - case PrinterAttributes.LandscapeOrientationRequestedPreferred: - case PrinterAttributes.MarginsPreAppliedDefault: - case PrinterAttributes.MarginsPreAppliedSupported: - case PrinterAttributes.MarkerColors: - case PrinterAttributes.MarkerHighLevels: - case PrinterAttributes.MarkerLevels: - case PrinterAttributes.MarkerLowLevels: - case PrinterAttributes.MarkerNames: - case PrinterAttributes.MarkerTypes: - case PrinterAttributes.MediaColDatabase: - case PrinterAttributes.MediaColDefault: - case PrinterAttributes.MediaColReady: - case PrinterAttributes.MediaColSupported: - case PrinterAttributes.MediaBottomMarginSupported: - case PrinterAttributes.MediaLeftMarginSupported: - case PrinterAttributes.MediaRightMarginSupported: - case PrinterAttributes.MediaTopMarginSupported: - case PrinterAttributes.MediaDefault: - case PrinterAttributes.MediaReady: - case PrinterAttributes.MediaSizeSupported: - case PrinterAttributes.MediaSourceSupported: - case PrinterAttributes.MediaTypeSupported: - case PrinterAttributes.MediaColorSupported: - case PrinterAttributes.MediaSupported: - case PrinterAttributes.MicrosoftPageOrderDefault: - case PrinterAttributes.MicrosoftPageOrderSupported: - case PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion: - case PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem: - case PrinterAttributes.MicrosoftUniversalPrintConnectorId: - case PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion: - case PrinterAttributes.MicrosoftUniversalPrinterDriverName: - case PrinterAttributes.MicrosoftUniversalPrinterDriverVersion: - case PrinterAttributes.MopriaCertified: - case PrinterAttributes.MultipleDocumentHandlingDefault: - case PrinterAttributes.MultipleDocumentHandlingSupported: - case PrinterAttributes.MultipleDocumentJobsSupported: - case PrinterAttributes.MultipleOperationTimeout: - case PrinterAttributes.MultipleOperationTimeoutAction: - case PrinterAttributes.NaturalLanguageConfigured: - case PrinterAttributes.NumberUpDefault: - case PrinterAttributes.NumberUpSupported: - case PrinterAttributes.OperationsSupported: - case PrinterAttributes.OrientationRequestedDefault: - case PrinterAttributes.OrientationRequestedSupported: - case PrinterAttributes.OutputBinDefault: - case PrinterAttributes.OutputBinSupported: - case PrinterAttributes.OverridesSupported: - case PrinterAttributes.PageRangesSupported: - case PrinterAttributes.PagesPerMinute: - case PrinterAttributes.PagesPerMinuteColor: - case PrinterAttributes.PclmRasterBackSide: - case PrinterAttributes.PclmSourceResolutionSupported: - case PrinterAttributes.PclmStripHeightPreferred: - case PrinterAttributes.PclmStripHeightSupported: - case PrinterAttributes.PdlOverrideSupported: - case PrinterAttributes.PrintContentOptimizeDefault: - case PrinterAttributes.PrintContentOptimizeSupported: - case PrinterAttributes.PrintRenderingIntentDefault: - case PrinterAttributes.PrintRenderingIntentSupported: - case PrinterAttributes.PrintQualityDefault: - case PrinterAttributes.PrintScalingDefault: - case PrinterAttributes.PrintScalingSupported: - case PrinterAttributes.PdfFitToPageDefault: - case PrinterAttributes.PdfFitToPageSupported: - case PrinterAttributes.PdfKOctetsSupported: - case PrinterAttributes.PdfSizeConstraints: - case PrinterAttributes.PdfVersionsSupported: - case PrinterAttributes.PresentationDirectionNumberUpDefault: - case PrinterAttributes.PresentationDirectionNumberUpSupported: - case PrinterAttributes.PrintQualitySupported: - case PrinterAttributes.PrintWFDS: - case PrinterAttributes.PrinterAlert: - case PrinterAttributes.PrinterAlertDescription: - case PrinterAttributes.PrinterConfigChangeDateTime: - case PrinterAttributes.PrinterConfigChangeTime: - case PrinterAttributes.PrinterCurrentTime: - case PrinterAttributes.PrinterDeviceId: - case PrinterAttributes.PrinterFirmwareName: - case PrinterAttributes.PrinterFirmwarePatches: - case PrinterAttributes.PrinterFirmwareStringVersion: - case PrinterAttributes.PrinterFirmwareVersion: - case PrinterAttributes.PrinterGeoLocation: - case PrinterAttributes.PrinterGetAttributesSupported: - case PrinterAttributes.PrinterIccProfiles: - case PrinterAttributes.PrinterIcons: - case PrinterAttributes.PrinterInfo: - case PrinterAttributes.PrinterInputTray: - case PrinterAttributes.PrinterIsAcceptingJobs: - case PrinterAttributes.PrinterKind: - case PrinterAttributes.PrinterLocation: - case PrinterAttributes.PrinterMoreInfo: - case PrinterAttributes.PrinterName: - case PrinterAttributes.PrinterOrganization: - case PrinterAttributes.PrinterOrganizationalUnit: - case PrinterAttributes.PrinterOutputTray: - case PrinterAttributes.PrinterResolutionDefault: - case PrinterAttributes.PrinterResolutionSupported: - case PrinterAttributes.PrinterStateMessage: - case PrinterAttributes.PrinterStateChangeDateTime: - case PrinterAttributes.PrinterStateChangeTime: - case PrinterAttributes.PrinterStaticResourceDirectoryUri: - case PrinterAttributes.PrinterStaticResourceKOctetsFree: - case PrinterAttributes.PrinterStaticResourceKOctetsSupported: - case PrinterAttributes.PrinterSupply: - case PrinterAttributes.PrinterSupplyDescription: - case PrinterAttributes.PrinterSupplyInfoUri: - case PrinterAttributes.PrinterUpTime: - case PrinterAttributes.PrinterUriSupported: - case PrinterAttributes.PrinterUuid: - case PrinterAttributes.PwgRasterDocumentResolutionSupported: - case PrinterAttributes.PwgRasterDocumentSheetBack: - case PrinterAttributes.PwgRasterDocumentTypeSupported: - case PrinterAttributes.QueuedJobCount: - case PrinterAttributes.SidesDefault: - case PrinterAttributes.SidesSupported: - case PrinterAttributes.UrfSupported: - case PrinterAttributes.UriAuthenticationSupported: - case PrinterAttributes.UriSecuritySupported: - case PrinterAttributes.WhichJobsSupported: - case PrinterAttributes.PullPrintEnabledWithOEMJobRelease: - return true; - - default: - return false; - } - } - - /// - /// IPP attributes that are considered Pii. - /// - private bool IsPiiAttribute() - { - switch (this.ValueName) - { - case OperationAttributes.RequestingUserName: - case OperationAttributes.RequestingUserUri: - case OperationAttributes.DocumentName: - case OperationAttributes.JobPassword: - case JobAttributes.JobOriginatingUserName: - case JobAttributes.JobOriginatingUserUri: - case JobAttributes.JobName: - return true; - - default: - return false; - } - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs b/BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs deleted file mode 100644 index 22a110b..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPAttributeGroup.cs +++ /dev/null @@ -1,187 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using BadgeReleaseDemo.IppLibrary.Common; - - public class IppAttributeGroup - { - private const string LexmarkStringForDuplicateAttributeException = "Lexmark"; - - /// - /// Contains 0 or more IPPAttributes associated with this Attribute Group - /// TBD: consider ConcurrentDictionary() remove the need to create new APIs for Add, Remove, and Replace. [Tracked by: 19959905] - /// - private Dictionary attributes; - - /// - /// Initializes a new instance of the class. - /// - /// The attribute type of the group. - public IppAttributeGroup(Tag attributeType) - { - this.Type = attributeType; - this.attributes = new Dictionary(); - } - - /// - /// Gets or sets the type of this Attribute Group - /// - public Tag Type { get; set; } - - /// - /// Gets the attributes of the group. - /// - public IReadOnlyDictionary Attributes => (IReadOnlyDictionary)this.attributes; - - /// - /// Add an attribute to the group. - /// - /// The attribute to add. - public void AddAttribute(IppAttribute attribute) - { - // Sanity check: duplicate attributes are not allowed (we're allowed to ignore them - // or to return an error, I'm opting for an error..) - if (this.attributes.ContainsKey(attribute.ValueName)) - { - if (attribute.ValueName.Contains(LexmarkStringForDuplicateAttributeException)) - { - // skip attribute for Lexmark native printers due to bug in their firmware - // IcM: https://portal.microsofticm.com/imp/v3/incidents/details/264596539/home - return; - } - - throw new IPPException(StatusCode.ClientErrorBadRequest, "Duplicate attribute name in IPP message:" + attribute.ValueName); - } - - this.attributes.Add(attribute.ValueName, attribute); - } - - /// - /// Remove an attribute from attribute list. - /// - /// The name of the attribute to be removed. - public void RemoveAttribute(string attributeName) - { - if (this.attributes.ContainsKey(attributeName)) - { - this.attributes.Remove(attributeName); - } - } - - /// - /// Replace an attribute. - /// - /// The new attribute object. - public void ReplaceAttribute(IppAttribute attribute) - { - this.RemoveAttribute(attribute.ValueName); - this.AddAttribute(attribute); - } - - /// - /// Serialize the attribute group. - /// - /// The output stream. - public void Serialize(Stream output) - { - // Write the "begin-attribute-group" tag - output.WriteByte((byte)this.Type); - - // And write out the attributes. - // The requirement for Tag.OperationAttributes is to serialize the AttributesCharset and AttributesNaturalLanguage first. - // From RFC 8081: https://tools.ietf.org/html/rfc8011#page-33 - // The "attributes-charset" and "attributes-natural-language" attributes - // MUST be the first two attributes in every IPP request and response, - // as part of the initial Operation Attributes group of the IPP message. - // The "attributes-charset" attribute MUST be the first attribute in the - // group, and the "attributes-natural-language" attribute MUST be the - // second attribute in the group. - if (this.Type == Tag.OperationAttributes) - { - if (this.attributes.TryGetValue(OperationAttributes.AttributesCharset, out IppAttribute charsetValue)) - { - charsetValue.Serialize(output); - } - - if (this.attributes.TryGetValue(OperationAttributes.AttributesNaturalLanguage, out IppAttribute langValue)) - { - langValue.Serialize(output); - } - } - - // The rest of the attributes. - foreach (var attribute in this.attributes.Values) - { - // Except for the AttributesCharset and AttributesNaturalLanguage attributes that are serialized first. - if (string.CompareOrdinal(attribute.ValueName, OperationAttributes.AttributesCharset) == 0 || - string.CompareOrdinal(attribute.ValueName, OperationAttributes.AttributesNaturalLanguage) == 0) - { - continue; - } - - attribute.Serialize(output); - } - } - - /// - /// Serialize to string without IPP attributes that are considered Pii. - /// - /// The string representation of the attribute group. - public override string ToString() - { - // The default is to exclude Pii attributes. This prevents user of this library to accidentally log Pii information. - return this.ToString(false); - } - - /// - /// Serialize to string with option to include IPP attributes that are considered Pii. - /// - public string ToString(bool includePiiAttributes) - { - var sb = new StringBuilder(); - sb.AppendFormat(CultureInfo.InvariantCulture, "Attribute Group: {0}\n", this.Type); - - foreach (var attribute in this.attributes.Values) - { - sb.AppendFormat(CultureInfo.InvariantCulture, " {0}\n", attribute.ToString(includePiiAttributes)); - } - - return sb.ToString(); - } - - /// - /// Compare two attribute groups. - /// - public override bool Equals(object obj) - { - if (obj is IppAttributeGroup other) - { - var isAttributeGroupTypeEqual = this.Type.Equals(other.Type); - var areAttributesEqual = this.attributes.ToList().SequenceEqual(other.attributes.ToList()); - return isAttributeGroupTypeEqual && areAttributesEqual; - } - - return false; - } - - /// - /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. - /// Attribute group comparison is used by test code. - /// - public override int GetHashCode() - { - return base.GetHashCode(); - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs b/BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs deleted file mode 100644 index ad58c5b..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPDatatypes.cs +++ /dev/null @@ -1,1183 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- - -// These are all types/strings/etc defined in the IPP RFCs. Look there for info. -#pragma warning disable SA1602 // Enumeration items should be documented -#pragma warning disable SA1600 // elements should be documented - -namespace BadgeReleaseDemo.IppLibrary -{ - /// - /// From RFC 2911: - /// - /// 0x0000 reserved, not used - /// 0x0001 reserved, not used - /// 0x0002 Print-Job - /// 0x0003 Print-URI - /// 0x0004 Validate-Job - /// 0x0005 Create-Job - /// 0x0006 Send-Document - /// 0x0007 Send-URI - /// 0x0008 Cancel-Job - /// 0x0009 Get-Job-Attributes - /// 0x000A Get-Jobs - /// 0x000B Get-Printer-Attributes - /// 0x000C Hold-Job - /// 0x000D Release-Job - /// 0x000E Restart-Job - /// 0x000F reserved for a future operation - /// 0x0010 Pause-Printer - /// 0x0011 Resume-Printer - /// 0x0012 Purge-Jobs - /// - /// For IPP INFRA - /// From: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf (section 14.4) - /// - /// 0x003f Acknowledge-Document - /// 0x0040 Acknowledge-Identity-Printer - /// 0x0041 Acknowledge-Job - /// 0x0042 Fetch-Document - /// 0x0043 Fetch-Job - /// 0x0044 Get-Output-Device-Attributes - /// 0x0045 Update-Active-Jobs - /// 0x0046 Deregister-Output-Device - /// 0x0047 Update-Document-Status - /// 0x0048 Update-Job-Status - /// 0x0049 Update-Output-Device-Attributes - /// - /// For IPP Job Extensions v2.0 - /// From: https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext20-20190816-5100.7.pdf (section 4) - /// - /// 0x003A CloseJob - /// - /// 0x0013-0x3FFF reserved for future IETF standards track - /// operations (see section 6.4) - /// 0x4000-0x8FFF reserved for vendor extensions (see section 6.4) - /// - public enum Operation - { - // IPP - PrintJob = 0x2, - PrintUri = 0x3, - ValidateJob = 0x4, - CreateJob = 0x5, - SendDocument = 0x6, - SendUri = 0x7, - CancelJob = 0x8, - GetJobAttributes = 0x9, - GetJobs = 0xA, - GetPrinterAttributes = 0xB, - HoldJob = 0xC, - ReleaseJob = 0xD, - RestartJob = 0xE, - PausePrinter = 0x10, - ResumePrinter = 0x11, - PurgeJobs = 0x12, - - // Job/Printer Set operations https://tools.ietf.org/html/rfc3380#section-4 - SetPrinterAttributes = 0x13, - SetJobAttributes = 0x14, - GetPrinterSupportedValues = 0x15, - - // IPP INFRA (from: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 14.3). - CreatePrinterSubscriptions = 0x16, - CreateJobSubscriptions = 0x17, - GetSubscriptionAttributes = 0x18, - GetSubscriptions = 0x19, - RenewSubscription = 0x1A, - CancelSubscription = 0x1B, - GetNotifications = 0x1C, - AcknowledgeDocument = 0x3F, - AcknowledgeIdentityPrinter = 0x40, /* not used in IPP Infra */ - AcknowledgeJob = 0x41, - FetchDocument = 0x42, - FetchJob = 0x43, - GetOutputDeviceAttributes = 0x44, - UpdateActiveJobs = 0x45, - DeregisterOutputDevice = 0x46, - UpdateDocumentStatus = 0x47, - UpdateJobStatus = 0x48, - UpdateOutputDeviceAttributes = 0x49, - - // IPP Job Extensions v2.0 (from: https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext20-20190816-5100.7.pdf section 4). - CloseJob = 0x3A, - - // Custom Windows IPP Extensions for IPP. - GetPrintDeviceCapabilities = 0x5000, - GetPrintDeviceResources = 0x5001, - - // Custom Windows IPP Extensions for IPP Infra. - SetPrintDeviceCapabilities = 0x5100, - SetPrintCapabilities = 0x5101 - } - - public enum PrinterState - { - Idle = 3, - Processing = 4, - Stopped = 5, - } - - public enum Orientation - { - Portrait = 3, - Landscape = 4, - ReverseLandscape = 5, - ReversePortrait = 6, - None = 7 - } - - public enum JobState - { - Pending = 3, - PendingHeld = 4, - Processing = 5, - ProcessingStopped = 6, - Canceled = 7, - Aborted = 8, - Completed = 9 - } - - // RFC 8011 section 5.2.13. - public enum PrintQuality - { - Draft = 3, - Normal = 4, - High = 5 - } - - // https://tools.ietf.org/html/rfc8011#page-115 and PWG5100.1. - public enum Finishings - { - None = 3, - Staple = 4, - Punch = 5, - Cover = 6, - Bind = 7, - SaddleStitch = 8, - EdgeStitch = 9, - Fold = 10, - Trim = 11, - Bale = 12, - BookletMaker = 13, - JogOffset = 14, - Coat = 15, - Laminate = 16, - StapleTopLeft = 20, - StapleBottomLeft = 21, - StapleTopRight = 22, - StapleBottomRight = 23, - EdgeStitchLeft = 24, - EdgeStitchTop = 25, - EdgeStitchRight = 26, - EdgeStitchBottom = 27, - StapleDualLeft = 28, - StapleDualTop = 29, - StapleDualRight = 30, - StapleDualBottom = 31, - StapleTripleLeft = 32, - StapleTripleTop = 33, - StapleTripleRight = 34, - StapleTripleBottom = 35, - BindLeft = 50, - BindTop = 51, - BindRight = 52, - BindBottom = 53, - TrimAfterPages = 60, - TrimAfterDocuments = 61, - TrimAfterCopies = 62, - TrimAfterJob = 63, - PunchTopLeft = 70, - PunchBottomLeft = 71, - PunchTopRight = 72, - PunchBottomRight = 73, - PunchDualLeft = 74, - PunchDualTop = 75, - PunchDualRight = 76, - PunchDualBottom = 77, - PunchTripleLeft = 78, - PunchTripleTop = 79, - PunchTripleRight = 80, - PunchTripleBottom = 81, - PunchQuadLeft = 82, - PunchQuadTop = 83, - PunchQuadRight = 84, - PunchQuadBottom = 85, - PunchMultipleLeft = 86, - PunchMultipleTop = 87, - PunchMultipleRight = 88, - PunchMultipleBottom = 89, - FoldAccordion = 90, - FoldDoubleGate = 91, - FoldGate = 92, - FoldHalf = 93, - FoldHalfZ = 94, - FoldLeftGate = 95, - FoldLetter = 96, - FoldParallel = 97, - FoldPoster = 98, - FoldRightGate = 99, - FoldZ = 100, - FoldEngineeringZ = 101, - } - - /// - /// Describes the standard IPP status codes - /// - public enum StatusCode - { - // Success codes (0x0000-0x00ff) - SuccessfulOk = 0x0000, - SuccessfulOkIgnoredOrSubstitutedAttributes = 0x0001, - SuccessfulOkConflictingAttributes = 0x0002, - SuccessfulOkIgnoredSubscriptions = 0x0003, - SuccessfulOkTooManyEvents = 0x0005, - SuccessfulOkMax = 0x00ff, - - // Client error status codes - // (used '[when] the client seems to have erred.') - ClientErrorBadRequest = 0x0400, - ClientErrorForbidden = 0x0401, - ClientErrorNotAuthenticated = 0x0402, - ClientErrorNotAuthorized = 0x0403, - ClientErrorNotPossible = 0x0404, - ClientErrorTimeout = 0x0405, - ClientErrorNotFound = 0x0406, - ClientErrorGone = 0x0407, - ClientErrorRequestEntityTooLarge = 0x0408, - ClientErrorRequestValueTooLong = 0x0409, - ClientErrorDocumentFormatNotSupported = 0x040a, - ClientErrorAttributesOrValuesNotSupported = 0x040b, - ClientErrorUriSchemeNotSupported = 0x040c, - ClientErrorCharsetNotSupported = 0x040d, - ClientErrorConflictingAttributes = 0x040e, - ClientErrorCompressionNotSupported = 0x040f, - ClientErrorCompressionError = 0x0410, - ClientErrorDocumentFormatError = 0x0411, - ClientErrorDocumentAccessError = 0x0412, - ClientErrorIgnoredAllSubscriptions = 0x0414, - ClientErrorTooManySubscriptions = 0x0415, - - // Server error status codes - // (used '[when] the IPP object is aware that it has erred or is incapable of performing the request.') - ServerErrorInternalError = 0x0500, - ServerErrorOperationNotSupported = 0x0501, - ServerErrorServiceUnavailable = 0x0502, - ServerErrorVersionNotSupported = 0x0503, - ServerErrorDeviceError = 0x0504, - ServerErrorTemporaryError = 0x0505, - ServerErrorNotAcceptingJobs = 0x0506, - ServerErrorBusy = 0x0507, - ServerErrorJobCanceled = 0x0508, - ServerErrorMultipleDocumentJobsNotSupported = 0x0509, - - // Note: this is only for internal consumption when status code is unknown. - Undefined = 0xffff - } - - // Section 13.3.1 of AirPrint Specification Version 2.1.1 (not publicly available, see team sharepoint) - public enum LandscapeOrientationRequestedPreferred - { - NinetyDegreesCounterClockwise = 4, - NinetyDegreesClockwise = 5 - } - - // RFC 8011 section 5.2.8: sides. - public struct Sides - { - public const string OneSided = "one-sided"; - public const string TwoSidedShortEdge = "two-sided-short-edge"; - public const string TwoSidedLongEdge = "two-sided-long-edge"; - } - - // PWG 5100.13 print-color-mode. - public struct ColorModes - { - public const string Auto = "auto"; - public const string BiLevel = "bi-level"; - public const string Color = "color"; - public const string Highlight = "highlight"; - public const string Monochrome = "monochrome"; - public const string ProcessBiLevel = "process-bi-level"; - public const string ProcessMonochrome = "process-monochrome"; - public const string MicrosoftGrayscaleOnly = "microsoft-grayscale-only"; // Custom. Used for grayscale-only Connector printers to show only grayscale option in PDC and print dialog. - public const string MicrosoftMonochromeOnly = "microsoft-monochrome-only"; // Custom. Used for monochrome-only Connector printers to show only monochrome option in PDC and print dialog. - } - - // PWG 5100.16 section 6.2.2. print-scaling values. - public struct PrintScaling - { - public const string Auto = "auto"; - public const string AutoFit = "auto-fit"; - public const string Fill = "fill"; - public const string Fit = "fit"; - public const string None = "none"; - } - - // Keywords for media-front-coating and media-back-coating. - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf. Section 3.13.10. - public struct MediaCoating - { - public const string None = "none"; - public const string Glossy = "glossy"; - public const string HighGloss = "high-gloss"; - public const string SemiGloss = "semi-gloss"; - public const string Satin = "satin"; - public const string Matte = "matte"; - } - - // Keywords for media-color. - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf. Section 3.13.4. - public struct MediaColors - { - public const string NoColor = "no-color"; - public const string White = "white"; - public const string Pink = "pink"; - public const string Yellow = "yellow"; - public const string Blue = "blue"; - public const string Green = "green"; - public const string Buff = "buff"; - public const string GoldenRod = "goldenrod"; - public const string Red = "red"; - public const string Gray = "gray"; - public const string Ivory = "ivory"; - public const string Orange = "orange"; - } - - // Keywords for multiple-document-handling - public struct MultipleDocumentHandling - { - public const string SeparateDocumentsCollatedCopies = "separate-documents-collated-copies"; - public const string SeparateDocumentsUncollatedCopies = "separate-documents-uncollated-copies"; - public const string SingleDocument = "single-document"; - public const string SingleDocumentNewSheet = "single-document-new-sheet"; - } - - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf Table 13. - public struct PresentationDirections - { - public const string ToRightToBottom = "toright-tobottom"; - public const string ToBottomToRight = "tobottom-toright"; - public const string ToLeftToBottom = "toleft-tobottom"; - public const string ToBottomToLeft = "tobottom-toleft"; - public const string ToRightToTop = "toright-totop"; - public const string ToTopToRight = "totop-toright"; - public const string ToLeftToTop = "toleft-totop"; - public const string ToTopToLeft = "totop-toleft"; - } - - /// - /// Media Size Self-Describing Names - /// https://ftp.pwg.org/pub/pwg/candidates/cs-pwgmsn10-20020226-5101.1.pdf section 5. - /// - public struct MediaSizeNames - { - // ASME sizes - public const string AsmeF = "asme_f_28x40in"; - - // ISO A series - public const string Iso2A0 = "iso_2a0_1189x1682mm"; - public const string IsoA0 = "iso_a0_841x1189mm"; - public const string IsoA1 = "iso_a1_594x841mm"; - public const string IsoA1x3 = "iso_a1x3_841x1783mm"; - public const string IsoA1x4 = "iso_a1x4_841x2378mm"; - public const string IsoA2 = "iso_a2_420x594mm"; - public const string IsoA2x3 = "iso_a2x3_594x1261mm"; - public const string IsoA2x4 = "iso_a2x4_594x1682mm"; - public const string IsoA2x5 = "iso_a2x5_594x2102mm"; - public const string IsoA3 = "iso_a3_297x420mm"; - public const string IsoA3Extra = "iso_a3-extra_322x445mm"; - public const string IsoA0x3 = "iso_a0x3_1189x2523mm"; - public const string IsoA3x3 = "iso_a3x3_420x891mm"; - public const string IsoA3x4 = "iso_a3x4_420x1189mm"; - public const string IsoA3x5 = "iso_a3x5_420x1486mm"; - public const string IsoA3x6 = "iso_a3x6_420x1783mm"; - public const string IsoA3x7 = "iso_a3x7_420x2080mm"; - public const string IsoA4Extra = "iso_a4-extra_235.5x322.3mm"; - public const string IsoA4Tab = "iso_a4-tab_225x297mm"; - public const string IsoA4 = "iso_a4_210x297mm"; - public const string IsoA4x3 = "iso_a4x3_297x630mm"; - public const string IsoA4x4 = "iso_a4x4_297x841mm"; - public const string IsoA4x5 = "iso_a4x5_297x1051mm"; - public const string IsoA4x6 = "iso_a4x6_297x1261mm"; - public const string IsoA4x7 = "iso_a4x7_297x1471mm"; - public const string IsoA4x8 = "iso_a4x8_297x1682mm"; - public const string IsoA4x9 = "iso_a4x9_297x1892mm"; - public const string IsoA5Extra = "iso_a5-extra_174x235mm"; - public const string IsoA5 = "iso_a5_148x210mm"; - public const string IsoA6 = "iso_a6_105x148mm"; - public const string IsoA7 = "iso_a7_74x105mm"; - public const string IsoA8 = "iso_a8_52x74mm"; - public const string IsoA9 = "iso_a9_37x52mm"; - public const string IsoA10 = "iso_a10_26x37mm"; - - // ISO B series - public const string IsoB0 = "iso_b0_1000x1414mm"; - public const string IsoB1 = "iso_b1_707x1000mm"; - public const string IsoB2 = "iso_b2_500x707mm"; - public const string IsoB3 = "iso_b3_353x500mm"; - public const string IsoB4 = "iso_b4_250x353mm"; - public const string IsoB5Extra = "iso_b5-extra_201x276mm"; - public const string IsoB5 = "iso_b5_176x250mm"; - public const string IsoB6 = "iso_b6_125x176mm"; - public const string IsoB6C4 = "iso_b6c4_125x324mm"; - public const string IsoB7 = "iso_b7_88x125mm"; - public const string IsoB8 = "iso_b8_62x88mm"; - public const string IsoB9 = "iso_b9_44x62mm"; - public const string IsoB10 = "iso_b10_31x44mm"; - - // ISO C series (envelopes) - public const string IsoC0 = "iso_c0_917x1297mm"; - public const string IsoC1LongEdge = "iso_c1-long-edge_917x648mm"; - public const string IsoC1 = "iso_c1_648x917mm"; - public const string IsoC2LongEdge = "iso_c2-long-edge_648x458mm"; - public const string IsoC2 = "iso_c2_458x648mm"; - public const string IsoC3LongEdge = "iso_c3-long-edge_458x324mm"; - public const string IsoC3 = "iso_c3_324x458mm"; - public const string IsoC4LongEdge = "iso_c4-long-edge_324x229mm"; - public const string IsoC4 = "iso_c4_229x324mm"; - public const string IsoC5LongEdge = "iso_c5-long-edge_229x162mm"; - public const string IsoC5 = "iso_c5_162x229mm"; - public const string IsoC6LongEdge = "iso_c6-long-edge_162x114mm"; - public const string IsoC6 = "iso_c6_114x162mm"; - public const string IsoC6C5 = "iso_c6c5_114x229mm"; - public const string IsoC7LongEdge = "iso_c7-long-edge_114x81mm"; - public const string IsoC7 = "iso_c7_81x114mm"; - public const string IsoC7C6 = "iso_c7c6_81x162mm"; - public const string IsoC8LongEdge = "iso_c8-long-edge_81x57mm"; - public const string IsoC8 = "iso_c8_57x81mm"; - public const string IsoC9LongEdge = "iso_c9-long-edge_57x40mm"; - public const string IsoC9 = "iso_c9_40x57mm"; - public const string IsoC10LongEdge = "iso_c10-long-edge_40x28mm"; - public const string IsoC10 = "iso_c10_28x40mm"; - public const string IsoDlLongEdge = "iso_dl-long-edge_220x110mm"; - public const string IsoDl = "iso_dl_110x220mm"; - - // ISO RA series - public const string IsoRa0 = "iso_ra0_860x1220mm"; - public const string IsoRa1 = "iso_ra1_610x860mm"; - public const string IsoRa2 = "iso_ra2_430x610mm"; - public const string IsoRa3 = "iso_ra3_305x430mm"; - public const string IsoRa4 = "iso_ra4_215x305mm"; - - // ISO SRA series - public const string IsoSra0 = "iso_sra0_900x1280mm"; - public const string IsoSra1 = "iso_sra1_640x900mm"; - public const string IsoSra2 = "iso_sra2_450x640mm"; - public const string IsoSra3 = "iso_sra3_320x450mm"; - public const string IsoSra4 = "iso_sra4_225x320mm"; - - // ISO ID-1 (credit card size) - public const string IsoId1 = "iso_id-1_53.98x85.6mm"; - - // JIS B series - public const string JisB0 = "jis_b0_1030x1456mm"; - public const string JisB1 = "jis_b1_728x1030mm"; - public const string JisB2 = "jis_b2_515x728mm"; - public const string JisB3 = "jis_b3_364x515mm"; - public const string JisB4 = "jis_b4_257x364mm"; - public const string JisB5 = "jis_b5_182x257mm"; - public const string JisB6 = "jis_b6_128x182mm"; - public const string JisB7 = "jis_b7_91x128mm"; - public const string JisB8 = "jis_b8_64x91mm"; - public const string JisB9 = "jis_b9_45x64mm"; - public const string JisB10 = "jis_b10_32x45mm"; - public const string JisExec = "jis_exec_216x330mm"; - - // Japanese envelope and postcard sizes - public const string JpnChou2 = "jpn_chou2_111.1x146mm"; - public const string JpnChou3 = "jpn_chou3_120x235mm"; - public const string JpnChou4 = "jpn_chou4_90x205mm"; - public const string JpnChou40 = "jpn_chou40_90x225mm"; - public const string JpnHagaki = "jpn_hagaki_100x148mm"; - public const string JpnKahu = "jpn_kahu_240x322.1mm"; - public const string JpnKaku1 = "jpn_kaku1_270x382mm"; - public const string JpnKaku2 = "jpn_kaku2_240x332mm"; - public const string JpnKaku3 = "jpn_kaku3_216x277mm"; - public const string JpnKaku4 = "jpn_kaku4_197x267mm"; - public const string JpnKaku5 = "jpn_kaku5_190x240mm"; - public const string JpnKaku7 = "jpn_kaku7_142x205mm"; - public const string JpnKaku8 = "jpn_kaku8_119x197mm"; - public const string JpnOufuku = "jpn_oufuku_148x200mm"; - public const string JpnYou1 = "jpn_you1_120x176mm"; - public const string JpnYou3 = "jpn_you3_98x148mm"; - public const string JpnYou4 = "jpn_you4_105x235mm"; - public const string JpnYou4LongEdge = "jpn_you4_235x105mm"; - public const string JpnYou5 = "jpn_you5_95x217mm"; - public const string JpnYou6 = "jpn_you6_98x190mm"; - public const string JpnYou7 = "jpn_you7_92x165mm"; - public const string JpnYouchou2 = "jpn_youchou2_146x111.1mm"; - public const string JpnYouchou3 = "jpn_youchou3_235x120mm"; - public const string JpnYouchou4 = "jpn_youchou4_205x90mm"; - - // North American sizes - public const string Na5x7 = "na_5x7_5x7in"; - public const string Na6x9 = "na_6x9_6x9in"; - public const string Na7x9 = "na_7x9_7x9in"; - public const string Na9x11 = "na_9x11_9x11in"; - public const string Na10x11 = "na_10x11_10x11in"; - public const string Na10x13 = "na_10x13_10x13in"; - public const string Na10x14 = "na_10x14_10x14in"; - public const string Na10x15 = "na_10x15_10x15in"; - public const string Na11x12 = "na_11x12_11x12in"; - public const string Na11x15 = "na_11x15_11x15in"; - public const string Na12x19 = "na_12x19_12x19in"; - public const string NaA2 = "na_a2_4.375x5.75in"; - public const string NaArchA = "na_arch-a_9x12in"; - public const string NaArchB = "na_arch-b_12x18in"; - public const string NaArchC = "na_arch-c_18x24in"; - public const string NaArchD = "na_arch-d_24x36in"; - public const string NaArchE2 = "na_arch-e2_26x38in"; - public const string NaArchE3 = "na_arch-e3_27x39in"; - public const string NaArchE = "na_arch-e_36x48in"; - public const string NaBPlus = "na_b-plus_12x19.17in"; - public const string NaC5 = "na_c5_6.5x9.5in"; - public const string NaC = "na_c_17x22in"; - public const string NaD = "na_d_22x34in"; - public const string NaE = "na_e_34x44in"; - public const string NaEdp = "na_edp_11x14in"; - public const string NaEurEdp = "na_eur-edp_12x14in"; - public const string NaExecutive = "na_executive_7.25x10.5in"; - public const string NaF = "na_f_44x68in"; - public const string NaFanfoldEur = "na_fanfold-eur_8.5x12in"; - public const string NaFanfoldUs = "na_fanfold-us_11x14.875in"; - public const string NaFoolscap = "na_foolscap_8.5x13in"; - public const string NaGovtLegal = "na_govt-legal_8x13in"; - public const string NaGovtLetter = "na_govt-letter_8x10in"; - public const string NaIndex3x5 = "na_index-3x5_3x5in"; - public const string NaIndex4x6Ext = "na_index-4x6-ext_6x8in"; - public const string NaIndex4x6 = "na_index-4x6_4x6in"; - public const string NaIndex5x8 = "na_index-5x8_5x8in"; - public const string NaInvoice = "na_invoice_5.5x8.5in"; - public const string NaLedger = "na_ledger_11x17in"; - public const string NaLegalExtra = "na_legal-extra_9.5x15in"; - public const string NaLegal = "na_legal_8.5x14in"; - public const string NaLetterExtra = "na_letter-extra_9.5x12in"; - public const string NaLetterPlus = "na_letter-plus_8.5x12.69in"; - public const string NaLetter = "na_letter_8.5x11in"; - public const string NaMonarchLongEdge = "na_monarch-long-edge_7.5x3.875in"; - public const string NaMonarch = "na_monarch_3.875x7.5in"; - public const string NaNumber9LongEdge = "na_number-9-long-edge_8.875x3.875in"; - public const string NaNumber9 = "na_number-9_3.875x8.875in"; - public const string NaNumber10LongEdge = "na_number-10-long-edge_9.5x4.125in"; - public const string NaNumber10 = "na_number-10_4.125x9.5in"; - public const string NaNumber11LongEdge = "na_number-11-long-edge_10.375x4.5in"; - public const string NaNumber11 = "na_number-11_4.5x10.375in"; - public const string NaNumber12LongEdge = "na_number-12-long-edge_11x4.75in"; - public const string NaNumber12 = "na_number-12_4.75x11in"; - public const string NaNumber14LongEdge = "na_number-14-long-edge_11.5x5in"; - public const string NaNumber14 = "na_number-14_5x11.5in"; - public const string NaOficio = "na_oficio_8.5x13.4in"; - public const string NaPersonalLongEdge = "na_personal-long-edge_6.5x3.625in"; - public const string NaPersonal = "na_personal_3.625x6.5in"; - public const string NaQuarto = "na_quarto_8.5x10.83in"; - public const string NaSuperA = "na_super-a_8.94x14in"; - public const string NaSuperB = "na_super-b_13x19in"; - public const string NaWideFormat = "na_wide-format_30x42in"; - - // Other sizes (oe_ prefix) - public const string Oe12x16 = "oe_12x16_12x16in"; - public const string Oe13x22 = "oe_13x22_13x22in"; - public const string Oe14x17 = "oe_14x17_14x17in"; - public const string Oe18x22 = "oe_18x22_18x22in"; - public const string OeA2Plus = "oe_a2plus_17x24in"; - public const string OeBusinessCard = "oe_business-card_2x3.5in"; - public const string OePhoto10r = "oe_photo-10r_10x12in"; - public const string OePhoto12r = "oe_photo-12r_12x15in"; - public const string OePhoto14x18 = "oe_photo-14x18_14x18in"; - public const string OePhoto16r = "oe_photo-16r_16x20in"; - public const string OePhoto20r = "oe_photo-20r_20x24in"; - public const string OePhoto20x30 = "oe_photo-20x30_20x30in"; - public const string OePhoto22r = "oe_photo-22r_22x29.5in"; - public const string OePhoto22x28 = "oe_photo-22x28_22x28in"; - public const string OePhoto24r = "oe_photo-24r_24x31.5in"; - public const string OePhoto24x30 = "oe_photo-24x30_24x30in"; - public const string OePhoto30r = "oe_photo-30r_30x40in"; - public const string OePhotoL = "oe_photo-l_3.5x5in"; - public const string OmPhotoL = "om_photo-l_89x119mm"; - public const string OePhotoS8r = "oe_photo-s8r_8x12in"; - public const string OePhotoS10r = "oe_photo-s10r_10x15in"; - public const string OeSquarePhoto4x4 = "oe_square-photo_4x4in"; - public const string OeSquarePhoto5x5 = "oe_square-photo_5x5in"; - - // Other metric sizes (om_ prefix) - public const string Om16k184x260 = "om_16k_184x260mm"; - public const string Om16k195x270 = "om_16k_195x270mm"; - public const string OmBusinessCard55x85 = "om_business-card_55x85mm"; - public const string OmBusinessCard55x91 = "om_business-card_55x91mm"; - public const string OmCard = "om_card_54x86mm"; - public const string OmDaiPaKai = "om_dai-pa-kai_275x395mm"; - public const string OmDscPhoto = "om_dsc-photo_89x119mm"; - public const string OmFolioSp = "om_folio-sp_215x315mm"; - public const string OmFolio = "om_folio_210x330mm"; - public const string OmInvite = "om_invite_220x220mm"; - public const string OmItalian = "om_italian_110x230mm"; - public const string OmJuuroKuKai = "om_juuro-ku-kai_198x275mm"; - public const string OmLargePhoto = "om_large-photo_200x300mm"; - public const string OmMediumPhoto = "om_medium-photo_130x180mm"; - public const string OmPaKai = "om_pa-kai_267x389mm"; - public const string OmPhoto30x40 = "om_photo-30x40_300x400mm"; - public const string OmPhoto30x45 = "om_photo-30x45_300x450mm"; - public const string OmPhoto30x90 = "om_photo-30x90_300x900mm"; - public const string OmPhoto35x46 = "om_photo-35x46_350x460mm"; - public const string OmPhoto40x60 = "om_photo-40x60_400x600mm"; - public const string OmPhoto50x75 = "om_photo-50x75_500x750mm"; - public const string OmPhoto50x76 = "om_photo-50x76_500x760mm"; - public const string OmPhoto60x90 = "om_photo-60x90_600x900mm"; - public const string OmSmallPhoto = "om_small-photo_100x150mm"; - public const string OmSquarePhoto = "om_square-photo_89x89mm"; - public const string OmWidePhoto = "om_wide-photo_100x200mm"; - - // PRC (People's Republic of China) sizes - public const string Prc1 = "prc_1_102x165mm"; - public const string Prc2 = "prc_2_102x176mm"; - public const string Prc4 = "prc_4_110x208mm"; - public const string Prc6 = "prc_6_120x320mm"; - public const string Prc7 = "prc_7_160x230mm"; - public const string Prc8 = "prc_8_120x309mm"; - public const string Prc16k = "prc_16k_146x215mm"; - public const string Prc32k = "prc_32k_97x151mm"; - - // ROC (Republic of China/Taiwan) sizes - public const string Roc8k = "roc_8k_10.75x15.5in"; - public const string Roc16k = "roc_16k_7.75x10.75in"; - - // other media sizes - public const string Universal11x14LexmarkCustomSize = "oe_universal_11x14in"; - } - - /// - /// keywords for media-source. - /// https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf section 7.6.5. - /// - public struct MediaSource - { - public const string Alternate = "alternate"; - public const string AlternateRoll = "alternate-roll"; - public const string Auto = "auto"; - public const string Bottom = "bottom"; - public const string ByPassTray = "by-pass-tray"; - public const string Center = "center"; - public const string Disc = "disc"; - public const string Envelope = "envelope"; - public const string Hagaki = "hagaki"; - public const string LargeCapacity = "large-capacity"; - public const string Left = "left"; - public const string Main = "main"; - public const string MainRoll = "main-roll"; - public const string Manual = "manual"; - public const string Middle = "middle"; - public const string Photo = "photo"; - public const string Rear = "rear"; - public const string Right = "right"; - public const string Roll1 = "roll-1"; - public const string Roll2 = "roll-2"; - public const string Roll3 = "roll-3"; - public const string Roll4 = "roll-4"; - public const string Roll5 = "roll-5"; - public const string Roll6 = "roll-6"; - public const string Roll7 = "roll-7"; - public const string Roll8 = "roll-8"; - public const string Roll9 = "roll-9"; - public const string Roll10 = "roll-10"; - public const string Side = "side"; - public const string Top = "top"; - public const string Tray1 = "tray-1"; - public const string Tray2 = "tray-2"; - public const string Tray3 = "tray-3"; - public const string Tray4 = "tray-4"; - public const string Tray5 = "tray-5"; - public const string Tray6 = "tray-6"; - public const string Tray7 = "tray-7"; - public const string Tray8 = "tray-8"; - public const string Tray9 = "tray-9"; - public const string Tray10 = "tray-10"; - public const string Tray11 = "tray-11"; - public const string Tray12 = "tray-12"; - public const string Tray13 = "tray-13"; - public const string Tray14 = "tray-14"; - public const string Tray15 = "tray-15"; - public const string Tray16 = "tray-16"; - public const string Tray17 = "tray-17"; - public const string Tray18 = "tray-18"; - public const string Tray19 = "tray-19"; - public const string Tray20 = "tray-20"; - } - - // keywords for media-type - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf section 3.13.2 - // https://ftp.pwg.org/pub/pwg/candidates/cs-pwgmsn10-20020226-5101.1.pdf section 3 - // https://ftp.pwg.org/pub/pwg/ipp/registrations/xerox-mediatypes-20201202.txt - // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xhtml - public struct MediaTypes - { - public const string Aluminum = "aluminum"; - public const string Auto = "auto"; - public const string BackPrintFilm = "back-print-film"; - public const string Cardboard = "cardboard"; - public const string Cardstock = "cardstock"; - public const string CardstockCoated = "cardstock-coated"; - public const string CardstockHeavyweight = "cardstock-heavyweight"; - public const string CardstockHeavyweightCoated = "cardstock-heavyweight-coated"; - public const string CardstockLightweight = "cardstock-lightweight"; - public const string CardstockLightweightCoated = "cardstock-lightweight-coated"; - public const string Cd = "cd"; - public const string Continuous = "continuous"; - public const string ContinuousLong = "continuous-long"; - public const string ContinuousShort = "continuous-short"; - public const string CorrugatedBoard = "corrugated-board"; - public const string Disc = "disc"; - public const string DiscGlossy = "disc-glossy"; - public const string DiscHighGloss = "disc-high-gloss"; - public const string DiscMatte = "disc-matte"; - public const string DiscSatin = "disc-satin"; - public const string DiscSemiGloss = "disc-semi-gloss"; - public const string DoubleWall = "double-wall"; - public const string DryFilm = "dry-film"; - public const string Dvd = "dvd"; - public const string EmbossingFoil = "embossing-foil"; - public const string EndBoard = "end-board"; - public const string Envelope = "envelope"; - public const string EnvelopeArchival = "envelope-archival"; - public const string EnvelopeBond = "envelope-bond"; - public const string EnvelopeCoated = "envelope-coated"; - public const string EnvelopeCotton = "envelope-cotton"; - public const string EnvelopeFine = "envelope-fine"; - public const string EnvelopeHeavyweight = "envelope-heavyweight"; - public const string EnvelopeInkjet = "envelope-inkjet"; - public const string EnvelopeLightweight = "envelope-lightweight"; - public const string EnvelopePlain = "envelope-plain"; - public const string EnvelopePreprinted = "envelope-preprinted"; - public const string EnvelopeWindow = "envelope-window"; - public const string Fabric = "fabric"; - public const string FabricArchival = "fabric-archival"; - public const string FabricGlossy = "fabric-glossy"; - public const string FabricHighGloss = "fabric-high-gloss"; - public const string FabricMatte = "fabric-matte"; - public const string FabricSemiGloss = "fabric-semi-gloss"; - public const string FabricWaterproof = "fabric-waterproof"; - public const string Film = "film"; - public const string FlexoBase = "flexo-base"; - public const string FlexoPhotoPolymer = "flexo-photo-polymer"; - public const string Flute = "flute"; - public const string Foil = "foil"; - public const string FullCutTabs = "full-cut-tabs"; - public const string Glass = "glass"; - public const string GlassColored = "glass-colored"; - public const string GlassOpaque = "glass-opaque"; - public const string GlassSurfaced = "glass-surfaced"; - public const string GlassTextured = "glass-textured"; - public const string GravureCylinder = "gravure-cylinder"; - public const string ImageSetterPaper = "image-setter-paper"; - public const string ImagingCylinder = "imaging-cylinder"; - public const string Labels = "labels"; - public const string LabelsColored = "labels-colored"; - public const string LabelsContinuous = "labels-continuous"; - public const string LabelsGlossy = "labels-glossy"; - public const string LabelsHeavyweight = "labels-heavyweight"; - public const string LabelsHighGloss = "labels-high-gloss"; - public const string LabelsInkjet = "labels-inkjet"; - public const string LabelsLightweight = "labels-lightweight"; - public const string LabelsMatte = "labels-matte"; - public const string LabelsPermanent = "labels-permanent"; - public const string LabelsSatin = "labels-satin"; - public const string LabelsSecurity = "labels-security"; - public const string LabelsSemiGloss = "labels-semi-gloss"; - public const string LaminatingFoil = "laminating-foil"; - public const string Letterhead = "letterhead"; - public const string Metal = "metal"; - public const string MetalGlossy = "metal-glossy"; - public const string MetalHighGloss = "metal-high-gloss"; - public const string MetalMatte = "metal-matte"; - public const string MetalSatin = "metal-satin"; - public const string MetalSemiGloss = "metal-semi-gloss"; - public const string MountingTape = "mounting-tape"; - public const string MultiLayer = "multi-layer"; - public const string MultiPartForm = "multi-part-form"; - public const string Other = "other"; - public const string Paper = "paper"; - public const string Photographic = "photographic"; - public const string PhotographicArchival = "photographic-archival"; - public const string PhotographicFilm = "photographic-film"; - public const string PhotographicGlossy = "photographic-glossy"; - public const string PhotographicHighGloss = "photographic-high-gloss"; - public const string PhotographicMatte = "photographic-matte"; - public const string PhotographicSatin = "photographic-satin"; - public const string PhotographicSemiGloss = "photographic-semi-gloss"; - public const string Plastic = "plastic"; - public const string PlasticArchival = "plastic-archival"; - public const string PlasticColored = "plastic-colored"; - public const string PlasticGlossy = "plastic-glossy"; - public const string PlasticHighGloss = "plastic-high-gloss"; - public const string PlasticMatte = "plastic-matte"; - public const string PlasticSatin = "plastic-satin"; - public const string PlasticSemiGloss = "plastic-semi-gloss"; - public const string Plate = "plate"; - public const string Polyester = "polyester"; - public const string PreCutTabs = "pre-cut-tabs"; - public const string Roll = "roll"; - public const string Screen = "screen"; - public const string ScreenPaged = "screen-paged"; - public const string SelfAdhesive = "self-adhesive"; - public const string SelfAdhesiveFilm = "self-adhesive-film"; - public const string ShrinkFoil = "shrink-foil"; - public const string SingleFace = "single-face"; - public const string SingleWall = "single-wall"; - public const string Sleeve = "sleeve"; - public const string Stationery = "stationery"; - public const string StationeryArchival = "stationery-archival"; - public const string StationeryBond = "stationery-bond"; - public const string StationeryCoated = "stationery-coated"; - public const string StationeryColored = "stationery-colored"; - public const string StationeryCotton = "stationery-cotton"; - public const string StationeryFine = "stationery-fine"; - public const string StationeryHeavyweight = "stationery-heavyweight"; - public const string StationeryHeavyweightCoated = "stationery-heavyweight-coated"; - public const string StationeryInkjet = "stationery-inkjet"; - public const string StationeryLetterhead = "stationery-letterhead"; - public const string StationeryLightweight = "stationery-lightweight"; - public const string StationeryPreprinted = "stationery-preprinted"; - public const string StationeryPrepunched = "stationery-prepunched"; - public const string StationeryRecycled = "stationery-recycled"; - public const string TabStock = "tab-stock"; - public const string Tractor = "tractor"; - public const string Transfer = "transfer"; - public const string Transparency = "transparency"; - public const string TripleWall = "triple-wall"; - public const string WetFilm = "wet-film"; - } - - // keywords for presentation-direction-number-up - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf - public struct PresentationDirectionNumberUp - { - public const string BottomLeft = "tobottom-toleft"; - public const string BottomRight = "tobottom-toright"; - public const string LeftBottom = "toleft-tobottom"; - public const string LeftTop = "toleft-totop"; - public const string RightBottom = "toright-tobottom"; - public const string RightTop = "toright-totop"; - public const string TopLeft = "totop-toleft"; - public const string TopRight = "totop-toright"; - } - - // keywords for page-order-received - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippprodprint10-20010212-5100.3.pdf - public struct PageOrderReceived - { - public const string OnetoNOrder = "1-to-n-order"; - public const string NtoOneOrder = "n-to-1-order"; - } - - // keywords for media-source-feed-direction. - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf section 7.6.6.1 - public struct MediaSourceFeedDirections - { - public const string LongEdgeFirst = "long-edge-first"; - public const string ShortEdgeFirst = "short-edge-first"; - } - - // keywords for output-bin-supported, etc. - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippoutputbin10-20010207-5100.2.pdf section 2.1. - public struct OutputBins - { - public const string Auto = "auto"; - public const string Top = "top"; - public const string Middle = "middle"; - public const string Bottom = "bottom"; - public const string Side = "side"; - public const string Left = "left"; - public const string Right = "right"; - public const string Center = "center"; - public const string Rear = "rear"; - public const string FaceUp = "face-up"; - public const string FaceDown = "face-down"; - public const string LargeCapacity = "large-capacity"; - public const string Stacker1 = "stacker-1"; - public const string Stacker2 = "stacker-2"; - public const string Stacker3 = "stacker-3"; - public const string Stacker4 = "stacker-4"; - public const string Stacker5 = "stacker-5"; - public const string Stacker6 = "stacker-6"; - public const string Stacker7 = "stacker-7"; - public const string Stacker8 = "stacker-8"; - public const string Stacker9 = "stacker-9"; - public const string Stacker10 = "stacker-10"; - public const string Mailbox1 = "mailbox-1"; - public const string Mailbox2 = "mailbox-2"; - public const string Mailbox3 = "mailbox-3"; - public const string Mailbox4 = "mailbox-4"; - public const string Mailbox5 = "mailbox-5"; - public const string Mailbox6 = "mailbox-6"; - public const string Mailbox7 = "mailbox-7"; - public const string Mailbox8 = "mailbox-8"; - public const string Mailbox9 = "mailbox-9"; - public const string Mailbox10 = "mailbox-10"; - public const string Mailbox11 = "mailbox-11"; - public const string Mailbox12 = "mailbox-12"; - public const string Mailbox13 = "mailbox-13"; - public const string Mailbox14 = "mailbox-14"; - public const string Mailbox15 = "mailbox-15"; - public const string Mailbox16 = "mailbox-16"; - public const string Mailbox17 = "mailbox-17"; - public const string Mailbox18 = "mailbox-18"; - public const string Mailbox19 = "mailbox-19"; - public const string Mailbox20 = "mailbox-20"; - public const string Mailbox21 = "mailbox-21"; - public const string Mailbox22 = "mailbox-22"; - public const string Mailbox23 = "mailbox-23"; - public const string Mailbox24 = "mailbox-24"; - public const string Mailbox25 = "mailbox-25"; - public const string MyMailbox = "my-mailbox"; - public const string Tray1 = "tray-1"; - public const string Tray2 = "tray-2"; - public const string Tray3 = "tray-3"; - public const string Tray4 = "tray-4"; - public const string Tray5 = "tray-5"; - public const string Tray6 = "tray-6"; - public const string Tray7 = "tray-7"; - public const string Tray8 = "tray-8"; - public const string Tray9 = "tray-9"; - public const string Tray10 = "tray-10"; - public const string Tray11 = "tray-11"; - public const string Tray12 = "tray-12"; - public const string Tray13 = "tray-13"; - public const string Tray14 = "tray-14"; - public const string Tray15 = "tray-15"; - public const string Tray16 = "tray-16"; - public const string Tray17 = "tray-17"; - public const string Tray18 = "tray-18"; - public const string Tray19 = "tray-19"; - public const string Tray20 = "tray-20"; - } - - // keywords for printer-state-reasons - // https://www.iana.org/assignments/ipp-registrations/ipp-registrations.xhtml - // we currently accept any non-empty values of printer-state-reasons - // the list below only includes some values from the entire list to be used during testing - public struct PrinterStateReasons - { - public const string None = "none"; - public const string Other = "other"; - public const string MediaNeeded = "media-needed"; - public const string MediaJam = "media-jam"; - public const string MovingToPaused = "moving-to-paused"; - public const string Paused = "paused"; - public const string Shutdown = "shutdown"; - public const string ConnectingToDevice = "connecting-to-device"; - public const string TimedOut = "timed-out"; - public const string Stopping = "stopping"; - public const string StoppedPartly = "stopped-partly"; - public const string TonerLow = "toner-low"; - public const string TonerEmpty = "toner-empty"; - public const string SpoolAreaFull = "spool-area-full"; - public const string CoverOpen = "cover-open"; - public const string InterlockOpen = "interlock-open"; - public const string DoorOpen = "door-open"; - public const string InputTrayMissing = "input-tray-missing"; - public const string MediaLow = "media-low"; - public const string MediaEmpty = "media-empty"; - public const string OutputTrayMissing = "output-tray-missing"; - public const string OutputAreaAlmostFull = "output-area-almost-full"; - public const string OutputAreaFull = "output-area-full"; - public const string MarkerSupplyLow = "marker-supply-low"; - public const string MarkerSupplyEmpty = "marker-supply-empty"; - public const string MarkerWasteAlmostFull = "marker-waste-almost-full"; - public const string MarkerWasteFull = "marker-waste-full"; - public const string FuserOverTemp = "fuser-over-temp"; - public const string FuserUnderTemp = "fuser-under-temp"; - public const string OpcNearEol = "opc-near-eol"; - public const string OpcLifeOver = "opc-life-over"; - public const string DeveloperLow = "developer-low"; - public const string DeveloperEmpty = "developer-empty"; - public const string InterpreterResourceUnavailable = "interpreter-resource-unavailable"; - } - - public struct PrinterStateReasonSuffix - { - public const string Report = "-report"; - public const string Warning = "-warning"; - public const string Error = "-error"; - } - - public struct JobStateReasons - { - public const string None = "none"; - public const string JobIncoming = "job-incoming"; - public const string JobDataInsufficient = "job-data-insufficient"; - public const string DocumentAccessError = "document-access-error"; - public const string SubmissionInterrupted = "submission-interrupted"; - public const string JobOutgoing = "job-outgoing"; - public const string JobHoldUntilSpecified = "job-hold-until-specified"; - public const string ResourcesAreNotReady = "resources-are-not-ready"; - public const string PrinterStoppedPartly = "printer-stopped-partly"; - public const string PrinterStopped = "printer-stopped"; - public const string JobInterpreting = "job-interpreting"; - public const string JobQueued = "job-queued"; - public const string JobTransforming = "job-transforming"; - public const string JobQueuedForMarker = "job-queued-for-marker"; - public const string JobPrinting = "job-printing"; - public const string JobCanceledByUser = "job-canceled-by-user"; - public const string JobCanceledByOperator = "job-canceled-by-operator"; - public const string JobCanceledAtDevice = "job-canceled-at-device"; - public const string AbortedBySystem = "aborted-by-system"; - public const string UnsupportedCompression = "unsupported-compression"; - public const string CompressionError = "compression-error"; - public const string UnsupportedDocumentFormat = "unsupported-document-format"; - public const string DocumentFormatError = "document-format-error"; - public const string ProcessingToStopPoint = "processing-to-stop-point"; - public const string ServiceOffLine = "service-off-line"; - public const string JobCompletedSuccessfully = "job-completed-successfully"; - public const string JobCompletedWithWarnings = "job-completed-with-warnings"; - public const string JobCompletedWithErrors = "job-completed-with-errors"; - public const string QueuedInDevice = "queued-in-device"; - public const string JobFetchable = "job-fetchable"; - public const string JobReleaseWait = "job-release-wait"; - public const string JobHeldForAuthorization = "job-held-for-authorization"; - public const string JobHeldForButtonPress = "job-held-for-button-press"; - public const string JobHeldForRelease = "job-held-for-release"; //Replacement for JobReleaseWait - public const string JobPasswordWait = "job-password-wait"; - public const string OtherError = "other-error"; - public const string JobFetchableByUser = "job-fetchable-by-user"; - } - - // http://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf - public struct IppFeaturesSupported - { - public const string AirPrintTwoDotOne = "airprint-2.1"; - public const string DocumentObject = "document-object"; - public const string JobSave = "job-save"; - public const string None = "none"; - public const string PageOverrides = "page-overrides"; - public const string ProofPrint = "proof-print"; - public const string SubscriptionObject = "subscription-object"; - } - - // Mopria 1.3 spec Wi-Fi_Direct_Services_Print_Technical_Specification_v1.0 - // https://microsoft.sharepoint.com/:w:/t/STACKTeam-CoreNetworkingMobileConnectivityPeripheralsStackSe/EQkk8BUSMqxGh1b0wkNLlhIBWqsk28NRk3n3F-jEKB69Gg?e=5iUo4A - public struct PdfVersionsSupported - { - public const string Adobe13 = "adobe-1.3"; - public const string Adobe14 = "adobe-1.4"; - public const string Adobe15 = "adobe-1.5"; - public const string Adobe16 = "adobe-1.6"; - public const string Iso1593012001 = "iso-15930-1_2001"; - public const string Iso1593032002 = "iso-15930-3_2002"; - public const string Iso1593042003 = "iso-15930-4_2003"; - public const string Iso1593062003 = "iso-15930-6_2003"; - public const string Iso1593072010 = "iso-15930-7_2010"; - public const string Iso1593082010 = "iso-15930-8_2010"; - public const string Iso1661222010 = "iso-16612-2_2010"; - public const string Iso1900512005 = "iso-19005-1_2005"; - public const string Iso1900522011 = "iso-19005-2_2011"; - public const string Iso1900532012 = "iso-19005-3_2012"; - public const string Iso3200012008 = "iso-32000-1_2008"; - public const string Pwg51023 = "pwg-5102.3"; - public const string None = "none"; - } - - // RFC 5100.7 Section 7.6 https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext10-20031031-5100.7.pdf - public struct DocumentFormatDetailsSupported - { - public const string DocumentSourceApplicationName = "document-source-application-name"; - public const string DocumentSourceApplicationVersion = "document-source-application-version"; - public const string DocumentSourceOsName = "document-source-os-name"; - public const string DocumentSourceOsVersion = "document-source-os-version"; - public const string DocumentFormat = "document-format"; - public const string DocumentFormatDeviceId = "document-format-device-id"; - public const string DocumentFormatVersion = "document-format-version"; - public const string DocumentNaturalLanguage = "document-natural-language"; - } - - // Section 6.4.12 of https://ftp.pwg.org/pub/pwg/candidates/cs-ipptrans10-20131108-5100.16.pdf - public struct PrinterKinds - { - public const string Disc = "disc"; - public const string Document = "document"; - public const string Envelope = "envelope"; - public const string Label = "label"; - public const string LargeFormat = "large-format"; - public const string Photo = "photo"; - public const string Postcard = "postcard"; - public const string Receipt = "receipt"; - public const string Roll = "roll"; - } - - // Section 5.3.3 of https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobext20-20190816-5100.7.pdf - public struct PrintContentOptimize - { - public const string Auto = "auto"; // AirPrint specific, see section 9.3.57 of AirPrint Version 2.1.1 specification - public const string Graphics = "graphics"; - public const string Photo = "photo"; - public const string Text = "text"; - public const string TextAndGraphics = "text-and-graphics"; - } - - // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.28 - public struct PdlOverride - { - public const string Attempted = "attempted"; - public const string Guaranteed = "guaranteed"; - public const string NotAttempted = "not-attempted"; - } - - // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.2 - // Section 9.7 of https://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf - // Section 7.8 of https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf - public struct UriAuthentication - { - public const string Basic = "basic"; - public const string Certificate = "certificate"; - public const string Digest = "digest"; - public const string Negotiate = "negotiate"; - public const string None = "none"; - public const string Oauth = "oauth"; - public const string RequestingUserName = "requesting-user-name"; - } - - // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.3 - public struct UriSecurity - { - public const string None = "none"; - public const string Ssl3 = "ssl3"; - public const string Tls = "tls"; - } - - // https://ftp.pwg.org/pub/pwg/ipp/wd/wd-ippnodriver20-20201029.pdf#section-6.6.17 - public struct MultipleOperationTimeoutAction - { - public const string AbortJob = "abort-job"; - public const string HoldJob = "hold-job"; - public const string ProcessJob = "process-job"; - } - - // https://ftp.pwg.org/pub/pwg/ipp/wd/wd-ippepx20-20230206.pdf#section-6.1.3 - // MopriaCloudPrintSpecificationv1.1.027#section 4.10 - public struct JobReleaseAction - { - public const string None = "none"; - public const string ButtonPress = "button-press"; - public const string JobPassword = "job-password"; - public const string OwnerAuthorized = "owner-authorized"; - public const string OwnerAuthorizedBadge = "owner-authorized-badge"; - public const string OwnerAuthorizedUsernamePassword = "owner-authorized-username-password"; - public const string OwnerAuthorizedBiometrics = "owner-authorized-biometrics"; - public const string OwnerAuthorizedOther = "owner-authorized-other"; - public const string WorkflowApp = "workflow-app"; - } - - // https://www.rfc-editor.org/rfc/rfc8011.html#section-5.4.32 - public struct Compression - { - public const string None = "none"; - public const string Compress = "compress"; - public const string Deflate = "deflate"; - public const string Gzip = "gzip"; - } -} - -#pragma warning restore SA1602 // Enumeration items should be documented -#pragma warning restore SA1600 // Elements should be documented diff --git a/BadgeReleaseDemo/IppLibrary/IPPEncoding.cs b/BadgeReleaseDemo/IppLibrary/IPPEncoding.cs deleted file mode 100644 index 86a72e0..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPEncoding.cs +++ /dev/null @@ -1,198 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- - -#pragma warning disable SA1402 // File may only contain a single type. IPP types, this is fine. -#pragma warning disable SA1602 // Document enum values. IPP types, this is fine. - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using BadgeReleaseDemo.IppLibrary.Common; - - /// - /// The IPP version supported by this library. - /// - public enum IppMajorVersion : sbyte - { - Version1 = 0x1, - Version2 = 0x2, - } - - /// - /// Describes the standard IPP tag types. - /// - public enum Tag - { - // Attribute tags - Reserved = 0x0, - OperationAttributes = 0x1, - JobAttributes = 0x2, - EndOfAttributes = 0x3, - PrinterAttributes = 0x4, - UnsupportedAttributes = 0x5, - SubscriptionAttributes = 0x6, - EventNotificationAttributes = 0x7, - DocumentAttributes = 0x9, - - // RawValue tags - Unsupported = 0x10, - ReservedDefault = 0x11, - Unknown = 0x12, - NoValue = 0x13, - - Integer = 0x21, - Boolean = 0x22, - Enum = 0x23, - - OctetString = 0x30, - DateTime = 0x31, - Resolution = 0x32, - RangeOfInteger = 0x33, - BegCollection = 0x34, - TextWithLanguage = 0x35, - NameWithLanguage = 0x36, - EndCollection = 0x37, - - TextWithoutLanguage = 0x41, - NameWithoutLanguage = 0x42, - Keyword = 0x44, - Uri = 0x45, - UriScheme = 0x46, - Charset = 0x47, - NaturalLanguage = 0x48, - MimeMediaType = 0x49, - - MemberAttrName = 0x4a, - - TypeExtension = 0x7f, // indicates a type beyond 255, first 4 bytes of value field are the tag value. - } - - /// - /// Public facing encoding related utility functions. - /// - public static class IppEncodingUtil - { - public static async Task> DeserializeAttributeGroupAsync(Stream input, IppAttributeGroup newGroup, CancellationToken cancellationToken) - { - var ippStream = new IPPInputStream(input); - return await DeserializeAttributeGroupAsync(ippStream, newGroup, cancellationToken); - } - - /// - /// Deserialize the input stream to IPPAttributeGroups stored in this.AttributeGroups. - /// - public static async Task> DeserializeAttributeGroupAsync(IPPInputStream input, IppAttributeGroup newGroup, CancellationToken cancellationToken) - { - var isOperationGroupRfcCompliant = true; - var isOperationGroup = newGroup.Type == Tag.OperationAttributes; - - // An attribute group contains zero or more attribute fields. - // Read a single "attribute-with-one-value" field; - // this may be followed by one or more "additional-value" fields - // for attributes with multiple values, or it may be a - // begin-attribute-group tag (indicating an empty group) or an - // end-of-attributes tag (indicating the end of attributes) - var tag = await input.ReadTagAsync(cancellationToken); - string nextAttributeName = null; - - while (Helpers.IsValueTag(tag)) - { - var returnedValue = await IppAttribute.DeserializeIppAttributeAsync(input, cancellationToken, tag, nextAttributeName); - var newAttribute = returnedValue.Item1; - tag = returnedValue.Item2; - nextAttributeName = returnedValue.Item3; - - // Per RFC 2911 section 3.1.4 - // "However, for these two attributes within the Operation Attributes group, the order - // is critical.The "attributes-charset" attribute MUST be the first attribute in the group and - // the "attributes-natural-language" attribute MUST be the second attribute in the group" - if (isOperationGroup) - { - // At least for now, we only support UTF-8 charset, otherwise we need to appropriately handle localized text, name encoding and decoding. - if (string.Compare(newAttribute.ValueName, OperationAttributes.AttributesCharset, StringComparison.OrdinalIgnoreCase) == 0 && - string.Compare(newAttribute.Values[0].GetNativeValue(), Constants.CharSet, StringComparison.OrdinalIgnoreCase) != 0) - { - throw new IPPException(BadgeReleaseDemo.IppLibrary.StatusCode.ClientErrorCharsetNotSupported, FormattableString.Invariant($"IPP charset {newAttribute.Values[0].GetNativeValue()} not supported")); - } - - if (newGroup.Attributes.Count == 0 && - string.Compare(newAttribute.ValueName, OperationAttributes.AttributesCharset, StringComparison.Ordinal) != 0) - { - isOperationGroupRfcCompliant = false; - } - - if (newGroup.Attributes.Count == 1 && - string.Compare(newAttribute.ValueName, OperationAttributes.AttributesNaturalLanguage, StringComparison.Ordinal) != 0) - { - isOperationGroupRfcCompliant = false; - } - } - - newGroup.AddAttribute(newAttribute); - } - - return new Tuple(tag, isOperationGroupRfcCompliant); - } - - /// - /// Deserialize a complete IPP attribute group object. Intended to deserialize a single IppAttributeGroup stored on - /// its own, e.g. in the PrinterObject. This will throw if an IPP request or response stream is passed in. - /// - /// The serialized attribute group. - /// The deserialized IPPAttributeGroup object. - public static async Task DeserializeFullAttributeGroupAsync(Stream input, IppAttributeGroup attrGroup, CancellationToken cancellationToken) - { - if (input == null) - { - throw new ArgumentNullException(nameof(input)); - } - - if (attrGroup == null) - { - throw new ArgumentNullException(nameof(attrGroup)); - } - - input.Seek(0, SeekOrigin.Begin); - attrGroup.Type = (Tag)input.ReadByte(); - - // This function is used to deserialize IppAttributeGroup objects, as opposed to deserializing requests and responses. - // Hence it is valid for the stream to end immediately after the begin-attribute-group-tag if the group is empty. - // Read next byte rather than Position and Length because Length can be 0 when HTTP chunked encoding is being used. - if (await input.ReadAsync(new byte[sizeof(byte)], 0, sizeof(byte), cancellationToken) <= 0) - { - return; - } - - // We still need the byte we read above to perform deserialization, so seek back to it. - input.Seek(-1, SeekOrigin.Current); - - // The byte returned by deserialization should be -1 because DeserializeAttributeGroupAsync uses ReadByte, - // which returns -1 when the end of the stream is reached. If a single, serialized attribute group was - // passed in, we should be at the end of the stream once deserialization is finished. - var lastByteRead = (await DeserializeAttributeGroupAsync(input, attrGroup, cancellationToken)).Item1; - if (lastByteRead >= 0) - { - throw new InvalidDataException($"Additional data found after serialized IPP attribute group. First byte of additional data: {lastByteRead}"); - } - } - } - - internal static class IppEncoding - { - public static sbyte IppVersionMajor => (sbyte)IppMajorVersion.Version2; - - public static sbyte IppVersionMinor => 0; - - public static string ContentType => "application/ipp"; - - public static string CharSet => "UTF-8"; - } -} -#pragma warning restore SA1402 // File may only contain a single type -#pragma warning restore SA1602 // Document enum values. IPP types, this is fine. diff --git a/BadgeReleaseDemo/IppLibrary/IPPException.cs b/BadgeReleaseDemo/IppLibrary/IPPException.cs deleted file mode 100644 index 7eca277..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPException.cs +++ /dev/null @@ -1,59 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - - /// - /// IPPException is expected to be thrown in any case where an error has occurred - /// that is meant to be surfaced to the calling client via an error response. - /// It encapsulates the IPP status code pertinent to the error as well as an associated - /// 'friendly' message (as well as an optional "detailed" status). - /// - public class IPPException : Exception - { - public IPPException(StatusCode statusCode, string message, IppAttributeGroup unsupportedAttributes = null) - : base(message) - { - this.StatusCode = statusCode; - this.DetailedStatusMessage = message; - this.UnsupportedAttributes = unsupportedAttributes; - } - - public StatusCode StatusCode { get; } - - public string DetailedStatusMessage { get; } - - /// - /// Gets or sets the IPP major version. - /// - public sbyte IPPRequestMajorVersion { get; set; } - - /// - /// Gets or sets the requested IPP minor version. - /// - public sbyte IPPRequestMinorVersion { get; set; } - - /// - /// Gets or sets the request id in IPP header. - /// - public int IPPRequestId { get; set; } - - /// - /// Gets or sets a value indicating whether the request was from printer. - /// - public bool IsRequestFromPrinter { get; set; } - - public string DetailedInternalInfo { get; set; } - - /// - /// Unsupported attributes group, to be sent in response in case of exception - /// https://datatracker.ietf.org/doc/html/rfc8011#section-4.1.7 - /// - public IppAttributeGroup UnsupportedAttributes { get; set; } - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs b/BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs deleted file mode 100644 index 5856a70..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPFactoryHelper.cs +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) Microsoft. All rights reserved. -// - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - - /// - /// Factory helper - /// - public static class IppFactoryHelper - { - /// - /// Create an instance of IIppRequestFactory class. - /// - /// The host of the cloud printer service. e.g., printer.microsoft.com. - /// The cloud device UUID. - /// For requesting-user-name attribute. - /// For requesting-user-uri attribute. - /// an instance of the factory - public static IIppRequestFactory CreateIppRequestFactory(string host, string printerId, string requestingUserName, string requestingUserUri) - { - // Below is text from RFC 8011. Essentially, the printer URI must be a URL. - // https://tools.ietf.org/html/rfc8011#section-4.1.5 regarding printer URI: - // In all cases, the target URIs contained within the body of IPP - // operation requests and responses MUST be in absolute format rather - // than relative format(a relative URL identifies a resource with the - // scope of the HTTP server, but does not include scheme, host, - // or port). - var printerUri = "ipps://" + host + "/printers/" + printerId; - return new IppRequestFactory() - { - PrinterUri = new Uri(printerUri), - RequestingUserName = requestingUserName, - RequestingUserUri = requestingUserUri - }; - } - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IPPInputStream.cs b/BadgeReleaseDemo/IppLibrary/IPPInputStream.cs deleted file mode 100644 index 62608d4..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPInputStream.cs +++ /dev/null @@ -1,183 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.IO; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - - public class IPPInputStream : IDisposable - { - public IPPInputStream(Stream inputStream) - { - this.Stream = inputStream; - this.BytesRead = 0; - } - - public long BytesRead { get; private set; } - - // would have preferred to keep Stream as private variable, however, need to keep this as public 'get' because it is assigned to IPPRequest.Data for reading print document payload - public Stream Stream { get; private set; } - - public void Dispose() - { - if (this.Stream != null) - { - this.Stream.Dispose(); - this.Stream = null; - } - } - - public async Task ReadNetworkShortAsync(CancellationToken cancellationToken) - { - var buffer = await this.ReadInternalAsync(sizeof(short), cancellationToken); - return (short)((buffer[0] << 8) | buffer[1]); - } - - public async Task ReadNetworkIntegerAsync(CancellationToken cancellationToken) - { - byte[] buffer = await this.ReadInternalAsync(sizeof(Int32), cancellationToken); - return (int)((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) | buffer[3]); - } - - public async Task ReadTagAsync(CancellationToken cancellationToken) - { - try - { - byte[] buffer = await this.ReadInternalAsync(sizeof(byte), cancellationToken); - return (Tag)buffer[0]; - } - catch (InvalidDataException) - { - // mocking the behavior for (Tag)input.ReadByte() when end of stream is reached - return (Tag)(-1); - } - } - - public async Task ReadSbyteAsync(CancellationToken cancellationToken) - { - var buffer = await this.ReadInternalAsync(sizeof(sbyte), cancellationToken); - return (sbyte)buffer[0]; - } - - /// - /// Reads an IPP string (ANSI) from the stream. - /// - /// - /// - /// - public async Task ReadStringAsync(int length, CancellationToken cancellationToken) - { - StringBuilder sb = new StringBuilder(); - - byte[] buffer = await this.ReadInternalAsync(length, cancellationToken); - - for (int i = 0; i < length; i++) - { - sb.Append((char)buffer[i]); - } - - return sb.ToString(); - } - - public async Task ReadAsync(int count, CancellationToken cancellationToken) - { - return await this.ReadInternalAsync(count, cancellationToken); - } - - /// - /// see: ReadNumberOfBytesOrEndOfStreamInternalAsync() - /// - public async Task ReadNumberOfBytesOrEndOfStreamAsync(int count, CancellationToken cancellationToken) - { - byte[] buffer = new byte[count]; - return await this.ReadNumberOfBytesOrEndOfStreamInternalAsync(buffer, cancellationToken); - } - - /// - /// Reads the stream and creates a buffer with a maximum of given number of bytes - /// - /// Caller provided buffer. - /// Cancellation token - /// Byte array with the content read from buffer - public async Task ReadNumberOfBytesOrEndOfStreamAsync(byte[] buffer, CancellationToken cancellationToken) - { - return await this.ReadNumberOfBytesOrEndOfStreamInternalAsync(buffer, cancellationToken); - } - - /// - /// Reads a give number of bytes from the stream - /// - /// Number of bytes to be read. - /// Cancellation token - /// Byte array with the content read from buffer - private async Task ReadInternalAsync(int count, CancellationToken cancellationToken) - { - int bytesLeftToRead = count; - byte[] buffer = new byte[count]; - var bytesRead = 0; - while (bytesLeftToRead > 0) - { - bytesRead = await this.Stream.ReadAsync(buffer, count - bytesLeftToRead, bytesLeftToRead, cancellationToken); - - if (bytesRead == bytesLeftToRead) - { - break; - } - else if (bytesRead > 0) - { - bytesLeftToRead -= bytesRead; - } - else - { - throw new InvalidDataException("Buffer returned 0 bytes"); - } - } - - this.BytesRead += count; - return buffer; - } - - /// - /// Reads the stream and creates a buffer with a maximum of given number of bytes - /// - /// Caller provided buffer. - /// Cancellation token - /// Byte array with the content read from buffer - private async Task ReadNumberOfBytesOrEndOfStreamInternalAsync(byte[] buffer, CancellationToken cancellationToken) - { - int count = buffer.Length; - int bytesLeftToRead = count; - var bytesRead = 0; - var totalBytesRead = 0; - while (bytesLeftToRead > 0) - { - bytesRead = await this.Stream.ReadAsync(buffer, count - bytesLeftToRead, bytesLeftToRead, cancellationToken); - totalBytesRead += bytesRead; - - if (bytesRead == bytesLeftToRead) - { - break; - } - else if (bytesRead > 0) - { - bytesLeftToRead -= bytesRead; - } - else - { - Array.Resize(ref buffer, totalBytesRead); - break; - } - } - - this.BytesRead += totalBytesRead; - return buffer; - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPJob.cs b/BadgeReleaseDemo/IppLibrary/IPPJob.cs deleted file mode 100644 index 501d3cb..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPJob.cs +++ /dev/null @@ -1,227 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - - /// - /// An object of IPP job description and status attribute as described in - /// https://tools.ietf.org/html/rfc8011#section-5.3. - /// - public class IppJob - { - public const int JobStateUnknown = -1; - - public const int JobIdUnknown = 0; - - /// - /// Initializes a new instance of the class. - /// - /// Optional job attributes. - /// Optional operation attributes. - public IppJob(IppAttributeGroup jobAttributeGroup = null, IppAttributeGroup operationAttributeGroup = null) - { - if (jobAttributeGroup == null) - { - return; - } - - this.JobAttributeGroup = jobAttributeGroup; - - this.OperationAttributeGroup = operationAttributeGroup; - - this.JobStateReasons = new List(); - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobUri)) - { - this.JobUri = (string)jobAttributeGroup.Attributes[Common.JobAttributes.JobUri].FirstValue.GetNativeValue(); - } - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobId)) - { - this.JobId = (int)jobAttributeGroup.Attributes[Common.JobAttributes.JobId].FirstValue.GetNativeValue(); - } - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobState)) - { - this.JobState = (int)jobAttributeGroup.Attributes[Common.JobAttributes.JobState].FirstValue.GetNativeValue(); - } - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobName)) - { - this.JobName = jobAttributeGroup.Attributes[Common.JobAttributes.JobName].FirstValue.GetNativeValue().ToString(); - } - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobOriginatingUserName)) - { - this.JobOriginatingUserName = jobAttributeGroup.Attributes[Common.JobAttributes.JobOriginatingUserName].FirstValue.GetNativeValue().ToString(); - } - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobOriginatingUserUri)) - { - this.JobOriginatingUserUri = new Uri(jobAttributeGroup.Attributes[Common.JobAttributes.JobOriginatingUserUri].FirstValue.GetNativeValue().ToString()); - } - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.JobStateReasons)) - { - var jobStateReasonsAttributeValues = jobAttributeGroup.Attributes[Common.JobAttributes.JobStateReasons].Values; - foreach (var value in jobStateReasonsAttributeValues) - { - this.JobStateReasons.Add(value.GetNativeValue().ToString()); - } - } - - // DateTimeAtCreation is always available for a job. - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.DateTimeAtCreation)) - { - this.DateTimeAtCreation = jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtCreation].FirstValue.GetNativeValue().ToString(); - } - - // Service returns unknown ipp type if job is not yet processed. - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.DateTimeAtProcessing) && - jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtProcessing].FirstValue.ValueType == Tag.DateTime) - { - this.DateTimeAtProcessing = jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtProcessing].FirstValue.GetNativeValue().ToString(); - } - else - { - this.DateTimeAtProcessing = string.Empty; - } - - // Service returns unknown ipp type if job is not yet completed. - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.DateTimeAtCompleted) && - jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtCompleted].FirstValue.ValueType == Tag.DateTime) - { - this.DateTimeAtCompleted = jobAttributeGroup.Attributes[Common.JobAttributes.DateTimeAtCompleted].FirstValue.GetNativeValue().ToString(); - } - else - { - this.DateTimeAtCompleted = string.Empty; - } - - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.TimeAtCreation)) - { - this.TimeAtCreation = this.GetIntOrUnknownValue(jobAttributeGroup, Common.JobAttributes.TimeAtCreation); - } - - // Service returns no-value ipp type if job is not yet processed. - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.TimeAtProcessing) && - jobAttributeGroup.Attributes[Common.JobAttributes.TimeAtProcessing].FirstValue.ValueType == Tag.Integer) - { - this.TimeAtProcessing = this.GetIntOrUnknownValue(jobAttributeGroup, Common.JobAttributes.TimeAtProcessing); - } - else - { - this.TimeAtProcessing = 0; - } - - // Service returns no-value ipp type if job is not yet completed. - if (jobAttributeGroup.Attributes.ContainsKey(Common.JobAttributes.TimeAtCompleted) && - jobAttributeGroup.Attributes[Common.JobAttributes.TimeAtCompleted].FirstValue.ValueType == Tag.Integer) - { - this.TimeAtCompleted = this.GetIntOrUnknownValue(jobAttributeGroup, Common.JobAttributes.TimeAtCompleted); - } - else - { - this.TimeAtCompleted = 0; - } - } - - /// - /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.2 - /// - public string JobUri { get; set; } = string.Empty; - - /// - /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.1 - /// - public int JobId { get; set; } = JobIdUnknown; - - /// - /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.7 - /// - public int JobState { get; set; } = JobStateUnknown; - - /// - /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.8 - /// - public List JobStateReasons { get; set; } - - /// - /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.5 - /// - public string JobName { get; set; } = string.Empty; - - /// - /// Gets or sets see: https://tools.ietf.org/html/rfc8011#section-5.3.6 - /// - public string JobOriginatingUserName { get; set; } = string.Empty; - - /// - /// Gets or sets the: https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf - /// section 5.3.3. - /// - public Uri JobOriginatingUserUri { get; set; } = null; - - /// - /// Gets or sets the creation date time. https://tools.ietf.org/html/rfc8011#section-5.3.14.5 - /// - public string DateTimeAtCreation { get; set; } - - /// - /// Gets or sets the job processing date time. https://tools.ietf.org/html/rfc8011#section-5.3.14.6 - /// - public string DateTimeAtProcessing { get; set; } - - /// - /// Gets or sets the job completed date time. https://tools.ietf.org/html/rfc8011#section-5.3.14.7 - /// - public string DateTimeAtCompleted { get; set; } - - /// - /// Gets or sets the creation time. https://tools.ietf.org/html/rfc8011#section-5.3.14.1 - /// - public int TimeAtCreation { get; set; } = 0; - - /// - /// Gets or sets the processing time. https://tools.ietf.org/html/rfc8011#section-5.3.14.2 - /// - public int TimeAtProcessing { get; set; } = 0; - - /// - /// Gets or sets the completed time. https://tools.ietf.org/html/rfc8011#section-5.3.14.3 - /// - public int TimeAtCompleted { get; set; } = 0; - - /// - /// Gets the complete job attributes. - /// - public IppAttributeGroup JobAttributeGroup { get; } = null; - - /// - /// Gets the complete operation attributes. - /// - public IppAttributeGroup OperationAttributeGroup { get; } = null; - - /// - /// If attribute is unknown then return 0 otherwise attribute value. - /// - private int GetIntOrUnknownValue(IppAttributeGroup jobAttributeGroup, string attributeName) - { - object value = jobAttributeGroup.Attributes[attributeName].FirstValue.GetNativeValue(); - int returnInt = 0; - - if (value != null) - { - returnInt = (int)value; - } - - return returnInt; - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs b/BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs deleted file mode 100644 index 32899f3..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPMemberAttribute.cs +++ /dev/null @@ -1,154 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - - public class IppMemberAttribute - { - private readonly IppAttribute MemberAttributeImpl; - - /// - /// Initializes a new instance of the class with no values and member attributes. - /// - public IppMemberAttribute(string attributeName) - { - this.MemberAttributeImpl = new IppAttribute(attributeName); - } - - /// - /// Initializes a new instance of the class with a value. - /// - public IppMemberAttribute(string attributeName, IppValue value) - : this(attributeName, new List { value }) - { - } - - /// - /// Initializes a new instance of the class with a list of values. - /// - public IppMemberAttribute(string attributeName, List values) - { - this.MemberAttributeImpl = new IppAttribute(attributeName, values); - } - - /// - /// Gets the values of the attribute. - /// - public IReadOnlyList Values => this.MemberAttributeImpl.Values; - - /// - /// Gets the name of the value, i.e., attribute name. - /// - public string ValueName => this.MemberAttributeImpl.ValueName; - - /// - /// Gets the the first value. - /// - public IppValue FirstValue => this.Values.First(); - - /// - /// Serialize a member attribute. - /// - public void SerializeMemberAttribute(Stream output, int collectionDepth) - { - if (this.Values.Count == 0) - { - throw new Exception("Values of a member attribute cannot be empty"); - } - - // Setup the member attribute: https://tools.ietf.org/html/rfc8010#section-3.1.6 - this.Values.ElementAt(0).SerializeAsMemberAttribute(output, this.ValueName, collectionDepth); // The first attribute value carries the name of the attribute - - for (int i = 1; i < this.Values.Count; i++) - { - this.Values.ElementAt(i).SerializeAsMemberAttribute(output, string.Empty, collectionDepth); // Additional attribute values use a name length of 0x0000. - } - } - - /// - /// Add additional value to the attribute. - /// - /// The value to add. - public void AddAdditionalValue(IppValue ippValue) - { - this.MemberAttributeImpl.AddAdditionalValue(ippValue); - } - - /// - /// Gets value indicating whether this is a collection attribute. - /// - public bool IsCollectionAttribute() - { - return this.MemberAttributeImpl.IsCollectionAttribute; - } - - /// - /// Compare two attributes. - /// - public override bool Equals(object obj) - { - if (obj is IppMemberAttribute other) - { - var isAttributeNameEqual = this.ValueName.Equals(other.ValueName, StringComparison.Ordinal); - var areValuesEqual = this.Values.SequenceEqual(other.Values); - return isAttributeNameEqual && areValuesEqual; - } - - return false; - } - - /// - /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. - /// Attribute comparison is used by test code. - /// - public override int GetHashCode() - { - return base.GetHashCode(); - } - - /// - /// Serialize the member attribute to string. - /// - /// String representation of the attribute. - public override string ToString() - { - if (this.Values.Count == 0) - { - throw new Exception("Values of a member attribute cannot be empty"); - } - - if (this.Values.Count > 1) - { - var sb = new StringBuilder(); - if (string.IsNullOrEmpty(this.ValueName)) - { - sb.Append(string.Empty); - } - - sb.AppendFormat(CultureInfo.InvariantCulture, "Member Attribute {0} - Multiple Values:\n", this.ValueName); - - foreach (var value in this.Values) - { - sb.Append("\t\t" + value.ToString()); - } - - return sb.ToString(); - } - else - { - // Single valued attribute - return string.Format(CultureInfo.InvariantCulture, "\t\tMember Attribute {0}: {1}", this.ValueName, this.FirstValue.ToString()); - } - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPRequest.cs b/BadgeReleaseDemo/IppLibrary/IPPRequest.cs deleted file mode 100644 index 96d946f..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPRequest.cs +++ /dev/null @@ -1,352 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using BadgeReleaseDemo.IppLibrary.Common; - - /// - /// Represents a raw IPP request and provides deserialization from - /// stream representations. - /// No semantic validation is done here, merely conformance to RFC 2910. - /// (See section 3 here: https://tools.ietf.org/html/rfc2910) - /// - public class IppRequest : RawIppEncodingBase - { - /// - /// The input stream for an incoming IPP request. - /// - private readonly IPPInputStream input; - - /// - /// Initializes a new instance of the class. - /// - private IppRequest() - { - } - - /// - /// Initializes a new instance of the class. - /// Private constructor. An IPPRequest object is created by CreateIppRequestFromStreamAsync(). - /// - private IppRequest(Stream input) - { - this.input = new IPPInputStream(input) ?? throw new InvalidOperationException("IPPRequest requires input stream."); - this.BaseAttributeGroups = new List(); - } - - /// - /// Gets the number of bytes read from the request stream - /// - public long BytesRead { get => this.input.BytesRead; } - - /// - /// Create a new IppRequest object with print payload. - /// - /// The operation id of the request. - /// The request id of the request. - /// The print payload. - /// - public static Task CreateAsync(Operation operationId, int requestId, Stream printPayload) - { - var request = new IppRequest() - { - OperationId = operationId, - RequestId = requestId, - MajorVersionNumber = (sbyte)IppEncoding.IppVersionMajor, - MinorVersionNumber = (sbyte)IppEncoding.IppVersionMinor, - Data = printPayload - }; - - return Task.FromResult(request); - } - - /// - /// Create a new IppRequest object without print payload. - /// - /// The operation id of the request. - /// The request id of the request. - /// - public static Task CreateAsync(Operation operationId, int requestId) - { - var request = new IppRequest() - { - OperationId = operationId, - RequestId = requestId, - MajorVersionNumber = (sbyte)IppEncoding.IppVersionMajor, - MinorVersionNumber = (sbyte)IppEncoding.IppVersionMinor, - }; - - return Task.FromResult(request); - } - - /// - /// Create a new IPP request from incoming stream. - /// - public static async Task CreateIppRequestFromStreamAsync(Stream requestStream, CancellationToken cancellationToken, Func throttlingControl = null) - { - var newIppRequest = new IppRequest(requestStream); - await newIppRequest.DeserializeAsync(cancellationToken, throttlingControl); - return newIppRequest; - } - - /// - /// Create a new IPP request from incoming stream. - /// - public static async Task CreateAsync(Stream requestStream) - { - return await CreateIppRequestFromStreamAsync(requestStream, new CancellationTokenSource().Token); - } - - /// - /// Gets or sets a value indicating whether the deserialized Operation attribute group is compliant with RFC. - /// Specifically: RFC 2911 requires first and second attributes of Operation groups to be - /// attributes-charset and attributes-natural-language respectively. - /// - public bool IsOperationGroupAttributesRfcCompliant { get; set; } = true; - - /// - /// Gets the requested operation. - /// - public Operation OperationId { get; set; } - - /// - /// Gets the request id. - /// - public int RequestId { get; set; } - - /// - /// Gets the attribute groups in the request. - /// - public List AttributeGroups => this.BaseAttributeGroups; - - public override void Dispose() - { - this.input?.Dispose(); - base.Dispose(); - } - - public List LookupAttributeGroup(Tag type) - { - var matchedAttributes = new List(); - foreach (var attribute in this.BaseAttributeGroups) - { - if (attribute.Type == type) - { - matchedAttributes.Add(attribute); - } - } - - return matchedAttributes; - } - - /// - /// Returns a string that represents the IPPRequest object. - /// - /// - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - sb.AppendFormat(CultureInfo.InvariantCulture, "Operation ID: {0}\n", this.OperationId); - sb.AppendFormat(CultureInfo.InvariantCulture, "Request ID: {0}\n", this.RequestId); - sb.Append(base.ToString()); - return sb.ToString(); - } - - /// - /// Must have required attributes. - /// - public void CheckOperationAttributes() - { - if (this.RequestId <= 0) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Request id must be a positive integer."); - } - - List operationAttributesList = this.LookupAttributeGroup(Tag.OperationAttributes); - if (operationAttributesList.Count == 0) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "No operation-attributes group."); - } - - IppAttributeGroup operationAttributes = operationAttributesList[0]; - if (!operationAttributes.Attributes.ContainsKey(RequiredAttributes.AttributesCharset)) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "No attributes-charset operation attribute."); - } - - if (!operationAttributes.Attributes.ContainsKey(RequiredAttributes.AttributesNaturalLanguage)) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "No attributes-natural-language operation attribute."); - } - - if (!this.IsOperationGroupAttributesRfcCompliant) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Incorrect order of attributes-charset and attributes-natural-language"); - } - } - - /// - /// Deserialize IPPRequest from stream. - /// - /// A task. - public async Task DeserializeAsync(CancellationToken cancellationToken, Func throttlingControl = null) - { - // Parse the header blob - this.MajorVersionNumber = await this.input.ReadSbyteAsync(cancellationToken); - this.MinorVersionNumber = await this.input.ReadSbyteAsync(cancellationToken); - this.OperationId = (Operation)await this.input.ReadNetworkShortAsync(cancellationToken); - this.RequestId = await this.input.ReadNetworkIntegerAsync(cancellationToken); - - if (throttlingControl != null) - { - await throttlingControl.Invoke(this.OperationId); - } - - // Read zero or more attributes from the request - var tag = await this.input.ReadTagAsync(cancellationToken); - - while (tag != Tag.EndOfAttributes) - { - // This should be a "begin-attribute-group" tag specifying the type. - if (!Helpers.IsBeginAttributeGroupTag(tag)) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Expected a begin-attribute-group tag here."); - } - - // Form a new attribute group. - var newGroup = new IppAttributeGroup(tag); - - var groupProps = await IppEncodingUtil.DeserializeAttributeGroupAsync(this.input, newGroup, cancellationToken); - var rfcCompliant = groupProps.Item2; - tag = groupProps.Item1; - - if (!rfcCompliant) - { - this.IsOperationGroupAttributesRfcCompliant = false; - } - - // Duplicate attribute groups are not expressly prohibited. If this is a duplicate group, - // merge the attributes from the new group into the existing group. See Bug #39498460 - var existingGroups = this.LookupAttributeGroup(newGroup.Type); - if (existingGroups.Count == 0) - { - this.BaseAttributeGroups.Add(newGroup); - } - else if (existingGroups.Count == 1) - { - foreach (var attribute in newGroup.Attributes) - { - existingGroups[0].AddAttribute(attribute.Value); - } - } - else // unreachable - { - throw new Exception("Serialization failed to merge duplicate attribute groups."); - } - } - - // We have read in all attribute-groups at this point. If there is anything left, it is the "data" field. - this.Data = this.input.Stream; - - // Done. - } - - /// - /// Serialize IPPRequest per RFC 8010 (https://tools.ietf.org/html/rfc8010). - /// - public Stream Serialize() - { - Stream memoryStream = new MemoryStream(); - - // IPP requestHeader - memoryStream.WriteByte((byte)this.MajorVersionNumber); - memoryStream.WriteByte((byte)this.MinorVersionNumber); - Helpers.WriteNetworkShort(memoryStream, (short)this.OperationId); - Helpers.WriteNetworkInteger(memoryStream, this.RequestId); - - foreach (var attributeGroup in this.AttributeGroups) - { - attributeGroup.Serialize(memoryStream); - } - - // End of attributes. - memoryStream.WriteByte((byte)Tag.EndOfAttributes); - - // Copy the print payload if any. - if (this.Data != null) - { - this.Data.Seek(0, SeekOrigin.Begin); - this.Data.CopyTo(memoryStream); - } - - memoryStream.Seek(0, SeekOrigin.Begin); - return memoryStream; - } - - /// - /// Compare two attributes. - /// - public override bool Equals(object obj) - { - if (obj is IppRequest other) - { - if (this.OperationId != other.OperationId) - { - return false; - } - - if (this.RequestId != other.RequestId) - { - return false; - } - - if (this.MajorVersionNumber != other.MajorVersionNumber || this.MinorVersionNumber != other.MinorVersionNumber) - { - return false; - } - - if (!this.AttributeGroups.SequenceEqual(other.AttributeGroups)) - { - return false; - } - - // just compare size of the payload. - if (this.Data != null) - { - var otherDataLength = other.Data.Length - other.Data.Position; - if (this.Data.Length != otherDataLength) - { - return false; - } - } - } - else - { - return false; - } - - return true; - } - - /// - /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. - /// Attribute comparison is used by test code. - /// - public override int GetHashCode() - { - return base.GetHashCode(); - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs b/BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs deleted file mode 100644 index cb06aec..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPRequestFactory.cs +++ /dev/null @@ -1,892 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Threading.Tasks; - using BadgeReleaseDemo.IppLibrary.Common; - - /// - /// Provides implementations for standard IPP INFRA operations - /// - internal class IppRequestFactory : IIppRequestFactory - { - /// - /// Gets or sets iPP requesting-user-name. - /// - public string RequestingUserName { get; set; } - - /// - /// Gets or sets iPP-INFRA requesting-user-uri. - /// - public string RequestingUserUri { get; set; } - - /// - /// Gets or sets iPP printer_uri. - /// - public Uri PrinterUri { get; set; } - - /// - /// Create Cancel-Job request. - /// - /// Request id of the ipp request. - /// - /// See IPP-INFRA section 5. - /// The id of the job. - /// The uri of the job. - /// - public async Task CreateCancelJobRequestAsync( - int requestId, - string requestingUserName, - string requestingUserUri, - int jobId, - string jobUri) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - - if (string.IsNullOrEmpty(jobUri)) - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); - } - else - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.JobUri, IppValue.CreateURIValue(jobUri))); - } - - var request = await IppRequest.CreateAsync(Operation.CancelJob, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create Get-Printer-Attributes request. - /// - /// Request id of the ipp request. - /// - /// See IPP-INFRA section 5. - /// Additional operation attributes to included. - /// Printer attributes requested. - /// - public async Task CreateGetPrinterAttributesRequestAsync( - int requestId, - string requestingUserName, - string requestingUserUri, - List extraOperationAttributes, - List requestedAttributes) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - - if (extraOperationAttributes != null - && extraOperationAttributes.Count != 0) - { - foreach (var attr in extraOperationAttributes) - { - operationAttributeGroup.AddAttribute(attr); - } - } - - /* RequestedAttributes is optional according to IPP. If requestedAttributes is null, IPP service should consider it as requesting "ALL" attributes - and should return all attributes of printer. Currently, MPS is not doing this and a bug has been created for same. Bug 20689820 */ - this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); - var request = await IppRequest.CreateAsync(Operation.GetPrinterAttributes, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create Get-Output-Device-Attributes request. - /// - /// Request id of the ipp request. - /// - /// See IPP-INFRA section 5. - /// Additional operation attributes to included. - /// Printer attributes requested. - /// - public async Task CreateGetOutputDeviceAttributesRequestAsync( - int requestId, - string requestingUserName, - string requestingUserUri, - List extraOperationAttributes, - List requestedAttributes) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - - if (extraOperationAttributes != null) - { - foreach (var attr in extraOperationAttributes) - { - operationAttributeGroup.AddAttribute(attr); - } - } - - this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); - var request = await IppRequest.CreateAsync(Operation.GetOutputDeviceAttributes, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create Set-Printer-Attributes request. - /// - /// Request id of the ipp request. - /// - /// See IPP-INFRA section 5. - /// Attributes to set - /// - public async Task CreateSetPrinterAttributesRequestAsync( - int requestId, - string requestingUserName, - string requestingUserUri, - IEnumerable attributesToSet) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - - var request = await IppRequest.CreateAsync(Operation.SetPrinterAttributes, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - - var attributesGrp = new IppAttributeGroup(Tag.PrinterAttributes); - - foreach (var attr in attributesToSet) - { - attributesGrp.AddAttribute(attr); - } - - request.AttributeGroups.Add(attributesGrp); - - return request; - } - - /// - /// Create Get-Printer-Supported-Values request. - /// - /// Request id of the ipp request. - /// - /// See IPP-INFRA section 5. - /// Printer attributes requested. - /// - public async Task CreateGetSupportedPrinterAttributesRequestAsync( - int requestId, - string requestingUserName, - string requestingUserUri, - List requestedAttributes) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - - if (requestedAttributes != null) - { - this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); - } - - var request = await IppRequest.CreateAsync(Operation.GetPrinterSupportedValues, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create Get-Printer-Device-Capabilities request. - /// - /// Request id of the ipp request. - /// - /// See IPP-INFRA section 5. - public async Task CreateGetPrinterDeviceCapabilitiesRequestAsync( - int requestId, - string requestingUserName, - string requestingUserUri) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - - var request = await IppRequest.CreateAsync(Operation.GetPrintDeviceCapabilities, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - -#pragma warning disable SA1515 // Single-line comment must be preceded by blank line -#pragma warning disable SA1614 // Element parameter documentation must have text - /// - /// Create Print-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.1). - /// - /// Request id of the IPP packet. - /// - /// - /// - /// - /// - /// representation - public async Task CreatePrintJobRequestAsync( - int requestId, - string jobName, - string documentName, - Stream documentPayload, - IppAttributeGroup extraOperationAttributes = null, - IppAttributeGroup jobTemplateAttributes = null) -#pragma warning restore SA1614 // Element parameter documentation must have text -#pragma warning restore SA1515 // Single-line comment must be preceded by blank line - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobName, IppValue.CreateURIValue(jobName))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentName, IppValue.CreateURIValue(documentName))); - - if (extraOperationAttributes != null) - { - foreach (var attr in extraOperationAttributes.Attributes) - { - operationAttributeGroup.AddAttribute(attr.Value); - } - } - - var request = await IppRequest.CreateAsync(Operation.PrintJob, requestId, documentPayload); - request.AttributeGroups.Add(operationAttributeGroup); - - if (jobTemplateAttributes != null) - { - request.AttributeGroups.Add(jobTemplateAttributes); - } - - return request; - } - -#pragma warning disable SA1515 // Single-line comment must be preceded by blank line -#pragma warning disable SA1614 // Element parameter documentation must have text - /// - /// Create Validate-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.3). - /// - /// Request id of the IPP packet. - /// - /// - /// - /// - /// representation - public async Task CreateValidateJobRequestAsync( - int requestId, - string jobName, - string documentName, - IppAttributeGroup extraOperationAttributes = null, - IppAttributeGroup jobTemplateAttributes = null) -#pragma warning restore SA1614 // Element parameter documentation must have text -#pragma warning restore SA1515 // Single-line comment must be preceded by blank line - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobName, IppValue.CreateURIValue(jobName))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentName, IppValue.CreateURIValue(documentName))); - - if (extraOperationAttributes != null) - { - foreach (var attr in extraOperationAttributes.Attributes) - { - operationAttributeGroup.AddAttribute(attr.Value); - } - } - - var request = await IppRequest.CreateAsync(Operation.ValidateJob, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - - if (jobTemplateAttributes != null) - { - request.AttributeGroups.Add(jobTemplateAttributes); - } - - return request; - } - -#pragma warning disable SA1515 // Single-line comment must be preceded by blank line -#pragma warning disable SA1614 // Element parameter documentation must have text - /// - /// Create Create-Job operation (https://tools.ietf.org/html/rfc8011#section-4.2.4). - /// - /// Request id of the IPP packet. - /// - /// - /// representation - public async Task CreateCreateJobRequestAsync( - int requestId, - string jobName = null, - IppAttributeGroup jobTemplateAttributes = null) -#pragma warning restore SA1614 // Element parameter documentation must have text -#pragma warning restore SA1515 // Single-line comment must be preceded by blank line - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - - // As per https://www.rfc-editor.org/rfc/rfc8011.html#section-4.1.5, printer-uri must be third operation attribute. - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - this.AddUserNameAndUserUri(operationAttributeGroup); - if (jobName != null) - { - operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobName, IppValue.CreateURIValue(jobName))); - } - - var request = await IppRequest.CreateAsync(Operation.CreateJob, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - if (jobTemplateAttributes != null) - { - request.AttributeGroups.Add(jobTemplateAttributes); - } - - return request; - } - -#pragma warning disable SA1515 // Single-line comment must be preceded by blank line -#pragma warning disable SA1614 // Element parameter documentation must have text - /// - /// Create Send-Document request. - /// - /// requestId - /// - /// jobUri - /// jobId - /// documentPayload - /// Attributes to be included in the operation attribute group. - /// The job template attributes to be included in the request. - /// representation - public async Task CreateSendDocumentRequestAsync( - int requestId, - bool lastDocument, - string jobUri = null, - int jobId = 0, - Stream documentPayload = null, - IppAttributeGroup extraOperationAttributes = null, - IppAttributeGroup jobTemplateAttributes = null) -#pragma warning restore SA1614 // Element parameter documentation must have text -#pragma warning restore SA1515 // Single-line comment must be preceded by blank line - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddJobUriOrId(jobUri, jobId, operationAttributeGroup); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.LastDocument, IppValue.CreateBooleanValue(lastDocument))); - if (extraOperationAttributes != null) - { - foreach (var attr in extraOperationAttributes.Attributes) - { - operationAttributeGroup.AddAttribute(attr.Value); - } - } - - var request = await IppRequest.CreateAsync(Operation.SendDocument, requestId, documentPayload); - request.AttributeGroups.Add(operationAttributeGroup); - if (jobTemplateAttributes != null) - { - request.AttributeGroups.Add(jobTemplateAttributes); - } - - return request; - } - - /// - /// Create Close-Job request. - /// - /// requestId - /// jobUri - /// jobId - /// representation - public async Task CreateCloseJobRequestAsync( - int requestId, - string jobUri = null, - int jobId = 0) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddJobUriOrId(jobUri, jobId, operationAttributeGroup); - this.AddUserNameAndUserUri(operationAttributeGroup); - - var request = await IppRequest.CreateAsync(Operation.CloseJob, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - -#pragma warning disable SA1515 // Single-line comment must be preceded by blank line -#pragma warning disable SA1614 // Element parameter documentation must have text - /// - /// Create Get-Job-AttributeGroups operation (https://tools.ietf.org/html/rfc8011#section-4.3.4). - /// - /// Request id of the IPP packet. - /// - /// - /// representation - public async Task CreateGetJobAttributesRequestAsync(int requestId, string jobUri, List requestedAttributes) -#pragma warning restore SA1614 // Element parameter documentation must have text -#pragma warning restore SA1515 // Single-line comment must be preceded by blank line - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(JobAttributes.JobUri, IppValue.CreateURIValue(jobUri))); - - this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); - var request = await IppRequest.CreateAsync(Operation.GetJobAttributes, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create Get-Jobs request. - /// - /// Request id of the IPP packet. - /// - /// Determine the value of "which-jobs" - /// See: section 8.2 http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf - /// - /// See IPP-INFRA section 5. - /// All of the new operations defined in this section are sent by the Proxy to the Infrastructure - /// Printer. For each operation, the "requesting-user-name" [RFC2911] and "requesting-useruri" - /// [PWG5100.13] operation attributes provide the unauthenticated identity of the Proxy - /// owner, e.g., "Jane Smith" and "mailto:jane.smith@example.com". - /// - /// See IPP-INFRA section 5. - /// Value for printer-uri attribute. - /// Value for output-device-uuid attribute (required for get-jobs requests from IPP-INFRA printers). - /// Job attributes requested. - /// representation - public async Task CreateGetJobsRequestAsync( - int requestId, - string jobType, - string requestingUserName, - string requestingUserUri, - string printerUri, - string outputDeviceUuid, - List requestedAttributes) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - - if (!string.IsNullOrEmpty(requestingUserUri)) - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.MyJobs, IppValue.CreateBooleanValue(true))); - } - - // Use printerUri value if provided. - var printerUriToUse = string.IsNullOrEmpty(printerUri) ? this.PrinterUri.ToString() : printerUri; - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(printerUriToUse))); - - // Add output-device-uuid if provided. - if (!string.IsNullOrEmpty(outputDeviceUuid)) - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - } - - if (!string.IsNullOrEmpty(jobType)) - { - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.WhichJobs, IppValue.CreateKeywordValue(jobType))); - } - - this.AddRequestedAttributesToOperationAttributeGroup(requestedAttributes, operationAttributeGroup); - var request = await IppRequest.CreateAsync(Operation.GetJobs, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Creates a Fetch-Job request - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The id of the job. - /// representation - public async Task CreateFetchJobRequestAsync(int requestId, string outputDeviceUuid, int jobId) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); - - var request = await IppRequest.CreateAsync(Operation.FetchJob, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create IPP-INFRA Acknowledge-Job request. - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The id of the job. - /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. - /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. again - /// representation - public async Task CreateAcknowledgeJobRequestAsync(int requestId, string outputDeviceUuid, int jobId, StatusCode fetchStatusCode, string fetchStatusMessage) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); - - // Undefined means the caller is not sending any value for fetchStatusCode. - if (fetchStatusCode != StatusCode.Undefined) - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.FetchStatusCode, IppValue.CreateEnumValue((int)fetchStatusCode))); - } - - // Include optional fetch-status-message attribute. - if (string.IsNullOrEmpty(fetchStatusMessage) == false) - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.FetchStatusMessage, IppValue.CreateTextWithoutLanguageValue(fetchStatusMessage))); - } - - var request = await IppRequest.CreateAsync(Operation.AcknowledgeJob, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create IPP-INFRA Acknowledge-Document request. - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The id of the job. - /// The document number (a job can have multiple documents). - /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. - /// See IPP-INFRA section 5.3.1 Acknowledge-Job Request. again - /// representation - public async Task CreateAcknowledgeDocumentRequestAsync( - int requestId, string outputDeviceUuid, int jobId, int documentNumber, StatusCode fetchStatusCode, string fetchStatusMessage) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentNumber, IppValue.CreateIntegerValue(documentNumber))); - - // Undefined means the caller is not sending any value for fetchStatusCode. - if (fetchStatusCode != StatusCode.Undefined) - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.FetchStatusCode, IppValue.CreateEnumValue((int)fetchStatusCode))); - } - - // Include optional fetch-status-message attribute. - if (string.IsNullOrEmpty(fetchStatusMessage) == false) - { - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.FetchStatusMessage, IppValue.CreateTextWithoutLanguageValue(fetchStatusMessage))); - } - - var request = await IppRequest.CreateAsync(Operation.AcknowledgeDocument, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Create IPP-INFRA Fetch-Document request. - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The id of the job. - /// The document number (a job can have multiple documents). - /// representation - public async Task CreateFetchDocumentRequestAsync(int requestId, string outputDeviceUuid, int jobId, int documentNumber) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.DocumentNumber, IppValue.CreateIntegerValue(documentNumber))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); - - var request = await IppRequest.CreateAsync(Operation.FetchDocument, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - return request; - } - - /// - /// Creates an Update-Output-device-attributes request - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// additional attributes - /// representation - public async Task CreateUpdateOutputDeviceAttributesRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalPrinterAttributes) - { - // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" - // OPTIONAL (common): (UserName+Uri) "requesting-user-name", "requesting-user-uri" - // OPTIONAL (group1): "printer-name", "copies-supported", "color-supported", "duplex-supported" - - // Generate the required required operation attributes - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - - // 'outputDeviceUuid' (aka., Device Id) is really the AADID - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - - // Generate a request for the caller with the supplied information - return await this.CreateUpdateRequestInternalAsync(Operation.UpdateOutputDeviceAttributes, requestId, operationAttributeGroup, optionalPrinterAttributes); - } - - /// - /// Creates an Get-Active-Jobs-Request - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// additional attributes - /// representation - public async Task CreateUpdateActiveJobsRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalOperationAttributes) - { - // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" - // OPTIONAL (group1): (UserName+Uri), "job-ids", "output-device-job-states", "output-device-job-reasons" - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute( - new IppAttribute(OperationAttributes.OutputDeviceUuid, IppValue.CreateCharsetValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - - if (optionalOperationAttributes != null) - { - foreach (var attr in optionalOperationAttributes.Attributes) - { - operationAttributeGroup.AddAttribute(attr.Value); - } - } - - return await this.CreateUpdateRequestInternalAsync(Operation.UpdateActiveJobs, requestId, operationAttributeGroup, null); - } - - /// - /// Creates a Update-Doument-Status request - /// - /// representation - public IppRequest CreateUpdateDocumentStatusRequest() - { - // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" - // REQUIRED (group1): "job-id", "document-number" - // OPTIONAL (group1): (UserName+Uri) - // OPTIONAL (subset of group2): "pages-completed", "output-device-document-state", "output-device-document-state-reasons" - throw new NotImplementedException(); - } - - /// - /// Creates a Update-Job-Status request - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// additional attributes - /// representation - public async Task CreateUpdateJobStatusRequestAsync(int requestId, string outputDeviceUuid, IppAttributeGroup optionalJobAttributes) - { - // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" - // REQUIRED (group1): "job-id" - // OPTIONAL (group1): (UserName+Uri) - // OPTIONAL (subset of group2): "job-pages-completed", "output-device-job-state", "output-device-job-reasons" - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute( - OperationAttributes.OutputDeviceUuid, IppValue.CreateTextWithoutLanguageValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - - if (optionalJobAttributes != null) - { - // If there is JobId in the JobAttributes group, move it to Operation group - if (optionalJobAttributes.Attributes.ContainsKey(JobAttributes.JobId)) - { - var jobId = optionalJobAttributes.Attributes[JobAttributes.JobId]; - operationAttributeGroup.AddAttribute(jobId); - optionalJobAttributes.RemoveAttribute(JobAttributes.JobId); - } - } - - return await this.CreateUpdateRequestInternalAsync(Operation.UpdateJobStatus, requestId, operationAttributeGroup, optionalJobAttributes); - } - - /// - /// Creates Microsoft's custom Set-Printer-Device-Capabilities request. - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The PDC stream. - /// representation - public async Task CreateSendPdcRequestAsync(int requestId, string outputDeviceUuid, Stream pdcData) - { - var request = await this.CreateDataRequestAsync(Operation.SetPrintDeviceCapabilities, requestId, outputDeviceUuid, pdcData); - return request; - } - - /// - /// Creates Microsoft's custom Set-Print-Capabilities request. - /// - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The PC stream. - /// representation - public async Task CreateSendPcRequestAsync(int requestId, string outputDeviceUuid, Stream pcData) - { - var request = await this.CreateDataRequestAsync(Operation.SetPrintCapabilities, requestId, outputDeviceUuid, pcData); - return request; - } - - /// - /// Creates a Create-Printer-Subscriptions request - /// - /// Request id of the IPP packet. - /// additional attributes - /// representation - public async Task CreateSubscriptionRelatedRequestAsync(Operation operation, int requestId, List optionalSubscriptionAttributeGroups) - { - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - var request = await IppRequest.CreateAsync(operation, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - if (optionalSubscriptionAttributeGroups != null) - { - foreach (var attrGroup in optionalSubscriptionAttributeGroups) - { - request.AttributeGroups.Add(attrGroup); - } - } - - return request; - } - - /// - /// - /// - /// - /// Request id of the IPP packet. - /// - /// - /// - private async Task CreateUpdateRequestInternalAsync(Operation operation, int requestId, IppAttributeGroup operationAttributes, IppAttributeGroup optionalAttributes) - { - var request = await IppRequest.CreateAsync(operation, requestId); - request.AttributeGroups.Add(operationAttributes); - if (optionalAttributes != null) - { - request.AttributeGroups.Add(optionalAttributes); - } - - return request; - } - - /// - /// Add user name and/or user uri to the provided operation attribute group. - /// - /// Attribute group to add the user name and uri attributes. - private void AddUserNameAndUserUri(IppAttributeGroup operationAttributeGroup) - { - // The user name and user uri are initialized in CreateIppRequestFactory call. - if (!string.IsNullOrEmpty(this.RequestingUserName)) - { - operationAttributeGroup.AddAttribute(new IppAttribute( - OperationAttributes.RequestingUserName, - IppValue.CreateURIValue(this.RequestingUserName))); - } - - if (!string.IsNullOrEmpty(this.RequestingUserUri)) - { - operationAttributeGroup.AddAttribute(new IppAttribute( - OperationAttributes.RequestingUserUri, - IppValue.CreateURIValue(this.RequestingUserUri))); - } - } - - /// - /// Creates a minimal IPP request with a data payload. - /// - /// The type of IPP operation to create. - /// Request id of the IPP packet. - /// The identify of the output device for the request. - /// The PC stream. - /// representation - public async Task CreateDataRequestAsync(Operation operation, int requestId, string outputDeviceUuid, Stream data) - { - // REQUIRED (common): (Lang+Charset), "printer-uri", "output-device-uuid" - var operationAttributeGroup = Helpers.CreateOperationAttributes(); - this.AddUserNameAndUserUri(operationAttributeGroup); - operationAttributeGroup.AddAttribute(new IppAttribute( - OperationAttributes.OutputDeviceUuid, IppValue.CreateTextWithoutLanguageValue(CreateOutputDeviceUuidUri(outputDeviceUuid).ToString()))); - - var request = await IppRequest.CreateAsync(operation, requestId); - request.AttributeGroups.Add(operationAttributeGroup); - request.Data = data; - return request; - } - - /// - /// Create URI format of output-device-uuid. - /// Example: urn:uuid:01234567-89AB-CDEF-FEDC-BA9876543210. - /// - /// Plain uuid. - /// - private static Uri CreateOutputDeviceUuidUri(string outputdeviceUuid) - { - const string urnScheme = "urn:"; - const string uuidPrefix = "uuid:"; - return new UriBuilder(urnScheme + uuidPrefix + outputdeviceUuid).Uri; - } - - /// - /// Adds the given list of requested attributes to the operation attributes group of the request - /// - /// Reqeusted attributes to be sent in the request - /// Operation attribute group of the request - private void AddRequestedAttributesToOperationAttributeGroup(List requestedAttributes, IppAttributeGroup operationAttributeGroup) - { - if (requestedAttributes?.Count > 0) - { - var requestedAttributesCollection = new IppAttribute(OperationAttributes.RequestedAttributes, IppValue.CreateKeywordValue(requestedAttributes[0].ValueName)); - foreach (var reqAttribute in requestedAttributes.GetRange(1, requestedAttributes.Count - 1)) - { - requestedAttributesCollection.AddAdditionalValue(IppValue.CreateKeywordValue(reqAttribute.ValueName)); - } - - operationAttributeGroup.AddAttribute(requestedAttributesCollection); - } - } - - private void AddJobUriOrId(string jobUri, int jobId, IppAttributeGroup operationAttributeGroup) - { - // See https://www.rfc-editor.org/rfc/rfc8011.html#section-4.1.5 - // for operation target attributes and their ordering - if (!((!string.IsNullOrEmpty(jobUri)) ^ (jobId != 0))) - { - throw new ArgumentException("Either the job-uri or job-id must be provided, but not both."); - } - - if (string.IsNullOrEmpty(jobUri)) - { - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.PrinterUri, IppValue.CreateURIValue(this.PrinterUri.ToString()))); - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobId, IppValue.CreateIntegerValue(jobId))); - } - else - { - operationAttributeGroup.AddAttribute(new IppAttribute(OperationAttributes.JobUri, IppValue.CreateURIValue(jobUri))); - } - } - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/IPPResponse.cs b/BadgeReleaseDemo/IppLibrary/IPPResponse.cs deleted file mode 100644 index 54a942f..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPResponse.cs +++ /dev/null @@ -1,396 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using BadgeReleaseDemo.IppLibrary.Common; - - /// - /// Represents a raw IPP response and provides serialization to a - /// stream representations. Automatically fills in the required OperationAttributes (and - /// as such creates the OperationAttributes attribute-group as well. - /// - public class IppResponse : RawIppEncodingBase - { - /// - /// The input stream for an incoming IPP response. - /// - private Stream input; - - public IppResponse(sbyte majorVersion, sbyte minorVersion, int responseId, StatusCode statusCode, string naturalLanguage = null) - { - // According to spec, any request with major version > 0 can be accepted. Ref: https://tools.ietf.org/html/rfc8010#section-9 - // "IPP objects should respond with a response containing the same "version-number" value used by the Client in the request (if the Client - supplied "version-number" is supported) or - // the highest "version-number" supported by the Printer(if the Client - supplied "version-number" is not supported)." - this.StatusCode = statusCode; - this.ResponseId = responseId; - this.MajorVersionNumber = (majorVersion != 0) ? majorVersion : (sbyte)IppMajorVersion.Version2; - this.MinorVersionNumber = (majorVersion == 0 && minorVersion == 0) ? (sbyte)IppMajorVersion.Version1 : minorVersion; - this.BaseAttributeGroups = new List(); - - // Add the REQUIRED operation attributes (3.1.4.2) to the response. - this.AddRequiredAttributes(naturalLanguage); - } - - /// - /// Initializes a new instance of the class. - /// - /// The IPP major version." - /// The IPP minor version." - /// The response ID of the the IPP response." - /// The status code of the response. - /// The detailed status message for the response. - /// The natural language of the response. - public IppResponse(sbyte majorVersion, sbyte minorVersion, int responseId, StatusCode statusCode, string detailedStatusMessage, string naturalLanguage) - : this(majorVersion, minorVersion, responseId, statusCode, naturalLanguage) - { - var operationAttributes = this.LookupAttributeGroup(Tag.OperationAttributes)[0]; - operationAttributes.AddAttribute( - new IppAttribute(OperationAttributes.DetailedStatusMessage, IppValue.CreateTextWithoutLanguageValue(detailedStatusMessage, Constants.MaxTextLength))); - } - - /// - /// Initializes a new instance of the class. - /// - private IppResponse() - { - } - - /// - /// Initializes a new instance of the class. - /// - private IppResponse(Stream input) - { - this.input = input ?? throw new InvalidOperationException("IPPRequest requires input stream."); - } - - /// - /// Gets or sets the status code of the response. - /// - public StatusCode StatusCode { get; set; } - - /// - /// Gets the response id. - /// - public int ResponseId { get; set; } - - /// - /// Gets the list of attribute groups in the response. - /// - public List AttributeGroups => this.BaseAttributeGroups; - - /// - /// Gets or sets the payload of the response. - /// - public new Stream Data - { - get => base.Data; - set => base.Data = value; - } - - /// - /// Search for attribute group matching provided tag type. - /// - /// The type of the attribute group. - /// A list of attribute groups with matching type. - public List LookupAttributeGroup(Tag type) - { - var matchedAttributes = new List(); - foreach (IppAttributeGroup attribute in this.BaseAttributeGroups) - { - if (attribute.Type == type) - { - matchedAttributes.Add(attribute); - } - } - - return matchedAttributes; - } - - /// - /// Serialize the response to string. - /// - /// String form of the response. - public override string ToString() - { - var sb = new StringBuilder(); - sb.AppendFormat(CultureInfo.InvariantCulture, "Status code {0}", this.StatusCode); - sb.AppendFormat(CultureInfo.InvariantCulture, "Response ID {0}", this.ResponseId); - sb.Append(base.ToString()); - return sb.ToString(); - } - - /// - /// Add required attributes for the response. - /// - /// Natural language of the response. - private void AddRequiredAttributes(string naturalLanguage = null) - { - var operationAttributes = new IppAttributeGroup(Tag.OperationAttributes); - - operationAttributes.AddAttribute( - new IppAttribute(IppTemplates.RequiredAttributes.AttributesCharset, IppValue.CreateCharsetValue(Constants.CharSet))); - - if (naturalLanguage == null) - { - naturalLanguage = Helpers.GetNaturalLanguage(); - } - - operationAttributes.AddAttribute( - new IppAttribute(IppTemplates.RequiredAttributes.AttributesNaturalLanguage, IppValue.CreateNaturalLanguageValue(naturalLanguage))); - - this.BaseAttributeGroups.Add(operationAttributes); - } - - /// - /// Create IppResponse object with message. - /// - public static Task CreateAsync(IppRequest ippRequest, StatusCode statusCode, string detailedStatusMessage, string naturalLanguage) - { - var response = new IppResponse() - { - StatusCode = statusCode, - ResponseId = ippRequest?.RequestId ?? 0, - MajorVersionNumber = ippRequest?.MajorVersionNumber ?? (sbyte)IppMajorVersion.Version2, - MinorVersionNumber = ippRequest?.MinorVersionNumber ?? (sbyte)IppMajorVersion.Version1 - }; - - // Add the REQUIRED operation attributes (3.1.4.2) to the response. - response.AddRequiredAttributes(naturalLanguage); - - var operationAttributes = response.LookupAttributeGroup(Tag.OperationAttributes)[0]; - operationAttributes.AddAttribute( - new IppAttribute(OperationAttributes.DetailedStatusMessage, IppValue.CreateTextWithoutLanguageValue(detailedStatusMessage, Constants.MaxTextLength))); - - return Task.FromResult(response); - } - - /// - /// Create IppResponse object without message. - /// - public static Task CreateAsync(IppRequest ippRequest, StatusCode statusCode, string naturalLanguage = null) - { - var response = new IppResponse() - { - StatusCode = statusCode, - ResponseId = ippRequest.RequestId, - MajorVersionNumber = ippRequest.MajorVersionNumber, - MinorVersionNumber = ippRequest.MinorVersionNumber, - }; - - // Add the REQUIRED operation attributes (3.1.4.2) to the response. - response.AddRequiredAttributes(naturalLanguage); - - return Task.FromResult(response); - } - - /// - /// Create the IPPResponse object from input stream. - /// - /// - /// - /// - /// - public static async Task CreateAsync(Stream input, bool stuffRemainingBytesInData, CancellationToken cancellationToken) - { - var response = new IppResponse(input); - await response.DeserializeAsync(stuffRemainingBytesInData, cancellationToken); - return response; - } - - /// - /// TBD: handle member attribute. - /// - /// - /// - public async Task DeserializeAsync(bool stuffRemainingBytesInData, CancellationToken cancellationToken) - { - var input = this.input; - var ippStream = new IPPInputStream(input); - - // Parse the header blob - try - { - this.MajorVersionNumber = await ippStream.ReadSbyteAsync(cancellationToken); - } - catch (InvalidDataException) - { - // Callers (e.g., the Connector) use this exception for indication that the Stream was disconnected. - throw new ZeroByteReadException("Initial read returned 0 bytes"); - } - - this.MinorVersionNumber = await ippStream.ReadSbyteAsync(cancellationToken); - this.StatusCode = (StatusCode)await ippStream.ReadNetworkShortAsync(cancellationToken); - this.ResponseId = await ippStream.ReadNetworkIntegerAsync(cancellationToken); - - // Read zero or more attributes from the request - var tag = await ippStream.ReadTagAsync(cancellationToken); - - while (tag != Tag.EndOfAttributes) - { - // This should be a "begin-attribute-group" tag specifying the type. - if (!Helpers.IsBeginAttributeGroupTag(tag)) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Expected a begin-attribute-group tag here."); - } - - // Form a new attribute group. - var newGroup = new IppAttributeGroup(tag); - - var groupProps = await IppEncodingUtil.DeserializeAttributeGroupAsync(this.input, newGroup, cancellationToken); - var rfcCompliant = groupProps.Item2; - tag = groupProps.Item1; - - this.BaseAttributeGroups.Add(newGroup); - } - - // copy the remaining stream part to a new one, - // if that is requested by caller - if (input.CanSeek) - { - if (input.Position < input.Length && stuffRemainingBytesInData) - { - Stream restOfDataStream = new System.IO.MemoryStream(); - // Forward the 'CancellationToken' parameter to methods - // Disabled here because the CopyToAsync overload in .net462 (MinimumIppLibraryFramework) that accepts a cancel token also requires a buffer size - // We want the library to use its internal buffer size calculator instead. -#pragma warning disable CA2016 - await input.CopyToAsync(restOfDataStream); -#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods - this.Data = restOfDataStream; - } - } - } - - /// - /// Serialize the response. - /// - public Stream Serialize() - { - var output = new MemoryStream(); - this.Serialize(output); - return output; - } - - /// - /// Serialize the response. - /// - /// The output stream. - public void Serialize(Stream output) - { - // Write out the header blob - output.WriteByte((byte)this.MajorVersionNumber); - output.WriteByte((byte)this.MinorVersionNumber); - Helpers.WriteNetworkShort(output, (short)this.StatusCode); - Helpers.WriteNetworkInteger(output, this.ResponseId); - - // Response requirement: https://tools.ietf.org/html/rfc8011#section-4.1.3 - // Later in this section, each operation is formally defined by - // identifying the allowed and expected groups of attributes for each - // request and response.The model identifies a specific order for each - // group in each request or response, but the attributes within each - // group can be in any order, unless specified otherwise. - // - // In general: operation attribute group first, unsupported attribute next, the rest of the attribute groups followed. - foreach (var atrributeGroup in this.BaseAttributeGroups) - { - if (atrributeGroup.Type != Tag.OperationAttributes) - { - continue; - } - - atrributeGroup.Serialize(output); - break; - } - - foreach (var atrributeGroup in this.BaseAttributeGroups) - { - if (atrributeGroup.Type != Tag.UnsupportedAttributes) - { - continue; - } - - atrributeGroup.Serialize(output); - break; - } - - // Begin writing out attributes if there are any - foreach (IppAttributeGroup atrributeGroup in this.BaseAttributeGroups) - { - if (atrributeGroup.Type != Tag.OperationAttributes && atrributeGroup.Type != Tag.UnsupportedAttributes) - { - atrributeGroup.Serialize(output); - } - } - - // End of attributes - output.WriteByte((byte)Tag.EndOfAttributes); - - // Write out any data if there is any. - base.Data?.CopyTo(output); - - // And we're done. - output.Seek(0, SeekOrigin.Begin); - } - - /// - /// Compare two attributes. - /// - public override bool Equals(object obj) - { - if (obj is IppResponse other) - { - if (this.ResponseId != other.ResponseId) - { - return false; - } - - if (this.StatusCode != other.StatusCode) - { - return false; - } - - if (!this.AttributeGroups.SequenceEqual(other.AttributeGroups)) - { - return false; - } - - // just compare size of the payload. - if (this.Data != null) - { - var otherDataLength = other.Data.Length - other.Data.Position; - if (this.Data.Length != otherDataLength) - { - return false; - } - } - } - else - { - return false; - } - - return true; - } - - /// - /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. - /// Attribute comparison is used by test code. - /// - public override int GetHashCode() - { - return base.GetHashCode(); - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPTemplates.cs b/BadgeReleaseDemo/IppLibrary/IPPTemplates.cs deleted file mode 100644 index d47149d..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPTemplates.cs +++ /dev/null @@ -1,512 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary.Common -{ - using System.Collections.Generic; - - public static class IppTemplates - { - /// - /// Gets a list of all supported template attributes applicable to a job - /// (as returned by get-job-attributes or get-jobs in a request for 'all' attributes) - /// https://tools.ietf.org/html/rfc8011#section-5.2 and - /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.16. - /// - public static string[] JobTemplateAttributes { get; } = - { - JobAttributes.Copies, - JobAttributes.Finishings, - JobAttributes.JobAccountId, - JobAttributes.JobAccountingUserId, - JobAttributes.JobPriority, - JobAttributes.JobHoldUntil, - JobAttributes.JobSheets, - JobAttributes.Media, - JobAttributes.MultipleDocumentHandling, - JobAttributes.NumberUp, - JobAttributes.OrientationRequested, - JobAttributes.PageRanges, - JobAttributes.PresentationDirectionNumberUp, - JobAttributes.PdfFitToPage, - JobAttributes.PrintScaling, - JobAttributes.PrinterResolution, - JobAttributes.PrinterQuality, - JobAttributes.Sides, - - // https://ftp.pwg.org/pub/pwg/candidates/cs-ippjobprinterext3v10-20120727-5100.13.pdf#page=30 - JobAttributes.JobOriginatingUserName, - JobAttributes.JobOriginatingUserUri, - - // INFRA only - JobAttributes.FeedOrientation, - JobAttributes.FinishingsCol, - JobAttributes.MediaCol, - JobAttributes.MediaBottomMargin, - JobAttributes.MediaLeftMargin, - JobAttributes.MediaRightMargin, - JobAttributes.MediaSize, - JobAttributes.MediaSource, - JobAttributes.MediaTopMargin, - JobAttributes.MediaType, - JobAttributes.OutputBin, - JobAttributes.Overrides, - JobAttributes.PrintColorMode, - JobAttributes.PrintContentOptimize, - JobAttributes.PrintRenderingIntent, - JobAttributes.PrintQuality, - JobAttributes.PrinterResolution - }; - - /// - /// Gets a list of document attributes defined as document template attributes in IPP Infra spec. - /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.12. - /// - public static string[] DocumentTemplateAttributes { get; } = - { - DocumentAttributes.Copies, - DocumentAttributes.FeedOrientation, - DocumentAttributes.Finishings, - DocumentAttributes.FinishingsCol, - DocumentAttributes.Media, - DocumentAttributes.MediaCol, - DocumentAttributes.MediaBottomMargin, - DocumentAttributes.MediaLeftMargin, - DocumentAttributes.MediaRightMargin, - DocumentAttributes.MediaSize, - DocumentAttributes.MediaSource, - DocumentAttributes.MediaTopMargin, - DocumentAttributes.MediaType, - DocumentAttributes.OrientationRequested, - DocumentAttributes.OutputBin, - DocumentAttributes.Overrides, - DocumentAttributes.PageRanges, - DocumentAttributes.PrintColorMode, - DocumentAttributes.PrintContentOptimize, - DocumentAttributes.PrintRenderingIntent, - DocumentAttributes.PrintQuality, - DocumentAttributes.PrinterResolution, - DocumentAttributes.Sides - }; - - /// - /// Gets a list of job description attributes defined in: - /// IPP INFRA: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.14. - /// IPP: https://tools.ietf.org/html/rfc8011#section-5.3. - /// - public static string[] JobDescriptionAttributes { get; } = - { - JobAttributes.JobImpressions, - JobAttributes.JobImpressionsCompleted, - JobAttributes.JobKOctets, - JobAttributes.JobMediaSheets, - JobAttributes.JobName, - JobAttributes.JobStateReasons, - JobAttributes.JobState, - JobAttributes.JobName, - JobAttributes.JobOriginatingUserName - }; - - /// - /// Gets a list of job status attributes defined in - /// IPP INFRA: http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.15. and - /// IPP: https://tools.ietf.org/html/rfc8011#section-5.3. - /// - public static string[] JobStatusAttributes { get; } = - { - JobAttributes.CompressionSupplied, - JobAttributes.DateTimeAtCompleted, - JobAttributes.DateTimeAtCreation, - JobAttributes.DateTimeAtProcessing, - JobAttributes.DocumentFormatSupplied, - JobAttributes.DocumentFormatVersionSupplied, - JobAttributes.DocumentNameSupplied, - JobAttributes.JobId, - JobAttributes.JobImpressionsCompleted, - JobAttributes.JobOriginatingUserName, - JobAttributes.JobPrinterUpTime, - JobAttributes.JobPrinterUri, - JobAttributes.JobReleaseAction, - JobAttributes.JobReleaseActionActual, - JobAttributes.JobReleaseActionId, - JobAttributes.JobState, - JobAttributes.JobStateMessage, - JobAttributes.JobStateReasons, - OperationAttributes.JobUri, - JobAttributes.JobUuid, - JobAttributes.TimeAtCompleted, - JobAttributes.TimeAtCreation, - - // IPP only. - JobAttributes.DateTimeAtProcessing, - JobAttributes.DateTimeAtCompleted, - JobAttributes.DateTimeAtCreation, - JobAttributes.DetailedStatusMessage, - JobAttributes.DocumentAccessError, - JobAttributes.JobKOctetsProcessed, - JobAttributes.JobMediaSheetsCompleted, - JobAttributes.JobMessageFromOperator, - JobAttributes.JobMoreInfo, - JobAttributes.NumberOfDocuments, - JobAttributes.NumberOfInterveningJobs, - JobAttributes.OutputDeviceAssigned, - JobAttributes.TimeAtCreation - }; - - /// - /// Gets a list of document status attributes defined in: - /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf section 4.11. - /// - public static string[] DocumentStatusAttributes { get; } = - { - OperationAttributes.AttributesCharset, - OperationAttributes.AttributesNaturalLanguage, - OperationAttributes.Compression, - DocumentAttributes.DateTimeAtCompleted, - JobAttributes.DateTimeAtCreation, - JobAttributes.DateTimeAtProcessing, - OperationAttributes.DocumentFormat, - DocumentAttributes.DocumentJobId, - OperationAttributes.DocumentNumber, - DocumentAttributes.DocumentPrinterUri, - DocumentAttributes.DocumentState, - DocumentAttributes.DocumentStateMessage, - DocumentAttributes.DocumentStateReasons, - OperationAttributes.DocumentUri, - DocumentAttributes.DocumentUuid, - DocumentAttributes.ImpressionsCompleted, - DocumentAttributes.LastDocument, - OperationAttributes.PrinterUpTime, - JobAttributes.TimeAtCompleted, - JobAttributes.TimeAtCreation, - JobAttributes.TimeAtProcessing - }; - - /// - /// Gets a list of printer description attributes. - /// RFC 8011: Printer attributes are divided into 2 groups. - /// "job-template" and "printer-description" attributes. - /// Here's the list for the printer-description attributes. - /// - public static string[] PrinterDescriptionAttributes { get; } = - { - PrinterAttributes.CharsetConfigured, - PrinterAttributes.CharsetSupported, - PrinterAttributes.ColorSupported, - PrinterAttributes.ColorModeSupported, - PrinterAttributes.ColorModeDefault, - PrinterAttributes.CompressionSupported, - PrinterAttributes.CopiesDefault, - PrinterAttributes.CopiesSupported, - PrinterAttributes.DocumentAccessSupported, - PrinterAttributes.DocumentFormatDefault, - PrinterAttributes.DocumentFormatDetailsSupported, - PrinterAttributes.DocumentFormatSupported, - PrinterAttributes.DocumentPasswordSupported, - PrinterAttributes.FeedOrientationDefault, - PrinterAttributes.FeedOrientationSupported, - PrinterAttributes.FinishingsColDatabase, - PrinterAttributes.FinishingsColDefault, - PrinterAttributes.FinishingsColReady, - PrinterAttributes.FinishingsColSupported, - PrinterAttributes.FinishingsDefault, - PrinterAttributes.FinishingsReady, - PrinterAttributes.FinishingsSupported, - PrinterAttributes.GeneratedNaturalLanguageSupported, - PrinterAttributes.IdentifyActionsDefault, - PrinterAttributes.IdentifyActionsSupported, - PrinterAttributes.IppFeaturesSupported, - PrinterAttributes.IppVersionsSupported, - PrinterAttributes.IppgetEventLife, - PrinterAttributes.JobAccountIdDefault, - PrinterAttributes.JobAccountIdSupported, - PrinterAttributes.JobAccountingUserIdDefault, - PrinterAttributes.JobAccountingUserIdSupported, - PrinterAttributes.JobConstraintsSupported, - PrinterAttributes.JobCreationAttributesSupported, - PrinterAttributes.JobIdsSupported, - PrinterAttributes.JobImpressionsSupported, - PrinterAttributes.JobMandatoryAttributesSupported, - PrinterAttributes.JobMediaSheetsSupported, - PrinterAttributes.JobPagesPerSetSupported, - PrinterAttributes.JobPasswordEncryptionSupported, - PrinterAttributes.JobPasswordSupported, - PrinterAttributes.JobPasswordLengthSupported, - PrinterAttributes.JobReleaseActionDefault, - PrinterAttributes.JobReleaseActionSupported, - PrinterAttributes.JobResolversSupported, - PrinterAttributes.MarginsPreAppliedDefault, - PrinterAttributes.MarginsPreAppliedSupported, - PrinterAttributes.MediaBottomMarginSupported, - PrinterAttributes.MediaColDatabase, - PrinterAttributes.MediaColDefault, - PrinterAttributes.MediaColReady, - PrinterAttributes.MediaColSupported, - PrinterAttributes.MediaDefault, - PrinterAttributes.MediaLeftMarginSupported, - PrinterAttributes.MediaReady, - PrinterAttributes.MediaRightMarginSupported, - PrinterAttributes.MediaSizeSupported, - PrinterAttributes.MediaSourceProperties, - PrinterAttributes.MediaSourceSupported, - PrinterAttributes.MediaSupported, - PrinterAttributes.MediaTopMarginSupported, - PrinterAttributes.MediaTypeSupported, - PrinterAttributes.MediaColorSupported, - PrinterAttributes.MicrosoftPageOrderDefault, - PrinterAttributes.MicrosoftPageOrderSupported, - PrinterAttributes.MicrosoftUniversalPrintConnectorAppVersion, - PrinterAttributes.MicrosoftUniversalPrintConnectorOperatingSystem, - PrinterAttributes.MicrosoftUniversalPrintConnectorId, - PrinterAttributes.MicrosoftUniversalPrinterDriverName, - PrinterAttributes.MicrosoftUniversalPrinterDriverVersion, - PrinterAttributes.MicrosoftUniversalPrintDocumentFormatSupportedViaConversion, - PrinterAttributes.MopriaCertified, - PrinterAttributes.MultipleDocumentHandlingDefault, - PrinterAttributes.MultipleDocumentHandlingSupported, - PrinterAttributes.MultipleDocumentJobsSupported, - PrinterAttributes.MultipleOperationTimeout, - PrinterAttributes.MultipleOperationTimeoutAction, - PrinterAttributes.NaturalLanguageConfigured, - PrinterAttributes.NotifyPullMethodSupported, - PrinterAttributes.NumberUpDefault, - PrinterAttributes.NumberUpSupported, - PrinterAttributes.OauthAuthorizationServerUri, - PrinterAttributes.OperationsSupported, - PrinterAttributes.OrientationRequestedDefault, - PrinterAttributes.OrientationRequestedSupported, - PrinterAttributes.OutputBinDefault, - PrinterAttributes.OutputBinSupported, - PrinterAttributes.OverridesSupported, - PrinterAttributes.PageRangesSupported, - PrinterAttributes.PageColorModeDefault, - PrinterAttributes.PageColorModeSupported, - PrinterAttributes.PclmRasterBackSide, - PrinterAttributes.PclmSourceResolutionSupported, - PrinterAttributes.PclmStripHeightPreferred, - PrinterAttributes.PclmStripHeightSupported, - PrinterAttributes.PdfFitToPageDefault, - PrinterAttributes.PdfFitToPageSupported, - PrinterAttributes.PdfKOctetsSupported, - PrinterAttributes.PdfSizeConstraints, - PrinterAttributes.PdfVersionsSupported, - PrinterAttributes.PdlOverrideSupported, - PrinterAttributes.PresentationDirectionNumberUpDefault, - PrinterAttributes.PresentationDirectionNumberUpSupported, - PrinterAttributes.PrintContentOptimizeDefault, - PrinterAttributes.PrintContentOptimizeSupported, - PrinterAttributes.PrintRenderingIntentDefault, - PrinterAttributes.PrintRenderingIntentSupported, - PrinterAttributes.PrintQualityDefault, - PrinterAttributes.PrintQualitySupported, - PrinterAttributes.PrintScalingDefault, - PrinterAttributes.PrintScalingSupported, - PrinterAttributes.PrintWFDS, - PrinterAttributes.PrinterDeviceId, - PrinterAttributes.PrinterGeoLocation, - PrinterAttributes.PrinterGetAttributesSupported, - PrinterAttributes.PrinterIccProfiles, - PrinterAttributes.PrinterIcons, - PrinterAttributes.PrinterInfo, - PrinterAttributes.PrinterKind, - PrinterAttributes.PrinterLocation, - PrinterAttributes.PrinterMakeAndModel, - PrinterAttributes.PrinterMoreInfoManufacturer, - PrinterAttributes.PrinterName, - PrinterAttributes.PrinterOrganization, - PrinterAttributes.PrinterOrganizationalUnit, - PrinterAttributes.PrinterOutputTray, - PrinterAttributes.PrinterResolutionDefault, - PrinterAttributes.PrinterResolutionSupported, - PrinterAttributes.PrinterStaticResourceDirectoryUri, - PrinterAttributes.PrinterStaticResourceKOctetsSupported, - PrinterAttributes.PrinterSupplyInfoUri, - PrinterAttributes.PrinterUriSupported, - PrinterAttributes.PwgRasterDocumentResolutionSupported, - PrinterAttributes.PwgRasterDocumentSheetBack, - PrinterAttributes.PwgRasterDocumentTypeSupported, - PrinterAttributes.SidesDefault, - PrinterAttributes.SidesSupported, - PrinterAttributes.WhichJobsSupported, - }; - - /// - /// Gets the list of printer status attributes. - /// - public static string[] PrinterStatusAttributes { get; } = - { - PrinterAttributes.LandscapeOrientationRequestedPreferred, - PrinterAttributes.PagesPerMinute, - PrinterAttributes.PagesPerMinuteColor, - PrinterAttributes.PrinterAlert, - PrinterAttributes.PrinterAlertDescription, - PrinterAttributes.PrinterConfigChangeDateTime, - PrinterAttributes.PrinterConfigChangeTime, - PrinterAttributes.PrinterCurrentTime, - PrinterAttributes.PrinterFirmwareName, - PrinterAttributes.PrinterFirmwarePatches, - PrinterAttributes.PrinterFirmwareStringVersion, - PrinterAttributes.PrinterFirmwareVersion, - PrinterAttributes.PrinterIsAcceptingJobs, - PrinterAttributes.PrinterMoreInfo, - PrinterAttributes.PrinterState, - PrinterAttributes.PrinterStateMessage, - PrinterAttributes.PrinterStateReasons, - PrinterAttributes.PrinterStateChangeDateTime, - PrinterAttributes.PrinterStateChangeTime, - PrinterAttributes.PrinterStateChangeMessage, - PrinterAttributes.PrinterStaticResourceKOctetsFree, - PrinterAttributes.PrinterSupply, - PrinterAttributes.PrinterSupplyDescription, - PrinterAttributes.PrinterSupplyInfoUri, - PrinterAttributes.PrinterUpTime, - PrinterAttributes.PrinterUriSupported, - PrinterAttributes.PrinterUuid, - PrinterAttributes.UrfSupported, - PrinterAttributes.UriSecuritySupported, - PrinterAttributes.UriAuthenticationSupported, - - // CUPS IPP Printer Status Attributes (Optional for Mopria 2.0 support) - PrinterAttributes.MarkerColors, - PrinterAttributes.MarkerHighLevels, - PrinterAttributes.MarkerLevels, - PrinterAttributes.MarkerLowLevels, - PrinterAttributes.MarkerNames, - PrinterAttributes.MarkerTypes, - }; - - public static HashSet PrinterDescriptionAttributesAutoPopulatedByService { get; } = new HashSet() - { - PrinterAttributes.IppFeaturesSupported, - PrinterAttributes.IppVersionsSupported, - PrinterAttributes.JobCreationAttributesSupported, - PrinterAttributes.JobImpressionsSupported, - PrinterAttributes.JobMediaSheetsSupported, - PrinterAttributes.JobPagesPerSetSupported, - PrinterAttributes.MultipleDocumentJobsSupported, - PrinterAttributes.MultipleOperationTimeout, - PrinterAttributes.MultipleOperationTimeoutAction, - PrinterAttributes.OperationsSupported, - PrinterAttributes.PrinterDeviceId, - PrinterAttributes.PrinterGeoLocation, - PrinterAttributes.PrinterIcons, - PrinterAttributes.PrinterLocation, - PrinterAttributes.PrinterMoreInfo, - PrinterAttributes.PrinterName, - PrinterAttributes.PrinterOrganization, - PrinterAttributes.PrinterOrganizationalUnit, - PrinterAttributes.PrinterSupplyInfoUri, - PrinterAttributes.PrinterUriSupported, - PrinterAttributes.PrinterUuid, - PrinterAttributes.UriAuthenticationSupported, - PrinterAttributes.UriSecuritySupported - }; - - /// - /// Must have attributes. - /// - public static class RequiredAttributes - { - public const string AttributesCharset = OperationAttributes.AttributesCharset; - public const string AttributesNaturalLanguage = OperationAttributes.AttributesNaturalLanguage; - } - - /// - /// Special set of requested attributes. - /// - public static class RequestedAttributes - { - public const string All = "all"; - public const string JobTemplate = "job-template"; - public const string JobDescription = "job-description"; - public const string PrinterDescription = "printer-description"; - } - - public static HashSet GetMopriaRequiredPrinterDescriptionAttributes(IppAttribute documentFormatSupportedAttribute) - { - var printerDescriptionAttributesRequiredByMopria = new HashSet - { - PrinterAttributes.CharsetConfigured, - PrinterAttributes.CharsetSupported, - PrinterAttributes.CopiesDefault, - PrinterAttributes.CopiesSupported, - PrinterAttributes.DocumentFormatDefault, - PrinterAttributes.DocumentFormatSupported, - PrinterAttributes.FinishingsDefault, - PrinterAttributes.FinishingsSupported, - PrinterAttributes.GeneratedNaturalLanguageSupported, - PrinterAttributes.IppFeaturesSupported, - PrinterAttributes.IppVersionsSupported, - PrinterAttributes.MediaColDatabase, - PrinterAttributes.MediaColDefault, - PrinterAttributes.MediaColSupported, - PrinterAttributes.MediaSupported, - PrinterAttributes.MediaTypeSupported, - PrinterAttributes.NaturalLanguageConfigured, - PrinterAttributes.OperationsSupported, - PrinterAttributes.OrientationRequestedDefault, - PrinterAttributes.OrientationRequestedSupported, - PrinterAttributes.OutputBinDefault, - PrinterAttributes.OutputBinSupported, - PrinterAttributes.PrintColorModeDefault, - PrinterAttributes.PrintColorModeSupported, - PrinterAttributes.PrintQualityDefault, - PrinterAttributes.PrintQualitySupported, - PrinterAttributes.PrinterIsAcceptingJobs, - PrinterAttributes.PrinterLocation, - PrinterAttributes.PrinterMakeAndModel, - PrinterAttributes.PrinterMoreInfo, - PrinterAttributes.PrinterResolutionDefault, - PrinterAttributes.PrinterResolutionSupported, - PrinterAttributes.PrinterState, - PrinterAttributes.PrinterStateReasons, - PrinterAttributes.PrinterUriSupported, - PrinterAttributes.SidesDefault, - PrinterAttributes.SidesSupported, - PrinterAttributes.UriSecuritySupported - }; - - if (documentFormatSupportedAttribute != null && documentFormatSupportedAttribute.Values != null) - { - foreach (var value in documentFormatSupportedAttribute.Values) - { - switch (value.GetNativeValue()) - { - case Constants.DocumentFormatPdf: - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PdfKOctetsSupported); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PdfVersionsSupported); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingDefault); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingSupported); - break; - - case Constants.DocumentFormatPclm: - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PageRangesSupported); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmRasterBackSide); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmSourceResolutionSupported); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmStripHeightPreferred); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PclmStripHeightSupported); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.MarginsPreAppliedDefault); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.MarginsPreAppliedSupported); - break; - - case Constants.DocumentFormatPwgRaster: - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PwgRasterDocumentResolutionSupported); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PwgRasterDocumentSheetBack); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PwgRasterDocumentTypeSupported); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingDefault); - printerDescriptionAttributesRequiredByMopria.Add(PrinterAttributes.PrintScalingSupported); - break; - - default: - break; - } - } - } - - return printerDescriptionAttributesRequiredByMopria; - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/IPPValue.cs b/BadgeReleaseDemo/IppLibrary/IPPValue.cs deleted file mode 100644 index 2796a9a..0000000 --- a/BadgeReleaseDemo/IppLibrary/IPPValue.cs +++ /dev/null @@ -1,951 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Text; - using BadgeReleaseDemo.IppLibrary.Common; - - public class IppValue - { - /// - /// Gets the value of the property. - /// - private byte[] Value { get; } - - /// - /// Gets the member attributes of the collection attribute. - /// - private List memberAttributes { get; } - - /// - /// Initializes a new instance of the class. - /// Constructs a TypeValuePair from raw data (used in deserialization and by factory methods) - /// We will validate that the size of the incoming data is appropriate for the specified type. - /// - /// The type of the attribute. - /// The value of the attribute. - /// Should only be used by unit tests to prep bad data. - public IppValue(Tag type, byte[] value, bool skipDataValidation = false) - { - if (!skipDataValidation) - { - ValidateDataSize(type, value); - } - - this.ValueType = type; - this.ExtendedType = 0; - this.Value = value; - - if (type == Tag.BegCollection) - { - this.memberAttributes = new List(); - } - } - - /// - /// Initializes a new instance of the class. - /// - /// The extended type of the attribute. - /// The value of the attribute. - public IppValue(int extendedType, byte[] value) - { - // Note: In general we can't validate the size of the value here. - // As we add support for extended types we can add validation for them. - this.ValueType = Tag.TypeExtension; - this.ExtendedType = extendedType; - this.Value = value; - } - - /// - /// Initializes a new instance of the class. - /// - /// The type of the attribute. - public IppValue(Tag type) - { - this.ValueType = type; - this.ExtendedType = 0; - if (type == Tag.BegCollection) - { - this.memberAttributes = new List(); - } - } - - /// - /// Gets the value type of the property. - /// - public Tag ValueType { get; } - - /// - /// Gets the extended type of the property. - /// - public int ExtendedType { get; } - - /// - /// Gets the member attributes of the collection attribute. - /// - public IReadOnlyList MemberAttributes => this.memberAttributes?.AsReadOnly(); - - /// - /// Factory constructors for IPP types. These are used to construct and encode TypeValuePairs for IPP responses - /// - /// The value for the integer attribute. - /// The integer attribute. - public static IppValue CreateIntegerValue(int value) - { - return new IppValue(Tag.Integer, Helpers.IntegerToByteArray(value)); - } - - /// - /// Creates a boolean attribute. - /// - /// The value for the boolean attribute. - /// The boolean attribute. - public static IppValue CreateBooleanValue(bool value) - { - return new IppValue(Tag.Boolean, Helpers.BoolToByteArray(value)); - } - - /// - /// Creates an enum attribute. - /// - /// The value for the enum attribute. - /// The enum attribute. - public static IppValue CreateEnumValue(int value) - { - return new IppValue(Tag.Enum, Helpers.IntegerToByteArray(value)); - } - - /// - /// Creates an octet string attribute. - /// - /// The values of the octet string attribute. - /// The octet string attribute. - public static IppValue CreateOctetStringValue(byte[] value) - { - // For clarity: an OCTET-STRING in IPP is not a text string (necessarily). - // it's just one or more bytes (effectively an array). - return new IppValue(Tag.OctetString, value); - } - - /// - /// Creates an octet string attribute. - /// - /// The values of the octet string attribute. - /// The octet string attribute. - public static IppValue CreateBadOctetStringValueForUnitTest(byte[] value) - { - // For clarity: an OCTET-STRING in IPP is not a text string (necessarily). - // it's just one or more bytes (effectively an array). - return new IppValue(Tag.OctetString, value, skipDataValidation: true); - } - - /// - /// Creates a date time attribute. - /// - /// The value of the attribute. - /// A date time attribute object. - public static IppValue CreateDateTimeValue(DateTime value) - { - return new IppValue(Tag.DateTime, Helpers.DateTimeToByteArray(value)); - } - - /// - /// Creates a resolution attribute. - /// - /// The x value of the attribute. - /// The y value of the attribute. - /// The unit of the attribute. - /// A resolution attribute object. - public static IppValue CreateResolutionValue(int x, int y, sbyte units) - { - return new IppValue(Tag.Resolution, Helpers.ResolutionToByteArray(x, y, units)); - } - - /// - /// Creates a range of integer attribute. - /// - /// The lower range of the value. - /// The upper range of the value. - /// A range of integer attribute object. - public static IppValue CreateRangeOfIntegerValue(int lower, int upper) - { - return new IppValue(Tag.RangeOfInteger, Helpers.IntegerRangeToByteArray(lower, upper)); - } - - /// - /// Creates a text with language value attribute. - /// - /// The value of the text. - /// The value of the natural language. - /// A text with natural language attribute. - public static IppValue CreateTextWithLanguageValue(string theString, string naturalLanguage) - { - return new IppValue(Tag.TextWithLanguage, Helpers.StringWithNaturalLanguageToByteArray(theString, naturalLanguage)); - } - - /// - /// Creates a name with language value attribute. - /// - /// The value of the name string. - /// The value of the natural language. - /// A name with natural language attribute. - public static IppValue CreateNameWithLanguageValue(string theString, string naturalLanguage) - { - return new IppValue(Tag.NameWithLanguage, Helpers.StringWithNaturalLanguageToByteArray(theString, naturalLanguage)); - } - - /// - /// Creates a text without language value. - /// - /// The value of the text. - /// The maximum length of the text. - /// A text without natural language attribute. - public static IppValue CreateTextWithoutLanguageValue(string value, int maxLength = Constants.MaxTextLength) - { - // Truncate strings longer than maxLength - if (value.Length > maxLength) - { - value = value.Substring(0, maxLength); - } - - // Note: Since we are only supporting UTF-8 encodings for both requests and responses, - // we don't need or care to support specifying an encoding here, we just always - // encode the string into UTF-8 in the byte array, which StringToByteArray does. - return new IppValue(Tag.TextWithoutLanguage, Helpers.StringToUTF8ByteArray(value)); - } - - /// - /// Creates a name without language value. - /// - /// The value of the name. - /// The maximum length of the name. - /// A name without language value attribute. - public static IppValue CreateNameWithoutLanguageValue(string value, int maxLength = Constants.MaxNameLength) - { - // Truncate strings longer than maxLength - if (value.Length > maxLength) - { - value = value.Substring(0, maxLength); - } - - // Note: See above comments. - return new IppValue(Tag.NameWithoutLanguage, Helpers.StringToUTF8ByteArray(value)); - } - - /// - /// Creates a key word attribute. - /// - /// The value of the keyword. - /// A keyword attribute. - public static IppValue CreateKeywordValue(string value) - { - if (value.Length > Constants.MaxKeywordLength) - { - throw new IPPException(StatusCode.ServerErrorInternalError, string.Format(CultureInfo.InvariantCulture, "Keyword value is greater than maximum keyword length: {0}", value)); - } - - // Note: Keywords are always encoded as US-ASCII; this means that anything unicode - // in the incoming string is ignored (we strip off the top 8 bits). - return new IppValue(Tag.Keyword, Helpers.StringToASCIIByteArray(value)); - } - - /// - /// Creates a uri attribute. - /// - /// The uri value. - /// A uri attribute object. - public static IppValue CreateURIValue(string value) - { - if (value.Length > Constants.MaxUriLength) - { - throw new IPPException(StatusCode.ServerErrorInternalError, string.Format(CultureInfo.InvariantCulture, "Uri value is greater than maximum url length: {0}", value)); - } - - return new IppValue(Tag.Uri, Helpers.StringToASCIIByteArray(value)); - } - - /// - /// Creates a uri scheme attribute. - /// - /// The value of the uri scheme. - /// A uri scheme attribute object. - public static IppValue CreateUriSchemeValue(string value) - { - return new IppValue(Tag.UriScheme, Helpers.StringToASCIIByteArray(value)); - } - - /// - /// Creates a charset value attribute. - /// - /// The value of the attribute. - /// The new charset attribute object. - public static IppValue CreateCharsetValue(string value) - { - return new IppValue(Tag.Charset, Helpers.StringToASCIIByteArray(value)); - } - - /// - /// Creates a natural language attribute. - /// - /// The value of the natural language. - /// The new attribute. - public static IppValue CreateNaturalLanguageValue(string value) - { - return new IppValue(Tag.NaturalLanguage, Helpers.StringToASCIIByteArray(value)); - } - - /// - /// Creates a mime media type attribute. - /// - /// The mime value. - /// The newly created attribute. - public static IppValue CreateMimeMediaTypeValue(string value) - { - if (value.Length > Constants.MaxMimeTypeLength) - { - throw new IPPException(StatusCode.ServerErrorInternalError, string.Format(CultureInfo.InvariantCulture, "Mime type value is greater than maximum mime type length: {0}", value)); - } - - return new IppValue(Tag.MimeMediaType, Helpers.StringToASCIIByteArray(value)); - } - - /// - /// Creates no-value value attribute. - /// - /// A value with Tag.NoVaue. - public static IppValue CreateNoValueValue() - { - // Empty byte array. - return new IppValue(Tag.NoValue, new byte[] { }); - } - - /// - /// Creates unknown value attribute. - /// - /// A value with Tag.Unknown. - public static IppValue CreateUnknownValue() - { - // Empty byte array. - return new IppValue(Tag.Unknown, new byte[] { }); - } - - /// - /// Creates collection attribute value. - /// - /// A beg collection. - public static IppValue CreateCollectionAttributeValue() - { - return new IppValue(Tag.BegCollection); - } - - /// - /// Adds member attribute to the collection value - /// - /// Member attribute to be added to the current collection value - public void AddMemberAttribute(IppMemberAttribute memberAttribute) - { - if (this.IsCollectionAttributeValue()) - { - this.memberAttributes.Add(memberAttribute); - } - else - { - throw new IPPException(StatusCode.ClientErrorBadRequest, "Cannot add member attributes to a non-collection attribute"); - } - } - - /// - /// Adds member attribute to the collection value - /// - /// Name to use for the member attribute. - /// Member attribute to be added to the current collection value - public void AddMemberAttribute(string memberAttributeName, IppValue memberAttributeValue) - { - this.AddMemberAttribute(new IppMemberAttribute(memberAttributeName, memberAttributeValue)); - } - - public bool IsCollectionAttributeValue() - { - return this.ValueType == Tag.BegCollection; - } - - public bool IsOutOfBandValue() - { - return this.ValueType == Tag.Unknown || this.ValueType == Tag.NoValue; - } - - /// - /// Returns the serialized binary value - /// - public byte[] GetSerializedValue() - { - return this.Value; - } - - /// - /// Returns the value as a .NET type (used for consuming the value) - /// - public T GetNativeValue() - { - return (T)this.GetNativeValue(); - } - - /// - /// Returns the value as a .NET type (used for consuming the value) - /// - public object GetNativeValue() - { - object value = null; - - switch (this.ValueType) - { - case Tag.Resolution: - value = Helpers.ByteArrayToResolution(this.Value); - break; - - case Tag.DateTime: - value = Helpers.ByteArrayToDateTime(this.Value); - break; - - case Tag.Integer: - case Tag.Enum: - value = Helpers.ByteArrayToInteger(this.Value); - break; - - case Tag.RangeOfInteger: - value = Helpers.ByteArrayToIntegerRange(this.Value); - break; - - case Tag.Boolean: - value = Helpers.ByteArrayToBool(this.Value); - break; - - case Tag.OctetString: - value = this.Value; - break; - - case Tag.TextWithLanguage: - case Tag.NameWithLanguage: - // Untill we find some customer scenarioes that need the language code, we decided to only give back the string value. - // At least Windows client side does NOT need to do any parsing or casing based on the language code. - value = Helpers.ByteArrayToStringWithNaturalLanguage(this.Value).Item1; - break; - - /* - * https://tools.ietf.org/html/rfc8010#section-3 - * 3.9. (Attribute) "value". - * NOTE: textWithoutLanguage is LOCALIZED - STRING - +----------------------+--------------------------------------------+ - - | Syntax of Attribute | Encoding | - - | Value | | - +----------------------+--------------------------------------------+ - - | textWithoutLanguage, | LOCALIZED - STRING - */ - case Tag.TextWithoutLanguage: - case Tag.NameWithoutLanguage: - value = Helpers.UTF8ByteArrayToString(this.Value); - break; - - case Tag.Keyword: - case Tag.Uri: - case Tag.UriScheme: - case Tag.Charset: - case Tag.NaturalLanguage: - case Tag.MimeMediaType: - value = Helpers.ASCIIByteArrayToString(this.Value); - break; - - case Tag.TypeExtension: - throw new NotImplementedException("Extended types not supported yet."); - - case Tag.NoValue: - case Tag.Unknown: - value = null; - break; - - case Tag.BegCollection: - throw new IPPException(StatusCode.ClientErrorBadRequest, "Collection attribute cannot have a native value."); - default: - throw new IPPException(StatusCode.ClientErrorBadRequest, "Unhandled attribute type."); - } - - return value; - } - - /// - /// Serializes this pair as an "attribute-with-one-value" if attribute name is non-empty - /// Serializes this pair as an "additional-value" if attribute name is empty or null - /// - /// The output stream. - /// The name of the attribute. - public void Serialize(Stream output, string attributeName = null) - { - if (this.IsCollectionAttributeValue()) - { - // Setup the collection attribute: https://tools.ietf.org/html/rfc8010#section-3.1.6 - output.WriteByte((byte)Tag.BegCollection); // value-tag This is a collection attribute: 0x34. - - if (string.IsNullOrEmpty(attributeName)) - { - Helpers.WriteNetworkShort(output, (short)0x0000); // name-length Set to 0 for additional collection attribute values. see https://tools.ietf.org/html/rfc8010#section-3.1.6. - } - else - { - Helpers.WriteNetworkShort(output, (short)attributeName.Length); // name-length Length of the attribute name. - Helpers.WriteAsciiString(output, attributeName); // name Attribute name. - } - - Helpers.WriteNetworkShort(output, (short)0x00); // value-length Always 0, signifying a collection attribute. - - // Serialize the member attributes. - for (var i = 0; i < this.memberAttributes.Count; i++) - { - this.memberAttributes[i].SerializeMemberAttribute(output, 1); // member-attribute The serialized content of all member attributes. - } - - // Complete the collection attribute. - output.WriteByte((byte)Tag.EndCollection); // end-value-tag Always 0x37 denoting end of the collection. - Helpers.WriteNetworkShort(output, (short)0x00); // end-name-length Always 0x0000. - Helpers.WriteNetworkShort(output, (short)0x00); // end-value-length Always 0x0000. - } - else - { - output.WriteByte((byte)this.ValueType); // value-tag Type of attribute. - - if (string.IsNullOrEmpty(attributeName)) - { - Helpers.WriteNetworkShort(output, (short)0x0000); // name-length 0x0000 for additional values. - } - else - { - Helpers.WriteNetworkShort(output, (short)attributeName.Length); // name-length Length of the attribute name. - Helpers.WriteAsciiString(output, attributeName); // name Attribute name. - } - - Helpers.WriteNetworkShort(output, (short)this.Value.Length); // value-length Length of the attribute value. - output.Write(this.Value, 0, this.Value.Length); // value Value of the attribute. - } - } - - /// - /// Serializes a member attribute. - /// - /// The output stream. - public void SerializeAsMemberAttribute(Stream output, string memberAttributeName, int collectionDepth) - { - // Setup the member attribute: https://tools.ietf.org/html/rfc8010#section-3.1.6 - if (this.IsCollectionAttributeValue()) - { - const int MaxCollectionDepthAllowed = 5; - - // Limit how many layers of collection of collection are allowed. - if (collectionDepth++ > MaxCollectionDepthAllowed) - { - throw new IPPException( - StatusCode.ClientErrorBadRequest, - FormattableString.Invariant($"Max collection depth of {MaxCollectionDepthAllowed} reached.")); - } - - // Setup this collection attribute as a member attribute. - output.WriteByte((byte)Tag.MemberAttrName); // value-tag This is a member attribute: 0x4a. - Helpers.WriteNetworkShort(output, (short)0x00); // name-length Always 0, signifying a member attr. - - if (string.IsNullOrEmpty(memberAttributeName)) - { - Helpers.WriteNetworkShort(output, (short)0x0000); // name-length 0x0000 for additional values. - } - else - { - Helpers.WriteNetworkShort(output, (short)memberAttributeName.Length); // value-length Length of member-name. - Helpers.WriteAsciiString(output, memberAttributeName); // value The member-name. - } - - output.WriteByte((byte)Tag.BegCollection); // member-value-tag This is a collection member attribute. - Helpers.WriteNetworkShort(output, (short)0x00); // name-length Second name length, always 0. - Helpers.WriteNetworkShort(output, (short)0x00); // member-value-length Always zero as what follows are member attributes. - - // Serialize the member attributes. - foreach (var memberAttribute in this.memberAttributes) - { - memberAttribute.SerializeMemberAttribute(output, collectionDepth); - } - - // Complete this collection attribute. - output.WriteByte((byte)Tag.EndCollection); // end-value-tag Always 0x37 denoting end of the collection. - Helpers.WriteNetworkShort(output, (short)0x00); // end-name-length Always 0x0000. - Helpers.WriteNetworkShort(output, (short)0x00); // end-value-length Always 0x0000. - } - else - { - output.WriteByte((byte)Tag.MemberAttrName); // value-tag This is a member attribute: 0x4a. - Helpers.WriteNetworkShort(output, (short)0x0000); // name-length Always 0, signifying a member attr. - - if (string.IsNullOrEmpty(memberAttributeName)) - { - Helpers.WriteNetworkShort(output, (short)0x0000); // name-length 0x0000 for additional values. - } - else - { - Helpers.WriteNetworkShort(output, (short)memberAttributeName.Length); // value-length Length of member-name. - Helpers.WriteAsciiString(output, memberAttributeName); // value The member-name. - } - - output.WriteByte((byte)this.ValueType); // member-value-tag Type of member attribute. - Helpers.WriteNetworkShort(output, (short)0x0000); // name-length Second name length, always 0. - Helpers.WriteNetworkShort(output, (short)this.Value.Length); // member-value-length Length of the member attribute value. - output.Write(this.Value, 0, this.Value.Length); // member-value The value of the member attribute. - } - } - - public override string ToString() - { - if (this.IsCollectionAttributeValue()) - { - var sb = new StringBuilder(); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Collection Attribute Value - Member Attribute list:\n"); - foreach (var memberAttribute in this.memberAttributes) - { - sb.AppendFormat(CultureInfo.InvariantCulture, "\t" + memberAttribute.ToString() + "\n"); - } - - return sb.ToString(); - } - else - { - return string.Format( - CultureInfo.InvariantCulture, - "SimpleIppValue-Type:{0}-Value:{1}\n", - this.ValueType, - (object)this.GetNativeValue()); - } - } - - /// - /// Avoid warning, need to override when overriding Object.Equals(). Nothing special here, rely on Equals. - /// Attribute comparison is used by test code. - /// - public override int GetHashCode() - { - return base.GetHashCode(); - } - - /// - /// Compare two IppValue objects. - /// - public override bool Equals(object obj) - { - if (obj is IppValue other) - { - if (this.IsCollectionAttributeValue()) - { - var isCollectionValueEqual = this.IsCollectionAttributeValue().Equals(other.IsCollectionAttributeValue()); - if (isCollectionValueEqual) - { - var areMemberAttributesEqual = this.memberAttributes.SequenceEqual(other.memberAttributes); - return areMemberAttributesEqual; - } - } - else - { - var isValueTypeEqual = this.ValueType.Equals(other.ValueType); - var isValueEqual = this.Value.SequenceEqual(other.Value); - return isValueTypeEqual && isValueEqual; - } - } - - return false; - } - - /// - /// Return a member attribute with name that matches memberAttributeName. - /// - /// The name of the member attribute. - /// The member attribute. - public IppMemberAttribute GetMemberAttribute(string memberAttributeName) - { - if (!this.IsCollectionAttributeValue()) - { - return null; - } - - return this.memberAttributes.Find(x => string.Equals(x.ValueName, memberAttributeName, StringComparison.Ordinal)); - } - - /// - /// Return a member attribute with name that matches memberAttributeName. - /// - /// The name of the member attribute. - /// The member attribute. - public object GetMemberAttributeNativeValue(string memberAttributeName) - { - if (!this.IsCollectionAttributeValue()) - { - return null; - } - - return this.memberAttributes.Find(x => string.Equals(x.ValueName, memberAttributeName, StringComparison.Ordinal)).FirstValue.GetNativeValue(); - } - - public static bool IsUPKnownMediaTypeKeyword(string mediaType) - { - switch (mediaType.ToLower()) - { - case MediaTypes.Aluminum: - case MediaTypes.Auto: - case MediaTypes.BackPrintFilm: - case MediaTypes.Cardboard: - case MediaTypes.Cardstock: - case MediaTypes.CardstockCoated: - case MediaTypes.CardstockHeavyweight: - case MediaTypes.CardstockHeavyweightCoated: - case MediaTypes.CardstockLightweight: - case MediaTypes.CardstockLightweightCoated: - case MediaTypes.Cd: - case MediaTypes.Continuous: - case MediaTypes.ContinuousLong: - case MediaTypes.ContinuousShort: - case MediaTypes.CorrugatedBoard: - case MediaTypes.Disc: - case MediaTypes.DiscGlossy: - case MediaTypes.DiscHighGloss: - case MediaTypes.DiscMatte: - case MediaTypes.DiscSatin: - case MediaTypes.DiscSemiGloss: - case MediaTypes.DoubleWall: - case MediaTypes.DryFilm: - case MediaTypes.Dvd: - case MediaTypes.EmbossingFoil: - case MediaTypes.EndBoard: - case MediaTypes.Envelope: - case MediaTypes.EnvelopeArchival: - case MediaTypes.EnvelopeBond: - case MediaTypes.EnvelopeCoated: - case MediaTypes.EnvelopeCotton: - case MediaTypes.EnvelopeFine: - case MediaTypes.EnvelopeHeavyweight: - case MediaTypes.EnvelopeInkjet: - case MediaTypes.EnvelopeLightweight: - case MediaTypes.EnvelopePlain: - case MediaTypes.EnvelopePreprinted: - case MediaTypes.EnvelopeWindow: - case MediaTypes.Fabric: - case MediaTypes.FabricArchival: - case MediaTypes.FabricGlossy: - case MediaTypes.FabricHighGloss: - case MediaTypes.FabricMatte: - case MediaTypes.FabricSemiGloss: - case MediaTypes.FabricWaterproof: - case MediaTypes.Film: - case MediaTypes.FlexoBase: - case MediaTypes.FlexoPhotoPolymer: - case MediaTypes.Flute: - case MediaTypes.Foil: - case MediaTypes.FullCutTabs: - case MediaTypes.Glass: - case MediaTypes.GlassColored: - case MediaTypes.GlassOpaque: - case MediaTypes.GlassSurfaced: - case MediaTypes.GlassTextured: - case MediaTypes.GravureCylinder: - case MediaTypes.ImageSetterPaper: - case MediaTypes.ImagingCylinder: - case MediaTypes.Labels: - case MediaTypes.LabelsColored: - case MediaTypes.LabelsContinuous: - case MediaTypes.LabelsGlossy: - case MediaTypes.LabelsHeavyweight: - case MediaTypes.LabelsHighGloss: - case MediaTypes.LabelsInkjet: - case MediaTypes.LabelsLightweight: - case MediaTypes.LabelsMatte: - case MediaTypes.LabelsPermanent: - case MediaTypes.LabelsSatin: - case MediaTypes.LabelsSecurity: - case MediaTypes.LabelsSemiGloss: - case MediaTypes.LaminatingFoil: - case MediaTypes.Letterhead: - case MediaTypes.Metal: - case MediaTypes.MetalGlossy: - case MediaTypes.MetalHighGloss: - case MediaTypes.MetalMatte: - case MediaTypes.MetalSatin: - case MediaTypes.MetalSemiGloss: - case MediaTypes.MountingTape: - case MediaTypes.MultiLayer: - case MediaTypes.MultiPartForm: - case MediaTypes.Other: - case MediaTypes.Paper: - case MediaTypes.Photographic: - case MediaTypes.PhotographicArchival: - case MediaTypes.PhotographicFilm: - case MediaTypes.PhotographicGlossy: - case MediaTypes.PhotographicHighGloss: - case MediaTypes.PhotographicMatte: - case MediaTypes.PhotographicSatin: - case MediaTypes.PhotographicSemiGloss: - case MediaTypes.Plastic: - case MediaTypes.PlasticArchival: - case MediaTypes.PlasticColored: - case MediaTypes.PlasticGlossy: - case MediaTypes.PlasticHighGloss: - case MediaTypes.PlasticMatte: - case MediaTypes.PlasticSatin: - case MediaTypes.PlasticSemiGloss: - case MediaTypes.Plate: - case MediaTypes.Polyester: - case MediaTypes.PreCutTabs: - case MediaTypes.Roll: - case MediaTypes.Screen: - case MediaTypes.ScreenPaged: - case MediaTypes.SelfAdhesive: - case MediaTypes.SelfAdhesiveFilm: - case MediaTypes.ShrinkFoil: - case MediaTypes.SingleFace: - case MediaTypes.SingleWall: - case MediaTypes.Sleeve: - case MediaTypes.Stationery: - case MediaTypes.StationeryArchival: - case MediaTypes.StationeryBond: - case MediaTypes.StationeryCoated: - case MediaTypes.StationeryColored: - case MediaTypes.StationeryCotton: - case MediaTypes.StationeryFine: - case MediaTypes.StationeryHeavyweight: - case MediaTypes.StationeryHeavyweightCoated: - case MediaTypes.StationeryInkjet: - case MediaTypes.StationeryLetterhead: - case MediaTypes.StationeryLightweight: - case MediaTypes.StationeryPreprinted: - case MediaTypes.StationeryPrepunched: - case MediaTypes.StationeryRecycled: - case MediaTypes.TabStock: - case MediaTypes.Tractor: - case MediaTypes.Transfer: - case MediaTypes.Transparency: - case MediaTypes.TripleWall: - case MediaTypes.WetFilm: - return true; - - default: - return false; - } - } - - /// - /// Validate that there are expected number of bytes for the given type. - /// - private static void ValidateDataSize(Tag type, byte[] value) - { - var invalid = false; - switch (type) - { - case Tag.Integer: - case Tag.Enum: - invalid = value.Length != 4; - break; - - case Tag.RangeOfInteger: - invalid = value.Length != 8; - break; - - case Tag.Boolean: - invalid = value.Length != 1; - break; - - case Tag.OctetString: - // See: https://tools.ietf.org/html/rfc8011#section-5.1.11 - invalid = value.Length > 1023; - break; - - case Tag.DateTime: - // See:https://tools.ietf.org/html/rfc2579 page 18. - invalid = value.Length != 11; - break; - - case Tag.Resolution: - // See: https://tools.ietf.org/html/rfc8011#section-5.1.16. - invalid = value.Length != 9; - break; - - case Tag.TextWithLanguage: - case Tag.NameWithLanguage: - // Note: from the RFC: - // a. a SIGNED-SHORT which is the number of - // octets in the following field - // b. a value of type natural-language, - // c. a SIGNED-SHORT which is the number of - // octets in the following field, - // d. a value of type textWithoutLanguage. - // The length of a textWithLanguage value MUST be - // 2 + the value of field a + 2 + the value of field c. - - // Validate that the buffer is at least 4 bytes long (the - // degenerate case). - invalid = value.Length < 4; - - // Validate the length of the buffer based on the length fields - if (!invalid) - { - short nlLength = Helpers.ByteArrayToShort(value); - invalid = value.Length < 2 + nlLength; - - if (!invalid) - { - short textLength = (short)(ushort)((value[2 + nlLength] << 8) | value[2 + nlLength + 1]); - invalid = value.Length != 2 + nlLength + 2 + textLength; - } - } - - break; - - case Tag.TextWithoutLanguage: - case Tag.NameWithoutLanguage: - case Tag.Keyword: - case Tag.Uri: - case Tag.UriScheme: - case Tag.Charset: - case Tag.NaturalLanguage: - case Tag.MimeMediaType: - // These cases require no specific validation, they are string values. - // For some of these (keyword, name, etc) having a zero-length string - // seems odd, but I can find nothing in the RFC specifically disallowing it. - invalid = false; - break; - - case Tag.NoValue: - case Tag.Unknown: - invalid = value.Length != 0; - break; - - case Tag.BegCollection: - // TBD: check that this is a valid collection attribute. - invalid = false; - break; - - default: - throw new IPPException(StatusCode.ClientErrorBadRequest, "Invalid type in encoding."); - } - - if (invalid) - { - throw new IPPException(StatusCode.ClientErrorBadRequest, FormattableString.Invariant($"Invalid encoding for type {type}.")) - { - DetailedInternalInfo = FormattableString.Invariant($"Invalid encoding for type {type}. Got {value.Length} bytes of data '{BitConverter.ToString(value).Replace("-", string.Empty)}'") - }; - } - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/JobAttributes.cs b/BadgeReleaseDemo/IppLibrary/JobAttributes.cs deleted file mode 100644 index e59d4a4..0000000 --- a/BadgeReleaseDemo/IppLibrary/JobAttributes.cs +++ /dev/null @@ -1,214 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - /// - /// A list of job attributes defined in the following IPP specs: - /// https://tools.ietf.org/html/rfc8011 - /// http://ftp.pwg.org/pub/pwg/candidates/cs-ippinfra10-20150619-5100.18.pdf - /// A list of job template attributes with "-actual" keyword suffix - /// https://ftp.pwg.org/pub/pwg/candidates/cs-ippactuals10-20030313-5100.8.pdf - /// - public static class JobAttributes - { - public const string CompressionSupplied = "compression-supplied"; - public const string Copies = DocumentAttributes.Copies; - - /// - /// Indicates the actual value of copies printed from the job's PT. - /// - public const string CopiesActual = DocumentAttributes.Copies + "-actual"; - - public const string CoverType = "cover-type"; - public const string DateTimeAtCompleted = "date-time-at-completed"; - public const string DateTimeAtCreation = "date-time-at-creation"; - public const string DateTimeAtProcessing = "date-time-at-processing"; - public const string DetailedStatusMessage = "detailed-status-message"; - public const string DocumentAccessError = "document-access-error"; - public const string DocumentFormatSupplied = "document-format-supplied"; - public const string DocumentFormatVersionSupplied = "document-format-version-supplied"; - public const string DocumentNameSupplied = "document-name-supplied"; - public const string FeedOrientation = DocumentAttributes.FeedOrientation; - public const string Finishings = DocumentAttributes.Finishings; - public const string FinishingsCol = DocumentAttributes.FinishingsCol; - public const string IppAttributeFidelity = OperationAttributes.IppAttributeFidelity; - public const string JobHoldUntil = "job-hold-until"; - public const string JobAccountId = "job-account-id"; - public const string JobAccountingUserId = "job-accounting-user-id"; - public const string JobId = OperationAttributes.JobId; - public const string JobImpressions = "job-impressions"; - public const string JobImpressionsCompleted = "job-impressions-completed"; - public const string JobKOctets = "job-k-octets"; - public const string JobKOctetsCompleted = "job-k-octets-completed"; - public const string JobKOctetsProcessed = "job-k-octets-processed"; - public const string JobMandatoryAttributes = "job-mandatory-attributes"; - public const string JobMediaSheets = "job-media-sheets"; - public const string JobMediaSheetsCompleted = "job-media-sheets-completed"; - public const string JobMessageFromOperator = "job-message-from-operator"; - public const string JobMoreInfo = "job-more-info"; - public const string JobName = "job-name"; - public const string JobOriginatingUserName = "job-originating-user-name"; - public const string JobOriginatingUserUri = "job-originating-user-uri"; - public const string JobPagesCompleted = "job-pages-completed"; - public const string JobPagesPerSet = "job-pages-per-set"; - public const string JobPriority = "job-priority"; - public const string JobPrinterUpTime = "job-printer-up-time"; - public const string JobPrinterUri = "job-printer-uri"; - public const string JobReleaseAction = "job-release-action"; - public const string JobReleaseActionId = "job-release-action-id"; - public const string JobReleaseActionActual = "job-release-action-actual"; - public const string JobSheets = "job-sheets"; - public const string JobState = "job-state"; - public const string JobStateMessage = "job-state-message"; - public const string JobStateReasons = "job-state-reasons"; - public const string JobUuid = "job-uuid"; - public const string JobUri = OperationAttributes.JobUri; - public const string Media = DocumentAttributes.Media; - public const string MediaBackCoating = DocumentAttributes.MediaBackCoating; - public const string MediaBottomMargin = DocumentAttributes.MediaBottomMargin; - public const string MediaCol = DocumentAttributes.MediaCol; - public const string MediaColor = DocumentAttributes.MediaColor; - public const string MediaGrain = DocumentAttributes.MediaGrain; - public const string MediaFrontCoating = DocumentAttributes.MediaFrontCoating; - public const string MediaHoleCount = DocumentAttributes.MediaHoleCount; - public const string MediaInfo = DocumentAttributes.MediaInfo; - public const string MediaKey = DocumentAttributes.MediaKey; - public const string MediaLeftMargin = DocumentAttributes.MediaLeftMargin; - public const string MediaOrderCount = DocumentAttributes.MediaOrderCount; - public const string MediaPreprinted = DocumentAttributes.MediaPreprinted; - public const string MediaRecycled = DocumentAttributes.MediaRecycled; - public const string MediaRightMargin = DocumentAttributes.MediaRightMargin; - public const string MediaSize = DocumentAttributes.MediaSize; - public const string MediaSizeName = DocumentAttributes.MediaSizeName; - public const string MediaSource = DocumentAttributes.MediaSource; - public const string MediaThickness = DocumentAttributes.MediaThickness; - public const string MediaTooth = DocumentAttributes.MediaTooth; - public const string MediaTopMargin = DocumentAttributes.MediaTopMargin; - public const string MediaType = DocumentAttributes.MediaType; - public const string MediaWeightMetric = DocumentAttributes.MediaWeightMetric; - - // Attribute extensions - // See reference: https://tools.ietf.org/html/rfc8011#section-7.2 - - /// - /// Only used by the first party Connector. - /// Indicates the time (in seconds) it took the Connector to fetch the print job. - /// Excludes the time that the job spent waiting to be fetched, - /// i.e. it is only the total time taken by Connector to perform the Acknowledge/Fetch-Job/Document operations. - /// - public const string MicrosoftJobFetchedTimeInSeconds = "microsoft-job-fetched-time-seconds"; - - /// - /// Only used by the first party Connector. - /// Indicates the time (in seconds) it took for the print job to be processed by the Connector before the job was sent to the spooler. - /// Excludes the time that was spent on fetching the job, i.e. timing starts after the Connector finished fetching the job. - /// - public const string MicrosoftJobProcessedTimeInSeconds = "microsoft-job-processed-time-seconds"; - - /// - /// Only used by the first party Connector before 2.2. - /// Indicates the time (in seconds) that the job spent in the Windows spooler before being sent to the printer. - /// Replaced by MicrosoftJobSpoolerTimeInSeconds to reduce confusion. - /// - public const string MicrosoftJobPrintedTimeInSeconds = "microsoft-job-printed-time-seconds"; - - /// - /// Only used by the first party Connector 2.2 and newer. - /// Indicates the time (in seconds) that the job spent in the Windows spooler before being sent to the printer. - /// - public const string MicrosoftJobSpoolerTimeInSeconds = "microsoft-job-spooler-time-in-seconds"; - - /// - /// Only used by the first party Connector 2.2 and newer. - /// Indicates the time (in seconds) that the job spent in the printer after being processed in the Windows spooler. - /// - public const string MicrosoftJobPrinterTimeInSeconds = "microsoft-job-printer-time-in-seconds"; - - /// - /// Indicates the time (in milliseconds) it took for converting a PDF format print job to XPS format by the printer. - /// - public const string MicrosoftPdfToXpsJobConversionTimeInMilliseconds = "microsoft-pdf-to-xps-job-conversion-time-milliseconds"; - - /// - /// Only used by the first party Connector. - /// Indicates the time (in milliseconds) it took for the Connector to generate the print ticket that was sent to the spooler - /// (i.e. time taken to transform and merge-and-validate the print ticket). - /// - public const string MicrosoftPrintTicketGenerationTimeInMilliseconds = "microsoft-print-ticket-generation-time-milliseconds"; - - /// - /// Indicates the extended (error code, message, etc) job state message from the printer. - /// - public const string MicrosoftOutputDeviceJobStateMessage = "microsoft-output-device-job-state-message"; - - /// - /// Only used by the first party Connector. - /// Indicates the print ticket mappings generated from the printer's original PDC/PC. - /// - public const string MicrosoftPrintTicketGenerationMappings = "microsoft-print-ticket-generation-mappings"; - - public const string MultipleDocumentHandling = "multiple-document-handling"; - public const string NumberOfDocuments = "number-of-documents"; - public const string NumberOfInterveningJobs = "number-of-intervening-jobs"; - public const string NumberUp = "number-up"; - public const string OrientationRequested = DocumentAttributes.OrientationRequested; - public const string OutputBin = DocumentAttributes.OutputBin; - public const string OutputDeviceAssigned = "output-device-assigned"; - public const string OutputDeviceJobState = "output-device-job-state"; - public const string OutputDeviceJobStateMessage = "output-device-job-state-message"; - public const string OutputDeviceJobStateReasons = "output-device-job-state-reasons"; - public const string OutputDeviceJobStates = "output-device-job-states"; - public const string Overrides = DocumentAttributes.Overrides; - public const string PageRanges = DocumentAttributes.PageRanges; - public const string PdfFitToPage = "pdf-fit-to-page"; - public const string PageOrderReceived = "page-order-received"; - public const string PresentationDirectionNumberUp = "presentation-direction-number-up"; - public const string PrintColorMode = DocumentAttributes.PrintColorMode; - - /// - /// Indicates the actual value of print-color-mode from the job's PT. - /// - public const string PrintColorModeActual = DocumentAttributes.PrintColorMode + "-actual"; - - public const string PrintScaling = "print-scaling"; - public const string PrintContentOptimize = DocumentAttributes.PrintContentOptimize; - public const string PrintRenderingIntent = DocumentAttributes.PrintRenderingIntent; - public const string PrinterQuality = "printer-quality"; - public const string PrinterResolution = DocumentAttributes.PrinterResolution; - public const string PrintQuality = DocumentAttributes.PrintQuality; - public const string Sides = DocumentAttributes.Sides; - - /// - /// Indicates the actual value of sides printed from the job's PT. - /// - public const string SidesActual = DocumentAttributes.Sides + "-actual"; - - public const string TimeAtCompleted = "time-at-completed"; - public const string TimeAtCreation = "time-at-creation"; - public const string TimeAtProcessing = "time-at-processing"; - public const string XDimension = DocumentAttributes.XDimension; - public const string YDimension = DocumentAttributes.YDimension; - - /// - /// The document numbers that the values passed in the "overrides" - /// job attribute applies to. UP doesn't have multiple documents per job, - /// but we still need to have this in "overrides-supported" to be conformant - /// to AirPrint and IPP specs. - /// Naming is non-standard because this is only permitted as an attribute - /// inside the "overrides" collections. - /// - public const string OverridesDocumentNumbers = "document-numbers"; - - /// - /// The pages that the values passed in the "overrides" job attribute applies to. - /// Naming is non-standard because this is only permitted as an attribute - /// inside the "overrides" collections and because "pages" is a ubiquitous term. - /// - public const string OverridesPages = "pages"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/JobEvents.cs b/BadgeReleaseDemo/IppLibrary/JobEvents.cs deleted file mode 100644 index 1b483a3..0000000 --- a/BadgeReleaseDemo/IppLibrary/JobEvents.cs +++ /dev/null @@ -1,17 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - -namespace BadgeReleaseDemo.IppLibrary -{ - public class JobEvents - { - public const string JobStateChanged = "job-state-changed"; - public const string JobCreated = "job-created"; - public const string JobCompleted = "job-completed"; - public const string JobStopped = "job-stopped"; - public const string JobConfigChanged = "job-config-changed"; - public const string JobProgress = "job-progress"; - public const string JobFetchable = "job-fetchable"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs b/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs deleted file mode 100644 index 4fa16de..0000000 --- a/BadgeReleaseDemo/IppLibrary/NotifyAttributes.cs +++ /dev/null @@ -1,44 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - public static class NotifyAttributes - { - public const string NotifyAttributesSupported = "notify-attributes-supported"; - public const string NotifyCharset = "notify-charset"; - public const string NotifyEvents = "notify-events"; - public const string NotifyEventsDefault = "notify-events-default"; - public const string NotifyEventsSupported = "notify-events-supported"; - public const string NotifyGetInterval = "notify-get-interval"; - public const string NotifyJobId = "notify-job-id"; - public const string NotifyLeaseDuration = "notify-lease-duration"; - public const string NotifyLeaseDurationDefault = "notify-lease-duration-default"; - public const string NotifyLeaseDurationSupported = "notify-lease-duration-supported"; - public const string NotifyleaseExpirationTime = "notify-lease-expiration-time"; - public const string NotifyMaxEventsSupported = "notify-max-events-supported"; - public const string NotifyNaturalLanguage = "notify-natural-language"; - public const string NotifyPrinterUptime = "notify-printer-up-time"; - public const string NotifyPrinterUri = "notify-printer-uri"; - public const string NotifyPullMethod = "notify-pull-method"; - public const string NotifyPullMethodSupported = "notify-pull-method-supported"; - public const string NotifyRecipientUri = "notify-recipient-uri"; - public const string NotifySchemesSupported = "notify-schemes-supported"; - public const string NotifySequenceNumber = "notify-sequence-number"; - public const string NotifySequenceNumbers = "notify-sequence-numbers"; - public const string NotifyStatuscode = "notify-status-code"; - public const string NotifySubscribedEvent = "notify-subscribed-event"; - public const string NotifySubscriberUsername = "notify-subscriber-user-name"; - public const string NotifySubscriptionId = "notify-subscription-id"; - public const string NotifySubscriptionIds = "notify-subscription-ids"; - public const string NotifyText = "notify-text"; - public const string NotifyTimeInterval = "notify-time-interval"; - public const string NotifyUserData = "notify-user-data"; - public const string NotifyWait = "notify-wait"; - public const string NotifyIppGet = "ippget"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/OperationAttributes.cs b/BadgeReleaseDemo/IppLibrary/OperationAttributes.cs deleted file mode 100644 index 9a25018..0000000 --- a/BadgeReleaseDemo/IppLibrary/OperationAttributes.cs +++ /dev/null @@ -1,60 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - public static class OperationAttributes - { - public const string AttributesCharset = "attributes-charset"; - public const string AttributesNaturalLanguage = "attributes-natural-language"; - public const string Compression = "compression"; - public const string CompressionAccepted = "compression-accepted"; // IPP Infra - public const string DetailedStatusMessage = "detailed-status-message"; - public const string DocumentAccess = "document-access"; - public const string DocumentFormat = "document-format"; - public const string DocumentFormatAccepted = "document-format-accepted"; - public const string DocumentFormatDetails = "document-format-details"; - public const string DocumentFormatName = "document-format-name"; - public const string DocumentName = "document-name"; - public const string DocumentNumber = "document-number"; - public const string DocumentPassword = "document-password"; - public const string DocumentPreprocessed = "document-preprocessed"; - public const string DocumentUri = "document-uri"; - public const string FetchStatusCode = "fetch-status-code"; - public const string FetchStatusMessage = "fetch-status-message"; - public const string FirstIndex = "first-index"; - public const string IdentifyActions = "identify-actions"; - public const string IppAttributeFidelity = "ipp-attribute-fidelity"; - public const string JobId = "job-id"; - public const string JobIds = "job-ids"; - public const string JobImpressions = "job-impressions"; - public const string JobMandatoryAttributes = "job-mandatory-attributes"; - public const string JobName = "job-name"; - public const string JobPassword = "job-password"; - public const string JobPasswordEncryption = "job-password-encryption"; - public const string JobUri = "job-uri"; - public const string LastDocument = "last-document"; - public const string Limit = "limit"; - public const string MyJobs = "my-jobs"; - public const string NotifyGetInterval = "notify-get-interval"; - public const string NotifySequenceNumbers = "notify-sequence-numbers"; - public const string NotifySubscriptionId = "notify-subscription-id"; - public const string NotifySubscriptionIds = "notify-subscription-ids"; - public const string NotifyWait = "notify-wait"; - public const string OutputDeviceJobState = JobAttributes.OutputDeviceJobState; - public const string OutputDeviceJobStates = JobAttributes.OutputDeviceJobStates; - public const string OutputDeviceUuid = "output-device-uuid"; - public const string PrinterUpTime = "printer-up-time"; - public const string PrinterUri = "printer-uri"; - public const string RequestedAttributes = "requested-attributes"; - public const string RequestingUserName = "requesting-user-name"; - public const string RequestingUserUri = "requesting-user-uri"; - public const string StatusMessage = "status-message"; - public const string WhichJobs = "which-jobs"; - public const string ContentRange = "content-range"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs b/BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs deleted file mode 100644 index 2eca293..0000000 --- a/BadgeReleaseDemo/IppLibrary/PrinterAttributes.cs +++ /dev/null @@ -1,217 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - public static class PrinterAttributes - { - public const string CharsetConfigured = "charset-configured"; - public const string CharsetSupported = "charset-supported"; - public const string ColorSupported = "color-supported"; - public const string ColorModeSupported = "print-color-mode-supported"; - public const string ColorModeDefault = "print-color-mode-default"; - public const string CompressionSupported = "compression-supported"; - public const string CopiesDefault = "copies-default"; - public const string CopiesSupported = "copies-supported"; - public const string DocumentAccessSupported = "document-access-supported"; - public const string DocumentFormatDetailsSupported = "document-format-details-supported"; - public const string DocumentFormatDefault = "document-format-default"; - public const string DocumentFormatPreferred = "document-format-preferred"; // From Windows zero install impl (Mopria). - public const string DocumentFormatSupported = "document-format-supported"; - public const string DocumentPasswordSupported = "document-password-supported"; - public const string FeedOrientationDefault = "feed-orientation-default"; - public const string FeedOrientationSupported = "feed-orientation-supported"; - public const string FinishingsColDatabase = "finishings-col-database"; - public const string FinishingsColDefault = "finishings-col-default"; - public const string FinishingsColReady = "finishings-col-ready"; - public const string FinishingsColSupported = "finishings-col-supported"; - public const string FinishingsDefault = "finishings-default"; - public const string FinishingsReady = "finishings-ready"; - public const string FinishingsSupported = "finishings-supported"; - public const string GeneratedNaturalLanguageSupported = "generated-natural-language-supported"; - public const string IdentifyActionsDefault = "identify-actions-default"; - public const string IdentifyActionsSupported = "identify-actions-supported"; - public const string IppFeaturesSupported = "ipp-features-supported"; - public const string IppVersionsSupported = "ipp-versions-supported"; - public const string IppgetEventLife = "ippget-event-life"; - public const string JobAccountIdDefault = "job-account-id-default"; - public const string JobAccountIdSupported = "job-account-id-supported"; - public const string JobAccountingUserIdDefault = "job-accounting-user-id-default"; - public const string JobAccountingUserIdSupported = "job-accounting-user-id-supported"; - public const string JobConstraintsSupported = "job-constraints-supported"; - public const string JobCreationAttributesSupported = "job-creation-attributes-supported"; - public const string JobIdsSupported = "job-ids-supported"; - public const string JobImpressionsSupported = "job-impressions-supported"; - public const string JobMandatoryAttributesSupported = "job-mandatory-attributes-supported"; - public const string JobMediaSheetsSupported = "job-media-sheets-supported"; - public const string JobPagesPerSetSupported = "job-pages-per-set-supported"; - public const string JobPasswordSupported = "job-password-supported"; - public const string JobPasswordEncryptionSupported = "job-password-encryption-supported"; - public const string JobPasswordLengthSupported = "job-password-length-supported"; - public const string JobReleaseActionDefault = "job-release-action-default"; - public const string JobReleaseActionSupported = "job-release-action-supported"; - public const string JobResolversSupported = "job-resolvers-supported"; - public const string JobSheetsDefault = "job-sheets-default"; - public const string JobSheetsSupported = "job-sheets-supported"; - public const string LandscapeOrientationRequestedPreferred = "landscape-orientation-requested-preferred"; - public const string MarginsPreAppliedDefault = "margins-pre-applied-default"; - public const string MarginsPreAppliedSupported = "margins-pre-applied-supported"; - public const string MediaBottomMarginSupported = "media-bottom-margin-supported"; - public const string MediaColDatabase = "media-col-database"; - public const string MediaColDefault = "media-col-default"; - public const string MediaColReady = "media-col-ready"; - public const string MediaColSupported = "media-col-supported"; - public const string MediaColorSupported = "media-color-supported"; - public const string MediaDefault = "media-default"; - public const string MediaLeftMarginSupported = "media-left-margin-supported"; - public const string MediaReady = "media-ready"; - public const string MediaRightMarginSupported = "media-right-margin-supported"; - public const string MediaSizeSupported = "media-size-supported"; - public const string MediaSourceDefault = "media-source-default"; - public const string MediaSourceFeedDirection = "media-source-feed-direction"; - public const string MediaSourceFeedOrientation = "media-source-feed-orientation"; - public const string MediaSourceProperties = "media-source-properties"; - public const string MediaSourceSupported = "media-source-supported"; - public const string MediaSupported = "media-supported"; - public const string MediaTopMargin = "media-top-margin"; - public const string MediaTopMarginSupported = "media-top-margin-supported"; - public const string MediaTypeSupported = "media-type-supported"; - public const string MopriaCertified = "mopria-certified"; - public const string MultipleDocumentHandlingDefault = "multiple-document-handling-default"; - public const string MultipleDocumentHandlingSupported = "multiple-document-handling-supported"; - public const string MultipleDocumentJobsSupported = "multiple-document-jobs-supported"; - public const string MultipleOperationTimeout = "multiple-operation-time-out"; - public const string MultipleOperationTimeoutAction = "multiple-operation-time-out-action"; - public const string NaturalLanguageConfigured = "natural-language-configured"; - public const string NumberUpDefault = "number-up-default"; - public const string NotifyPullMethodSupported = "notify-pull-method-supported"; - public const string NumberUpSupported = "number-up-supported"; - public const string OauthAuthorizationServerUri = "oauth-authorization-server-uri"; - public const string OperationsSupported = "operations-supported"; - public const string OrientationRequestedDefault = "orientation-requested-default"; - public const string OrientationRequestedSupported = "orientation-requested-supported"; - public const string OutputBinDefault = "output-bin-default"; - public const string OutputBinSupported = "output-bin-supported"; - public const string OverridesSupported = "overrides-supported"; - public const string PageRangesSupported = "page-ranges-supported"; - public const string PageColorModeDefault = "page-color-mode-default"; - public const string PageColorModeSupported = "page-color-mode-supported"; - public const string PageOrderReceivedSupported = "page-order-received-supported"; - public const string PageOrderReceivedDefault = "page-order-received-default"; - public const string PagesPerMinute = "pages-per-minute"; - public const string PagesPerMinuteColor = "pages-per-minute-color"; - public const string PclmRasterBackSide = "pclm-raster-back-side"; - public const string PclmSourceResolutionSupported = "pclm-source-resolution-supported"; - public const string PclmStripHeightPreferred = "pclm-strip-height-preferred"; - public const string PclmStripHeightSupported = "pclm-strip-height-supported"; - public const string PdlOverrideSupported = "pdl-override-supported"; - public const string PdfFitToPageDefault = "pdf-fit-to-page-default"; // from Windows zero install impl (Mopria). - public const string PdfFitToPageSupported = "pdf-fit-to-page-supported"; // from Windows zero install impl (Mopria). - public const string PdfKOctetsSupported = "pdf-k-octets-supported"; - public const string PdfSizeConstraints = "pdf-size-constraints"; - public const string PdfVersionsSupported = "pdf-versions-supported"; - public const string PresentationDirectionNumberUpDefault = "presentation-direction-number-up-default"; - public const string PresentationDirectionNumberUpSupported = "presentation-direction-number-up-supported"; - public const string PrinterConfigChangeTime = "printer-config-change-time"; - public const string PrinterConfigChangeDateTime = "printer-config-change-date-time"; - public const string PrintColorModeDefault = "print-color-mode-default"; - public const string PrintColorModeSupported = "print-color-mode-supported"; - public const string PrintContentOptimizeDefault = "print-content-optimize-default"; - public const string PrintContentOptimizeSupported = "print-content-optimize-supported"; - public const string PrintRenderingIntentDefault = "print-rendering-intent-default"; - public const string PrintRenderingIntentSupported = "print-rendering-intent-supported"; - public const string PrintQualityDefault = "print-quality-default"; - public const string PrintQualitySupported = "print-quality-supported"; - public const string PrintScalingDefault = "print-scaling-default"; - public const string PrintScalingSupported = "print-scaling-supported"; - public const string PrinterAlert = "printer-alert"; - public const string PrinterAlertDescription = "printer-alert-description"; - public const string PrinterCurrentTime = "printer-current-time"; - public const string PrinterDeviceId = "printer-device-id"; - public const string PrinterFirmwareName = "printer-firmware-name"; - public const string PrinterFirmwarePatches = "printer-firmware-patches"; - public const string PrinterFirmwareStringVersion = "printer-firmware-string-version"; - public const string PrinterFirmwareVersion = "printer-firmware-version"; - public const string PrinterGeoLocation = "printer-geo-location"; - public const string PrinterGetAttributesSupported = "printer-get-attributes-supported"; - public const string PrinterIccProfiles = "printer-icc-profiles"; - public const string PrinterIcons = "printer-icons"; - public const string PrinterInfo = "printer-info"; - public const string PrinterInputTray = "printer-input-tray"; - public const string PrinterIsAcceptingJobs = "printer-is-accepting-jobs"; - public const string PrinterKind = "printer-kind"; - public const string PrinterLocation = "printer-location"; - public const string PrinterMakeAndModel = "printer-make-and-model"; - public const string PrinterMoreInfo = "printer-more-info"; - public const string PrinterMoreInfoManufacturer = "printer-more-info-manufacturer"; - public const string PrinterName = "printer-name"; - public const string PrinterOrganization = "printer-organization"; - public const string PrinterOrganizationalUnit = "printer-organizational-unit"; - public const string PrinterOutputTray = "printer-output-tray"; - public const string PrinterResolutionDefault = "printer-resolution-default"; - public const string PrinterResolutionSupported = "printer-resolution-supported"; - public const string PrinterState = "printer-state"; - public const string PrinterStateChangeDateTime = "printer-state-change-date-time"; - public const string PrinterStateChangeTime = "printer-state-change-time"; - public const string PrinterStateChangeMessage = "printer-state-change-message"; - public const string PrinterStateMessage = "printer-state-message"; - public const string PrinterStateReasons = "printer-state-reasons"; - public const string PrinterStaticResourceDirectoryUri = "printer-static-resource-directory-uri"; - public const string PrinterStaticResourceKOctetsFree = "printer-static-resource-k-octets-free"; - public const string PrinterStaticResourceKOctetsSupported = "printer-static-resource-k-octets-supported"; - public const string PrinterSupply = "printer-supply"; - public const string PrinterSupplyDescription = "printer-supply-description"; - public const string PrinterSupplyInfoUri = "printer-supply-info-uri"; - public const string PrinterUpTime = "printer-up-time"; - public const string PrinterUriSupported = "printer-uri-supported"; - public const string PrinterUuid = "printer-uuid"; - public const string PwgRasterDocumentResolutionSupported = "pwg-raster-document-resolution-supported"; - public const string PwgRasterDocumentSheetBack = "pwg-raster-document-sheet-back"; - public const string PwgRasterDocumentTypeSupported = "pwg-raster-document-type-supported"; - public const string QueuedJobCount = "queued-job-count"; - public const string SidesDefault = "sides-default"; - public const string SidesSupported = "sides-supported"; - public const string UrfSupported = "urf-supported"; - public const string UriSecuritySupported = "uri-security-supported"; - public const string UriAuthenticationSupported = "uri-authentication-supported"; - public const string WhichJobsSupported = "which-jobs-supported"; - public const string PreferredChunkSizeKOctets = "preferred-chunk-size-k-octets"; - - // Attribute extensions - // See reference: https://tools.ietf.org/html/rfc8011#section-7.2 - public const string MicrosoftPageOrderDefault = "microsoft-page-order-default"; - public const string MicrosoftPageOrderSupported = "microsoft-page-order-supported"; - public const string MicrosoftUniversalPrintConnectorAppVersion = "microsoft-universal-print-connector-app-version"; - public const string MicrosoftUniversalPrintConnectorOperatingSystem = "microsoft-universal-print-connector-operating-system"; - public const string MicrosoftUniversalPrintConnectorId = "microsoft-universal-print-connector-id"; - public const string MicrosoftUniversalPrinterDriverName = "microsoft-universal-printer-driver-name"; - public const string MicrosoftUniversalPrinterDriverVersion = "microsoft-universal-printer-driver-version"; - public const string MicrosoftUniversalPrintDocumentFormatSupportedViaConversion = "microsoft-universal-print-document-format-supported-via-conversion"; - - // smi attributes - public const string PullPrintEnabledWithOEMJobRelease = "smi311-universal-print-anywhere-enabled"; - - // Custom Windows extensions - public const string PrintDeviceCapabilities = "print-device-capabilites"; // Universal Print connector sends printer's PDC. - public const string PrintDeviceResources = "print-device-resources"; - - // CUPS Printer Attributes. (Optional for Mopria 2.0 support) - // Ref: Mopria 2.0 spec Section 4.14 - // https://microsoft.sharepoint.com/:b:/t/STACKTeam-CoreNetworkingMobileConnectivityPeripheralsStackSe/EYP81I7FD95JiQdMtD14FlEBMHbF1pbe_1aYO9kPVN-w-w?e=rJxA7L - // CUPS spec: https://www.cups.org/doc/spec-ipp.html - public const string MarkerColors = "marker-colors"; - public const string MarkerHighLevels = "marker-high-levels"; - public const string MarkerLevels = "marker-levels"; - public const string MarkerLowLevels = "marker-low-levels"; - public const string MarkerNames = "marker-names"; - public const string MarkerTypes = "marker-types"; - - // Ref: Mopria 2.0 Section 4.5.1 - // https://microsoft.sharepoint.com/:b:/t/STACKTeam-CoreNetworkingMobileConnectivityPeripheralsStackSe/EYP81I7FD95JiQdMtD14FlEBMHbF1pbe_1aYO9kPVN-w-w?e=rJxA7L - public const string PrintWFDS = "print_wfds"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/PrinterEvents.cs b/BadgeReleaseDemo/IppLibrary/PrinterEvents.cs deleted file mode 100644 index 094cbf8..0000000 --- a/BadgeReleaseDemo/IppLibrary/PrinterEvents.cs +++ /dev/null @@ -1,18 +0,0 @@ -// -// Copyright (c) Microsoft Corporation. All rights reserved. -// - -namespace BadgeReleaseDemo.IppLibrary -{ - public class PrinterEvents - { - public const string PrinterStateChanged = "printer-state-changed"; - public const string PrinterRestarted = "printer-restarted"; - public const string PrinterShutdown = "printer-shutdown"; - public const string PrinterStopped = "printer-stopped"; - public const string PrinterConfigChanged = "printer-config-changed"; - public const string PrinterMediaChanged = "printer-media-changed"; - public const string PrinterFinishingsChanged = "printer-finishings-changed"; - public const string PrinterQueueorderChanged = "printer-queue-order-changed"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs b/BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs deleted file mode 100644 index 9aad39e..0000000 --- a/BadgeReleaseDemo/IppLibrary/RawIppEncodingBase.cs +++ /dev/null @@ -1,81 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - using System.Collections.Generic; - using System.Globalization; - using System.IO; - using System.Text; - - /// - /// The raw handling of IPP encoding. - /// - public abstract class RawIppEncodingBase : IDisposable - { - /// - /// Gets or sets the major version number. - /// - public sbyte MajorVersionNumber { get; set; } - - /// - /// Gets or sets the minor version number. - /// - public sbyte MinorVersionNumber { get; set; } - - /// - /// Gets or sets the attribute group list. - /// - public List BaseAttributeGroups { get; set; } = new List(); - - /// - /// Gets or sets the print data payload. - /// - public Stream Data { get; set; } - - /// - /// Serialize to string. - /// - /// Serialized string. - public override string ToString() - { - var sb = new StringBuilder(); - - sb.AppendFormat(CultureInfo.InvariantCulture, "Version: {0}.{1}\n", this.MajorVersionNumber, this.MinorVersionNumber); - sb.Append("Attributes:\n"); - - foreach (IppAttributeGroup group in this.BaseAttributeGroups) - { - sb.AppendFormat(CultureInfo.InvariantCulture, "{0}\n", group); - } - - // Note that the stream length will be equal to the stream position for chunked requests, not the total length of the stream - if (this.Data != null && this.Data.CanSeek) - { - sb.AppendFormat(CultureInfo.InvariantCulture, "Data: {0} bytes", this.Data.Length); - } - - return sb.ToString(); - } - - /// - /// Dispose. - /// - public virtual void Dispose() => this.Data?.Dispose(); - - /// - /// Suport IPP version 1.2. - /// - public void CheckVersion() - { - if (this.MinorVersionNumber != (sbyte)IppMajorVersion.Version1 && this.MajorVersionNumber != (sbyte)IppMajorVersion.Version2) - { - throw new IPPException(BadgeReleaseDemo.IppLibrary.StatusCode.ServerErrorVersionNotSupported, FormattableString.Invariant($"IPP version {this.MajorVersionNumber}.{this.MinorVersionNumber} not supported.")); - } - } - } -} diff --git a/BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs b/BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs deleted file mode 100644 index 1ed6f0d..0000000 --- a/BadgeReleaseDemo/IppLibrary/RequestedAttributes.cs +++ /dev/null @@ -1,16 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - public static class RequestedAttributes - { - public const string All = "all"; - public const string JobTemplate = "job-template"; - public const string JobDescription = "job-description"; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs b/BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs deleted file mode 100644 index a0b43ae..0000000 --- a/BadgeReleaseDemo/IppLibrary/RequiredAttributes.cs +++ /dev/null @@ -1,15 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -// ReSharper disable once CheckNamespace -namespace BadgeReleaseDemo.IppLibrary.Common -{ - public static class RequiredAttributes - { - public const string AttributesCharset = OperationAttributes.AttributesCharset; - public const string AttributesNaturalLanguage = OperationAttributes.AttributesNaturalLanguage; - } -} \ No newline at end of file diff --git a/BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs b/BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs deleted file mode 100644 index 4d16271..0000000 --- a/BadgeReleaseDemo/IppLibrary/ZeroByteReadException.cs +++ /dev/null @@ -1,21 +0,0 @@ -//----------------------------------------------------------------------- -// -// Copyright (c) Microsoft. All rights reserved. -// -//----------------------------------------------------------------------- - -namespace BadgeReleaseDemo.IppLibrary -{ - using System; - - /// - /// ZeroByteReadException is expected to be thrown when the initial read from the stream returns 0 bytes - /// - public class ZeroByteReadException : Exception - { - public ZeroByteReadException(string message) - : base(message) - { - } - } -} From e2664fd4450b2aaac5486a83880094ceef7618a2 Mon Sep 17 00:00:00 2001 From: Adi Keswani Date: Thu, 9 Jul 2026 16:06:18 -0700 Subject: [PATCH 6/7] Restore default appsettings placeholders Revert BadgeReleaseDemo appsettings.json to the original template values and default print.microsoft.com endpoints. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- BadgeReleaseDemo/appsettings.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/BadgeReleaseDemo/appsettings.json b/BadgeReleaseDemo/appsettings.json index 9229f41..82df14f 100644 --- a/BadgeReleaseDemo/appsettings.json +++ b/BadgeReleaseDemo/appsettings.json @@ -1,10 +1,10 @@ { - "AppId": "eb6526b2-528e-49df-ab52-6bc5adaf0628", - "Tenant": "6923dc21-4f0f-42e5-800e-ef1e8ba0b218", + "AppId": "YOUR_APP_ID_HERE", + "Tenant": "YOUR_TENANT_HERE", "GraphBaseUrl": "https://graph.microsoft.com/v1.0", - "GraphPrintBaseUrl": "https://graph.print-next.microsoft.com/v1.0", - "RegistrationBaseUrl": "https://register.print-next.microsoft.com", - "IppServiceBaseUrl": "https://print.print-next.microsoft.com", + "GraphPrintBaseUrl": "https://graph.print.microsoft.com/v1.0", + "RegistrationBaseUrl": "https://register.print.microsoft.com", + "IppServiceBaseUrl": "https://print.print.microsoft.com", "IppServicePrinterPath": "/printers", "BadgesApiPath": "/api/v1.0/badges" } From cb9a7cc5288c7afc18bfb7c4a743130ade033d0a Mon Sep 17 00:00:00 2001 From: Adi Keswani Date: Thu, 9 Jul 2026 16:18:55 -0700 Subject: [PATCH 7/7] Address high-signal review findings Fix upload-session handling (chunked PUTs and no Authorization header), remove shared HttpClient default-header mutations, harden cleanup so each resource is cleaned independently, improve IPP parsing robustness, and tighten job correlation to avoid releasing the wrong queued job. Also update docs to explicitly note government cloud is not supported by this demo/badge API flow today. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- BadgeReleaseDemo/Auth/AuthHelper.cs | 4 +- BadgeReleaseDemo/GraphApi/BadgeManagement.cs | 57 +++------- .../GraphApi/PrintJobSubmission.cs | 50 +++++---- BadgeReleaseDemo/IppOperations/MinimalIpp.cs | 82 +++++++++++--- .../IppOperations/PrinterIppClient.cs | 16 +-- BadgeReleaseDemo/Program.cs | 106 +++++++++++++----- BadgeReleaseDemo/README.md | 18 +-- 7 files changed, 199 insertions(+), 134 deletions(-) diff --git a/BadgeReleaseDemo/Auth/AuthHelper.cs b/BadgeReleaseDemo/Auth/AuthHelper.cs index 61872db..7c6c5f6 100644 --- a/BadgeReleaseDemo/Auth/AuthHelper.cs +++ b/BadgeReleaseDemo/Auth/AuthHelper.cs @@ -227,12 +227,12 @@ private string CreateDeviceJwt(string nonce) { "x5c", new[] { certBase64 } }, }; - // Use the app's client_id and the standard native client redirect URI + // Use the app's client_id and the configured desktop redirect URI. var clientId = string.IsNullOrEmpty(registrationResult!.PrinterClientId) ? appId : registrationResult.PrinterClientId; var redirectUri = string.IsNullOrEmpty(registrationResult.PrinterRedirectUri) - ? "https://login.microsoftonline.com/common/oauth2/nativeclient" + ? "http://localhost" : registrationResult.PrinterRedirectUri; var claims = new[] diff --git a/BadgeReleaseDemo/GraphApi/BadgeManagement.cs b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs index 981bc6b..1b2454c 100644 --- a/BadgeReleaseDemo/GraphApi/BadgeManagement.cs +++ b/BadgeReleaseDemo/GraphApi/BadgeManagement.cs @@ -30,10 +30,10 @@ public BadgeManagement(string graphBaseUrl) /// public async Task CreateBadgeCollectionAsync(string accessToken) { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var content = new StringContent("{}", Encoding.UTF8, "application/json"); - var response = await httpClient.PostAsync($"{graphBaseUrl}/print/badgeCollections", content); + using var request = new HttpRequestMessage(HttpMethod.Post, $"{graphBaseUrl}/print/badgeCollections"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + request.Content = new StringContent("{}", Encoding.UTF8, "application/json"); + var response = await httpClient.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.Conflict) @@ -61,8 +61,6 @@ public async Task CreateBadgeCollectionAsync(string accessToken) private async Task WaitForBadgeCollectionProvisioningAsync(string accessToken) { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - const int maxAttempts = 60; const int delayMilliseconds = 10000; @@ -92,9 +90,9 @@ private async Task GetBadgeCollectionIdAsync(string accessToken) private async Task TryGetBadgeCollectionIdAsync(string accessToken) { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var response = await httpClient.GetAsync($"{graphBaseUrl}/print/badgeCollections"); + using var request = new HttpRequestMessage(HttpMethod.Get, $"{graphBaseUrl}/print/badgeCollections"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + var response = await httpClient.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.NotFound) @@ -134,8 +132,6 @@ private async Task GetBadgeCollectionIdAsync(string accessToken) /// public async Task AddBadgeAsync(string accessToken, string collectionId, string badgeId, string upn) { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - var requestBody = new { id = badgeId, @@ -143,18 +139,17 @@ public async Task AddBadgeAsync(string accessToken, string collectionId, string }; var json = JsonSerializer.Serialize(requestBody); - var content = new StringContent(json, Encoding.UTF8, "application/json"); - - var response = await httpClient.PostAsync( - $"{graphBaseUrl}/print/badgeCollections/{collectionId}/badges", - content); + using var request = new HttpRequestMessage(HttpMethod.Post, + $"{graphBaseUrl}/print/badgeCollections/{collectionId}/badges"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + var response = await httpClient.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); if (response.StatusCode == HttpStatusCode.Conflict) { - ConsoleHelper.WriteInfo($"Badge '{badgeId}' already exists. Updating..."); - await UpdateBadgeAsync(accessToken, collectionId, badgeId, upn); - return; + throw new InvalidOperationException( + $"Badge '{badgeId}' already exists. Choose a unique badge ID to avoid overwriting an existing user mapping."); } if (!response.IsSuccessStatusCode) @@ -162,30 +157,6 @@ public async Task AddBadgeAsync(string accessToken, string collectionId, string throw new HttpRequestException($"Failed to add badge: {response.StatusCode} - {responseBody}"); } } - - /// - /// Updates an existing badge's UPN. - /// - private async Task UpdateBadgeAsync(string accessToken, string collectionId, string badgeId, string upn) - { - var requestBody = new { upn }; - var json = JsonSerializer.Serialize(requestBody); - var request = new HttpRequestMessage(HttpMethod.Patch, - $"{graphBaseUrl}/print/badgeCollections/{collectionId}/badges/{badgeId}") - { - Content = new StringContent(json, Encoding.UTF8, "application/json") - }; - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var response = await httpClient.SendAsync(request); - var responseBody = await response.Content.ReadAsStringAsync(); - - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException($"Failed to update badge: {response.StatusCode} - {responseBody}"); - } - } - /// /// Deletes a badge from the collection. /// diff --git a/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs b/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs index 6669acc..8219550 100644 --- a/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs +++ b/BadgeReleaseDemo/GraphApi/PrintJobSubmission.cs @@ -115,27 +115,39 @@ public async Task CreateUploadSessionAsync( } /// - /// Uploads a PDF document to the upload session. + /// Uploads a PDF document to the upload session in chunks smaller than 10 MB. /// - public async Task UploadDocumentAsync(string accessToken, string uploadUrl, byte[] pdfData) + public async Task UploadDocumentAsync(string uploadUrl, byte[] pdfData) { - using var request = new HttpRequestMessage(HttpMethod.Put, uploadUrl); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + // Universal Print upload sessions require each PUT to be less than 10 MB. + const int maxChunkSizeBytes = 9 * 1024 * 1024; + var totalLength = pdfData.Length; + var offset = 0; - request.Content = new ByteArrayContent(pdfData); - request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); - request.Content.Headers.ContentLength = pdfData.Length; - request.Content.Headers.Add("Content-Range", $"bytes 0-{pdfData.Length - 1}/{pdfData.Length}"); + while (offset < totalLength) + { + var chunkLength = Math.Min(maxChunkSizeBytes, totalLength - offset); + var rangeStart = offset; + var rangeEnd = offset + chunkLength - 1; - var response = await httpClient.SendAsync(request); - var responseBody = await response.Content.ReadAsStringAsync(); + using var request = new HttpRequestMessage(HttpMethod.Put, uploadUrl); + request.Content = new ByteArrayContent(pdfData, offset, chunkLength); + request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf"); + request.Content.Headers.ContentLength = chunkLength; + request.Content.Headers.Add("Content-Range", $"bytes {rangeStart}-{rangeEnd}/{totalLength}"); - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException($"Failed to upload document: {response.StatusCode} - {responseBody}"); + var response = await httpClient.SendAsync(request); + var responseBody = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException($"Failed to upload document range {rangeStart}-{rangeEnd}: {response.StatusCode} - {responseBody}"); + } + + offset += chunkLength; } - ConsoleHelper.WriteInfo($"Uploaded {pdfData.Length} bytes."); + ConsoleHelper.WriteInfo($"Uploaded {totalLength} bytes."); } /// @@ -143,12 +155,12 @@ public async Task UploadDocumentAsync(string accessToken, string uploadUrl, byte /// public async Task StartJobAsync(string accessToken, string shareId, string jobId) { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); - - var content = new StringContent("{}", Encoding.UTF8, "application/json"); + using var request = new HttpRequestMessage(HttpMethod.Post, + $"{graphBaseUrl}/print/shares/{shareId}/jobs/{jobId}/start"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + request.Content = new StringContent("{}", Encoding.UTF8, "application/json"); - var response = await httpClient.PostAsync( - $"{graphBaseUrl}/print/shares/{shareId}/jobs/{jobId}/start", content); + var response = await httpClient.SendAsync(request); var responseBody = await response.Content.ReadAsStringAsync(); if (!response.IsSuccessStatusCode) diff --git a/BadgeReleaseDemo/IppOperations/MinimalIpp.cs b/BadgeReleaseDemo/IppOperations/MinimalIpp.cs index ccfe4a7..ec4b5cd 100644 --- a/BadgeReleaseDemo/IppOperations/MinimalIpp.cs +++ b/BadgeReleaseDemo/IppOperations/MinimalIpp.cs @@ -26,7 +26,7 @@ public static class MinimalIpp private const ushort OP_FETCH_JOB = 0x0043; private const ushort OP_ACKNOWLEDGE_JOB = 0x0041; private const ushort OP_FETCH_DOCUMENT = 0x0042; - private const ushort OP_UPDATE_JOB_STATUS = 0x0045; + private const ushort OP_UPDATE_JOB_STATUS = 0x0048; // Attribute group tags private const byte TAG_OPERATION_ATTRIBUTES = 0x01; @@ -203,10 +203,10 @@ public static byte[] BuildAcknowledgeJobRequest( // Job ID WriteIntegerAttribute(stream, "job-id", jobId); - // Status message (if provided) + // Fetch status message (if provided) if (!string.IsNullOrEmpty(statusMessage)) { - WriteStringAttribute(stream, "status-message", statusMessage); + WriteStringAttribute(stream, "fetch-status-message", statusMessage); } stream.WriteByte(TAG_END_OF_ATTRIBUTES); @@ -293,9 +293,12 @@ public static byte[] BuildUpdateJobStatusRequest( WriteAttributeWithTag(stream, TAG_MS_CHARSET, "output-device-uuid", $"urn:uuid:{outputDeviceUuid}"); } - // Job ID and job state + // Job ID in operation attributes WriteIntegerAttribute(stream, "job-id", jobId); - WriteIntegerAttribute(stream, "job-state", jobState); + + // Job state in job attributes group + stream.WriteByte(TAG_JOB_ATTRIBUTES); + WriteEnumAttribute(stream, "output-device-job-state", jobState); stream.WriteByte(TAG_END_OF_ATTRIBUTES); @@ -308,6 +311,11 @@ public static byte[] BuildUpdateJobStatusRequest( /// public static (ushort StatusCode, List> JobAttributes) ParseGetJobsResponse(byte[] responseData) { + if (responseData.Length < 8) + { + throw new InvalidDataException("Invalid IPP response: expected at least 8 bytes for header."); + } + using var stream = new MemoryStream(responseData); using var reader = new BinaryReader(stream); @@ -345,8 +353,13 @@ public static (ushort StatusCode, List> JobAttributes /// /// Parses an IPP response for Fetch-Job and extracts job attributes. /// - public static (ushort StatusCode, Dictionary JobAttributes, MemoryStream? DocumentData) ParseFetchJobResponse(byte[] responseData) + public static (ushort StatusCode, Dictionary JobAttributes, byte[]? DocumentData) ParseFetchJobResponse(byte[] responseData) { + if (responseData.Length < 8) + { + throw new InvalidDataException("Invalid IPP response: expected at least 8 bytes for header."); + } + using var stream = new MemoryStream(responseData); using var reader = new BinaryReader(stream); @@ -377,9 +390,7 @@ public static (ushort StatusCode, Dictionary JobAttributes, Memo // Remaining bytes are the document data (if any) byte[] documentData = reader.ReadBytes((int)(stream.Length - stream.Position)); - var docStream = documentData.Length > 0 ? new MemoryStream(documentData) : null; - - return (statusCode, jobAttrs, docStream); + return (statusCode, jobAttrs, documentData.Length > 0 ? documentData : null); } /// @@ -388,7 +399,9 @@ public static (ushort StatusCode, Dictionary JobAttributes, Memo public static ushort ParseStatusCodeResponse(byte[] responseData) { if (responseData.Length < 8) - return 0; + { + throw new InvalidDataException("Invalid IPP response: expected at least 8 bytes for header."); + } return (ushort)((responseData[2] << 8) | responseData[3]); } @@ -443,6 +456,7 @@ private static void WriteString(MemoryStream stream, string value) private static Dictionary ParseAttributeGroup(BinaryReader reader) { var attrs = new Dictionary(); + string? lastAttributeName = null; while (reader.BaseStream.Position < reader.BaseStream.Length) { @@ -457,7 +471,32 @@ private static Dictionary ParseAttributeGroup(BinaryReader reade string name = ReadString(reader); object value = ReadValue(reader, tag); - attrs[name] = value; + var attributeName = string.IsNullOrEmpty(name) ? lastAttributeName : name; + if (string.IsNullOrEmpty(attributeName)) + { + throw new InvalidDataException("Malformed IPP attribute group: encountered empty attribute name without a previous name."); + } + + if (!string.IsNullOrEmpty(name)) + { + lastAttributeName = name; + } + + if (attrs.TryGetValue(attributeName, out var existingValue)) + { + if (existingValue is List existingList) + { + existingList.Add(value); + } + else + { + attrs[attributeName] = new List { existingValue, value }; + } + } + else + { + attrs[attributeName] = value; + } } return attrs; @@ -490,15 +529,26 @@ private static string ReadString(BinaryReader reader) private static object ReadValue(BinaryReader reader, byte tag) { ushort length = ReadUInt16BigEndian(reader); + byte[] valueBytes = reader.ReadBytes(length); + if (valueBytes.Length != length) + { + throw new InvalidDataException("Malformed IPP value: insufficient bytes for declared length."); + } return tag switch { - TAG_INTEGER => ReadInt32BigEndian(reader), - TAG_BOOLEAN => reader.ReadByte() != 0, - TAG_ENUM => ReadInt32BigEndian(reader), + TAG_INTEGER => length == 4 + ? (int)((valueBytes[0] << 24) | (valueBytes[1] << 16) | (valueBytes[2] << 8) | valueBytes[3]) + : valueBytes, + TAG_BOOLEAN => length == 1 + ? valueBytes[0] != 0 + : valueBytes, + TAG_ENUM => length == 4 + ? (int)((valueBytes[0] << 24) | (valueBytes[1] << 16) | (valueBytes[2] << 8) | valueBytes[3]) + : valueBytes, TAG_KEYWORD or TAG_NAME_WITHOUT_LANGUAGE or TAG_TEXT_WITHOUT_LANGUAGE or TAG_URI or TAG_STRING => - Encoding.UTF8.GetString(reader.ReadBytes(length)), - _ => reader.ReadBytes(length) + Encoding.UTF8.GetString(valueBytes), + _ => valueBytes }; } diff --git a/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs b/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs index 3104f81..f8ff559 100644 --- a/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs +++ b/BadgeReleaseDemo/IppOperations/PrinterIppClient.cs @@ -119,7 +119,7 @@ public PrinterIppClient(string ippServiceBaseUrl, string ippServicePrinterPath, /// /// Sends Fetch-Job IPP request to get job metadata. /// - public async Task<(ushort StatusCode, Dictionary JobAttributes, MemoryStream? DocumentData)> FetchJobAsync( + public async Task<(ushort StatusCode, Dictionary JobAttributes, byte[]? DocumentData)> FetchJobAsync( string printerToken, string printerId, int jobId, string requestingUserUri) { var ippHost = new Uri(ippServiceBaseUrl).Host; @@ -176,7 +176,7 @@ public async Task AcknowledgeJobAsync( jobUri: jobUri); var responseData = await SendIppRequestAsync(printerToken, ippRequest); - var (statusCode, _, docStream) = MinimalIpp.ParseFetchJobResponse(responseData); + var (statusCode, _, documentData) = MinimalIpp.ParseFetchJobResponse(responseData); if (statusCode != 0x0000) { @@ -184,19 +184,13 @@ public async Task AcknowledgeJobAsync( return null; } - if (docStream == null) + if (documentData == null) { ConsoleHelper.WriteError("Fetch-Document response contained no document data."); return null; } - - using (docStream) - { - docStream.Seek(0, SeekOrigin.Begin); - using var ms = new MemoryStream(); - await docStream.CopyToAsync(ms); - return ms.ToArray(); - } + + return documentData; } /// diff --git a/BadgeReleaseDemo/Program.cs b/BadgeReleaseDemo/Program.cs index 6587ac6..e8e2b1f 100644 --- a/BadgeReleaseDemo/Program.cs +++ b/BadgeReleaseDemo/Program.cs @@ -166,7 +166,7 @@ public static async Task Main(string[] args) ConsoleHelper.WriteProgress("Uploading document..."); var pdfData = await File.ReadAllBytesAsync(pdfPath); var uploadUrl = await jobSubmission.CreateUploadSessionAsync(graphToken, shareId, jobId, documentId, pdfData.Length); - await jobSubmission.UploadDocumentAsync(graphToken, uploadUrl, pdfData); + await jobSubmission.UploadDocumentAsync(uploadUrl, pdfData); // Start the job await jobSubmission.StartJobAsync(graphToken, shareId, jobId); @@ -237,8 +237,47 @@ public static async Task Main(string[] args) return; } - resolvedJobId = jobs[0].JobId; - resolvedJobUri = jobs[0].JobUri; + var selectedJob = jobs[0]; + if (int.TryParse(jobId, out var submittedJobId)) + { + var attemptsRemaining = 6; + while (true) + { + bool found = false; + foreach (var candidate in jobs) + { + if (candidate.JobId == submittedJobId) + { + selectedJob = candidate; + found = true; + break; + } + } + + if (found) + { + break; + } + + attemptsRemaining--; + if (attemptsRemaining <= 0) + { + ConsoleHelper.WriteError($"Submitted job {submittedJobId} was not found in fetchable jobs."); + return; + } + + ConsoleHelper.WriteInfo($"Submitted job {submittedJobId} not fetchable yet. Polling again in 5 seconds..."); + await Task.Delay(TimeSpan.FromSeconds(5)); + jobs = await ippClient.GetJobsAsync(printerToken, printerId, resolvedUserUri!); + } + } + else + { + ConsoleHelper.WriteWarning($"Could not parse Graph job ID '{jobId}' as IPP integer. Using the first fetchable job."); + } + + resolvedJobId = selectedJob.JobId; + resolvedJobUri = selectedJob.JobUri; ConsoleHelper.WriteSuccess($"Found {jobs.Count} fetchable job(s)."); ConsoleHelper.WriteKeyValue("Fetching Job ID", resolvedJobId.ToString()); if (!string.IsNullOrEmpty(resolvedJobUri)) @@ -250,7 +289,7 @@ public static async Task Main(string[] args) // Step 11: Fetch-Job (get job metadata) // ═══════════════════════════════════════════════════════════ ConsoleHelper.WriteStep("🖨️", "Printer: Fetching job metadata..."); - var (fetchJobStatusCode, fetchJobAttrs, fetchJobDocStream) = await ippClient.FetchJobAsync( + var (fetchJobStatusCode, _, _) = await ippClient.FetchJobAsync( printerToken, printerId, resolvedJobId, resolvedUserUri!); if (fetchJobStatusCode != 0x0000) // 0x0000 = successful-ok @@ -327,52 +366,63 @@ public static async Task Main(string[] args) { ConsoleHelper.WriteHeader("🧹 Cleaning up demo resources..."); - try + if (savedDocumentPath != null && File.Exists(savedDocumentPath)) { - if (savedDocumentPath != null && File.Exists(savedDocumentPath)) + try { - try - { - ConsoleHelper.WriteProgress("Deleting downloaded document..."); - File.Delete(savedDocumentPath); - ConsoleHelper.WriteSuccess("Document deleted."); - } - catch (IOException) - { - ConsoleHelper.WriteWarning($"Could not delete document (may be open in another app): {savedDocumentPath}"); - } + ConsoleHelper.WriteProgress("Deleting downloaded document..."); + File.Delete(savedDocumentPath); + ConsoleHelper.WriteSuccess("Document deleted."); + } + catch (IOException) + { + ConsoleHelper.WriteWarning($"Could not delete document (may be open in another app): {savedDocumentPath}"); } + } - if (!string.IsNullOrEmpty(createdBadgeId) && !string.IsNullOrEmpty(badgeCollectionId)) + if (!string.IsNullOrEmpty(createdBadgeId) && !string.IsNullOrEmpty(badgeCollectionId)) + { + try { ConsoleHelper.WriteProgress($"Deleting badge '{createdBadgeId}'..."); var printToken = await auth.GetUserTokenAsync(); await badgeMgmt.DeleteBadgeAsync(printToken, badgeCollectionId, createdBadgeId); ConsoleHelper.WriteSuccess("Badge deleted."); } + catch (Exception badgeCleanupEx) + { + ConsoleHelper.WriteWarning($"Failed to delete badge: {badgeCleanupEx.Message}"); + } + } - var graphToken = await auth.GetGraphTokenAsync(); - - if (!string.IsNullOrEmpty(shareId)) + if (!string.IsNullOrEmpty(shareId)) + { + try { + var graphToken = await auth.GetGraphTokenAsync(); ConsoleHelper.WriteProgress($"Deleting share {shareId}..."); await printerShare.DeleteShareAsync(graphToken, shareId); ConsoleHelper.WriteSuccess("Share deleted."); } + catch (Exception shareCleanupEx) + { + ConsoleHelper.WriteWarning($"Failed to delete share: {shareCleanupEx.Message}"); + } + } - if (!string.IsNullOrEmpty(printerId)) + if (!string.IsNullOrEmpty(printerId)) + { + try { + var graphToken = await auth.GetGraphTokenAsync(); ConsoleHelper.WriteProgress($"Deleting printer {printerId}..."); await printerShare.DeletePrinterAsync(graphToken, printerId); ConsoleHelper.WriteSuccess("Printer deleted."); } - } - catch (Exception cleanupEx) - { - ConsoleHelper.WriteWarning($"Cleanup failed: {cleanupEx.Message}"); - ConsoleHelper.WriteInfo("You may need to clean up manually in the Azure portal."); - ConsoleHelper.WriteKeyValue("Printer ID", printerId); - ConsoleHelper.WriteKeyValue("Share ID", shareId); + catch (Exception printerCleanupEx) + { + ConsoleHelper.WriteWarning($"Failed to delete printer: {printerCleanupEx.Message}"); + } } } } diff --git a/BadgeReleaseDemo/README.md b/BadgeReleaseDemo/README.md index b6db777..c292974 100644 --- a/BadgeReleaseDemo/README.md +++ b/BadgeReleaseDemo/README.md @@ -97,18 +97,11 @@ Edit `appsettings.json`: | `AppId` | Your Entra ID app registration client ID (GUID) | `a1b2c3d4-e5f6-7890-abcd-ef1234567890` | | `Tenant` | Your tenant domain or GUID | `contoso.onmicrosoft.com` or a tenant GUID | -The remaining settings point to commercial production Universal Print endpoints. For government cloud environments, see below. +The remaining settings point to commercial production Universal Print endpoints. ### Government Cloud -For US Government cloud environments, update the service URLs in `appsettings.json`: - -| Setting | GCC | GCCH | DoD | -|---------|-----|------|-----| -| `GraphBaseUrl` | `https://graph.microsoft.com/v1.0` | `https://graph.microsoft.us/v1.0` | `https://dod-graph.microsoft.us/v1.0` | -| `GraphPrintBaseUrl` | `https://gcc-graph.print.azure.us/v1.0` | `https://graph.print.azure.us/v1.0` | `https://graph.print-dod.azure.us/v1.0` | -| `RegistrationBaseUrl` | `https://gcc-register.print.azure.us` | `https://register.print.azure.us` | `https://register.print-dod.azure.us` | -| `IppServiceBaseUrl` | `https://gcc-print.print.azure.us` | `https://print.print.azure.us` | `https://print.print-dod.azure.us` | +Government cloud is not supported by this demo today. Badge Release APIs in this demo target commercial Universal Print endpoints only. ## Build & Run @@ -139,14 +132,9 @@ BadgeReleaseDemo/ │ └── PrintJobSubmission.cs # Job creation, document upload, job start via Graph │ ├── IppOperations/ +│ ├── MinimalIpp.cs # Minimal custom IPP serializer/parser for required operations │ └── PrinterIppClient.cs # IPP INFRA operations + Badge REST API call │ -├── IppLibrary/ # IPP protocol serialization library (vendored) -│ ├── IPPRequest.cs # IPP request building and serialization -│ ├── IPPResponse.cs # IPP response parsing -│ ├── IPPAttribute.cs # IPP attribute types and encoding -│ └── ... (28 files) # Constants, datatypes, encoding helpers -│ ├── Helpers/ │ ├── ConsoleHelper.cs # Colored console output helpers │ └── CryptoHelper.cs # RSA keypair + CSR generation (BouncyCastle)