{"id":110297,"date":"2024-09-23T07:00:00","date_gmt":"2024-09-23T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110297"},"modified":"2024-09-23T09:10:24","modified_gmt":"2024-09-23T16:10:24","slug":"20240923-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240923-00\/?p=110297","title":{"rendered":"Going beyond the empty set: Embracing the power of other empty things"},"content":{"rendered":"<p>The empty set contains nothing. This sounds really silly, but it&#8217;s actually really nice.<\/p>\n<p>The Windows Runtime has a policy that if a method returns a collection (such as an <code>IVector<\/code>), and the method produces no results, then <a title=\"Embracing the power of the empty set in API design (and applying this principle to selectors and filters)\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20240812-00\/?p=110121\"> it should return an empty collection, rather than a null reference<\/a>. That way, consumers can just iterate over the collection without having to deal with a null test.<\/p>\n<p>For example, suppose you have a method <code>Widget::<wbr \/>Get\u00adAssociated\u00adDoodads<\/code> which returns an <code>IVectorView&lt;Doodad&gt;<\/code> representing the <code>Doodad<\/code> objects that have been associated with a <code>Widget<\/code> object. If no <code>Doodad<\/code>s have been associated with the <code>Widget<\/code>, then it should return an empty vector, not a null pointer. That allows developers to write the natural-looking code:<\/p>\n<pre>\/\/ C#\r\nforeach (var doodad in widget.GetAssociatedDoodads()) {\r\n    \u27e6 process each doodad \u27e7\r\n}\r\n\r\n\/\/ C++\/WinRT\r\nfor (auto&amp;&amp; doodad : widget.GetAssociatedDoodads()) {\r\n    \u27e6 process each doodad \u27e7\r\n}\r\n\r\n\/\/ JavaScript\r\nwidget.GetAssociatedDoodads().forEach(doodad =&gt;\r\n{\r\n    \u27e6 process each doodad \u27e7\r\n});\r\n<\/pre>\n<p>rather than having to insert a null test (which is easily forgotten):<\/p>\n<pre>\/\/ C#\r\nvar doodads = widget.GetAssociatedDoodads();\r\nif (doodads != null) { \/\/ annoying null test\r\n    foreach (var doodad in widget.GetAssociatedDoodads()) {\r\n        \u27e6 process each doodad \u27e7\r\n    }\r\n}\r\n\r\n\/\/ C++\/WinRT\r\nauto doodads = widget.GetAssociatedDoodads();\r\nif (doodads) { \/\/ annoying null test\r\n    for (auto&amp;&amp; doodad : doodads) {\r\n        \u27e6 process each doodad \u27e7\r\n    }\r\n}\r\n\r\n\/\/ JavaScript\r\nvar doodads = widget.GetAssociatedDoodads();\r\nif (doodads) { \/\/ annoying null test\r\n    doodads.forEach(doodad =&gt;\r\n    {\r\n        \u27e6 process each doodad \u27e7\r\n    });\r\n}\r\n<\/pre>\n<p>The principle of the empty collection applies to other types of collections, like <code>IMap&lt;K, V&gt;<\/code>, <code>array<\/code>. You can think of strings as collections of characters, and you can think of memory buffers (such as <code>IBuffer<\/code>) as collections of bytes.<\/p>\n<p>An example of a poor design is the <code>Cryptographic\u00adBuffer<\/code> class. (Sorry, <code>Cryptographic\u00adBuffer<\/code>, for throwing you under the bus.)<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"3\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Method<\/th>\n<th>Expected Result<\/th>\n<th>Actual Result<\/th>\n<\/tr>\n<tr>\n<td><code>buffer = ConvertStringToBinary(\"\");<\/code><\/td>\n<td rowspan=\"5\"><code>buffer != null<\/code><br \/>\n<code>buffer.Length == 0<\/code><\/td>\n<td rowspan=\"4\"><code>buffer == null<\/code><br \/>\n<code>buffer.Length \/* crashes *\/<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buffer = CreateFromByteArray(new[] {});<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buffer = DecodeFromBase64String(\"\");<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buffer = DecodeFromHexString(\"\");<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>buffer = GenerateRandom(0);<\/code><\/td>\n<td><code>buffer != null<\/code><br \/>\n<code>buffer.Length == 0<\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>If the <code>Convert\u00adString\u00adTo\u00adBinary<\/code>, <code>Create\u00adFrom\u00adByte\u00adArray<\/code>, <code>Decode\u00adFrom\u00adBase64\u00adString<\/code>, <code>Decode\u00adFrom\u00adHex\u00adString<\/code> are given empty strings or arrays, you expect them to produce an empty buffer, but instead they return <i>no buffer at all<\/i>.<\/p>\n<p>This means that code like this looks correct:<\/p>\n<pre>\/\/ Write the string to a file as UTF-8\r\nvar buffer = CryptographicBuffer.ConvertStringToBinary(\r\n        BinaryStringEncoding.Utf8, message);\r\nawait FileIO.WriteBufferAsync(storageFile, buffer);\r\n<\/pre>\n<p>but then you discover (probably at a very inconvenient moment) that it crashes if the message is an empty string, because <code>Convert\u00adString\u00adTo\u00adBinary<\/code> returned <code>null<\/code> (instead of a non-null reference to an empty buffer), and then <code>Write\u00adBuffer\u00adAsync<\/code> threw an invalid parameter exception because the buffer cannot be null.<\/p>\n<p>On the other hand, if you ask <code>Generate\u00adRandom<\/code> to generate zero random bytes, it correctly gives you an empty buffer, rather than a null pointer. So at least one of the methods in the <code>CryptographicBuffer<\/code> class understands how empty collections work.<\/p>\n<p>As a bonus insult, the <code>Cryptographic\u00adBuffer.<wbr \/>Compare<\/code> method requires that both buffers be non-null, so you can&#8217;t even do this:<\/p>\n<pre>\/\/ Do it twice and confirm the results are the same\r\nvar buffer1 = CryptographicBuffer.ConvertStringToBinary(\r\n        BinaryStringEncoding.Utf8, message);\r\nvar buffer2 = CryptographicBuffer.ConvertStringToBinary(\r\n        BinaryStringEncoding.Utf8, message);\r\nif (CryptographicBuffer.Compare(buffer1, buffer2)) {\r\n    \/\/ the buffers are equal\r\n}\r\n<\/pre>\n<p>The code crashes if the message is an empty string because <code>buffer1<\/code> and <code>buffer2<\/code> will be <code>null<\/code>, which is not a valid parameter to <code>Cryptographic\u00adBuffer.<wbr \/>Compare<\/code>. It&#8217;s a bit ironic that the <code>Cryptographic\u00adBuffer<\/code> can dish out null buffers but can&#8217;t take them.<\/p>\n<p>Cryptography in general seems to have a hard time with the concept of zero. The <code>User\u00adData\u00adProtection\u00adManager.<wbr \/>Protect\u00adBuffer\u00adAsync<\/code> method, for example, rejects attempts to protect an empty buffer, so if you want to protect a buffer that might be empty, you need to special-case the empty buffer.<\/p>\n<pre>\/\/ This version crashes if the buffer is empty.\r\nstatic class Protector\r\n{\r\n    static UserDataProtectionManager manager =\r\n        UserDataProtectionManager.TryGetDefault();\r\n\r\n    public Task&lt;IBuffer&gt; ProtectBufferAsync(IBuffer buffer)\r\n    {\r\n        if (manager != null) {\r\n            return await manager.ProtectBufferAsync(buffer,\r\n                    UserDataAvailability.AfterFirstUnlock);\r\n        } else {\r\n            \/\/ No protection available - leave unprotected.\r\n            return buffer;\r\n        }\r\n    }\r\n\r\n    public Task&lt;IBuffer&gt; UnprotectBufferAsync(IBuffer buffer)\r\n    {\r\n        if (manager != null) {\r\n            return await manager.UnProtectBufferAsync(buffer);\r\n        } else {\r\n            \/\/ No protection available - it was left unprotected.\r\n            return buffer;\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>A na\u00efve way of fixing this is to detect an empty buffer and just skip the <code>Protect\u00adBuffer\u00adAsync<\/code> call, letting an empty buffer be its own protected buffer. This is a bad idea, however, because a bad guy who sees an empty protected buffer will know that this represents an empty unprotected buffer. If the buffer represents a password, then they will know that the password is blank!<\/p>\n<p>If you choose some sentinel non-empty buffer value to represent a non-empty buffer, you then have to have some way of distinguishing this from a genuine non-empty buffer that happens to match your sentinel. In mathematical terms, your function that converts buffers to non-empty buffers needs to be injective. One way is to append a dummy byte to the buffer, and remove the dummy byte when unprotecting.<\/p>\n<pre>\/\/ C#\r\n\r\n\/\/ Work around inability to protect empty buffers\r\n\/\/ by appending a dummy byte to all buffers.\r\nvar paddedBuffer = WindowsRuntimeBuffer.Create(buffer.Length + 1);\r\npaddedBuffer.Length = actualBuffer.Capacity;\r\nbuffer.CopyTo(paddedBuffer);\r\nvar protectedBuffer = await manager.ProtectBufferAsync(\r\n    paddedBuffer, UserDataAvailability.AfterFirstUnlock);\r\n\r\n\/\/ Reverse the workaround by removing the dummy byte\r\n\/\/ after unprotecting.\r\nvar result = await manager.UnprotectBufferAsync(protectedBuffer);\r\nif (result.Status == UserDataBufferUnprotectStatus.Succeeded)\r\n{\r\n    var trimmedBuffer = result.UnprotectedBuffer;\r\n    trimmedBuffer.Length = trimmedBuffer.Length - 1;\r\n    \u27e6 do something with the trimmed buffer \u27e7\r\n}\r\n\r\n\/\/ C++\r\n\r\n\/\/ Work around inability to protect empty buffers\r\n\/\/ by appending a dummy byte to all buffers.\r\nauto length = buffer.Length();\r\nauto paddedBuffer = winrt::Buffer(length + 1);\r\npaddedBuffer.Length(length + 1);\r\nmemcpy_s(paddedBuffer.data(), length, buffer.data(), length);\r\nauto protectedBuffer = co_await manager.ProtectBufferAsync(\r\n    paddedBuffer, winrt::UserDataAvailability::.AfterFirstUnlock);\r\n\r\n\/\/ Reverse the workaround by removing the dummy byte\r\n\/\/ after unprotecting.\r\nauto result = co_await manager.UnprotectBufferAsync(protectedBuffer);\r\nif (result.Status() == winrt::UserDataBufferUnprotectStatus::Succeeded) {\r\n    auto trimmedBuffer = result.UnprotectedBuffer();\r\n    trimmedBuffer.Length(trimmedBuffer.Length() - 1);\r\n    \u27e6 do something with the trimmed buffer \u27e7\r\n}\r\n<\/pre>\n<p>The inability to handle zero-byte buffers makes everybody&#8217;s life harder.<\/p>\n<p>Zero. It&#8217;s a valid number. Please support it.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Just because there&#8217;s nothing in it doesn&#8217;t mean it&#8217;s not valid.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110297","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Just because there&#8217;s nothing in it doesn&#8217;t mean it&#8217;s not valid.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110297","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=110297"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110297\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110297"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110297"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110297"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}