<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://zhineng.li/feed.xml" rel="self" type="application/atom+xml" /><link href="https://zhineng.li/" rel="alternate" type="text/html" /><updated>2024-12-06T08:40:40+00:00</updated><id>https://zhineng.li/feed.xml</id><title type="html">Li Zhineng</title><subtitle>Just another Jekyll site.</subtitle><entry><title type="html">Represent 64-bit Unsigned Integer in PHP</title><link href="https://zhineng.li/php-64bit-unsigned-integer.html" rel="alternate" type="text/html" title="Represent 64-bit Unsigned Integer in PHP" /><published>2024-12-06T00:00:00+00:00</published><updated>2024-12-06T00:00:00+00:00</updated><id>https://zhineng.li/php-64bit-unsigned-integer</id><content type="html" xml:base="https://zhineng.li/php-64bit-unsigned-integer.html"><![CDATA[<p>Recently, while implementing a CRC-64 checksum for cloud file existence checks
(to avoid redundant uploads), I encountered an interesting issue with large
hexadecimal values in PHP. My usual approach involves a precomputed table for
faster calculations, and the polynomial result is represented in hexadecimal.
However, I noticed unexpected behavior when dealing with specific values.</p>

<p>For example, the hexadecimal value <code class="language-plaintext highlighter-rouge">0x995DC9BBDF1939FA</code> should correspond to
the decimal value <code class="language-plaintext highlighter-rouge">11051210869376104954</code>. However, when dumped in PHP, it’s
displayed as a floating-point number:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% php <span class="nt">-r</span> <span class="s2">"var_dump(0x995DC9BBDF1939FA);"</span>
float<span class="o">(</span>1.1051210869376104E+19<span class="o">)</span>
</code></pre></div></div>

<p>This is unexpected and undesirable for checksum calculations. Upon
investigation, I confirmed that the value is within the bounds of a
64-bit integer:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2^64 &gt; 11051210869376104954
True
</code></pre></div></div>

<p>The issue arises because, despite running on a 64-bit system, PHP’s maximum
signed integer value <code class="language-plaintext highlighter-rouge">PHP_INT_MAX</code> is smaller:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>% php <span class="nt">-r</span> <span class="s2">"var_dump(PHP_INT_MAX);"</span>
int<span class="o">(</span>9223372036854775807<span class="o">)</span>
</code></pre></div></div>

<p>This limitation stems from PHP’s lack of an unsigned integer data type. It uses
<code class="language-plaintext highlighter-rouge">int</code> (signed) and <code class="language-plaintext highlighter-rouge">float</code> for numerical representation.</p>

<p>To understand this better, let’s examine <code class="language-plaintext highlighter-rouge">PHP_INT_MAX</code> in binary:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>01111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111 (2)
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 63 bits
</code></pre></div></div>

<p>The most significant bit (MSB) represents the sign (0 for positive, 1 for
negative). In <code class="language-plaintext highlighter-rouge">PHP_INT_MAX</code>, the MSB is 0, and the remaining bits are set,
effectively representing <code class="language-plaintext highlighter-rouge">2^63</code>. When a number exceeds <code class="language-plaintext highlighter-rouge">PHP_INT_MAX</code>, PHP
automatically converts it to a float.</p>

<p>So, how can we accurately represent this large number in PHP?</p>

<h2 id="solutions">Solutions</h2>

<h3 id="handling-high-and-low-bits-separately">Handling High and Low Bits Separately</h3>

<p>We can split the 64-bit number into two 32-bit parts (high and low) and
reconstruct it during runtime:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(hex) 0x995DC9BBDF1939FA
(dec) 10011001_01011101_11001001_10111011_11011111_00011001_00111001_11111010
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      high bits                           low bits
</code></pre></div></div>

<p>To extract the high bits, we right-shift the number by 32 bits. For the low
bits, we apply a bitmask <code class="language-plaintext highlighter-rouge">0xFFFFFFFF</code> using the bitwise AND operator:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>

<span class="nv">$high</span> <span class="o">=</span> <span class="mh">0x995DC9BB</span><span class="p">;</span>
<span class="nv">$low</span> <span class="o">=</span> <span class="mh">0xDF1939FA</span><span class="p">;</span>
</code></pre></div></div>

<p>We can then reconstruct the original value by left-shifting the high bits by
32 bits and combining them with the low bits using the bitwise OR operator:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>

<span class="nv">$number</span> <span class="o">=</span> <span class="nv">$high</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span> <span class="o">|</span> <span class="nv">$low</span><span class="p">;</span> <span class="c1">// 0x995DC9BBDF1939FA</span>
</code></pre></div></div>

<h4 id="illustration">Illustration</h4>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(H) 10011001_01011101_11001001_10111011                                      = 0x995DC9BB
 &lt;&lt; 32 bits
  = -----------------------------------------------------------------------
    10011001_01011101_11001001_10111011_00000000_00000000_00000000_00000000
 OR
(L)                                     11011111_00011001_00111001_11111010  = 0xDF1939FA
  = -----------------------------------------------------------------------
    10011001_01011101_11001001_10111011_11011111_00011001_00111001_11111010  = 0x995DC9BBDF1939FA
</code></pre></div></div>

<h3 id="manual-hexadecimal-to-decimal-conversion">Manual Hexadecimal to Decimal Conversion</h3>

<p>Another approach involves manually converting the hexadecimal string to
decimal, bypassing PHP’s implicit conversion. We can achieve this using
the <code class="language-plaintext highlighter-rouge">pack</code> and <code class="language-plaintext highlighter-rouge">unpack</code> functions:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>

<span class="c1">// 0x995DC9BBDF1939FA</span>
<span class="nv">$number</span> <span class="o">=</span> <span class="nb">unpack</span><span class="p">(</span>
    <span class="s1">'J'</span><span class="p">,</span> <span class="nb">pack</span><span class="p">(</span><span class="s1">'H*'</span><span class="p">,</span> <span class="s1">'995DC9BBDF1939FA'</span><span class="p">)</span>
<span class="p">);</span>
</code></pre></div></div>

<p>We use pack with the format <code class="language-plaintext highlighter-rouge">H*</code> to encode the hexadecimal string into a binary
string. Then, unpack with format <code class="language-plaintext highlighter-rouge">J</code> decodes it as a 64-bit unsigned integer.
Refer to the <a href="https://www.php.net/manual/en/function.pack.php">PHP documentation</a>
for a complete description of format codes.</p>

<h2 id="performance-comparison">Performance Comparison</h2>

<p>A quick benchmark comparing the two methods over 1 million iterations reveals
a significant performance difference:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span>

<span class="c1">// helper</span>
<span class="c1">// ===================================================</span>

<span class="k">function</span> <span class="n">benchmark</span><span class="p">(</span><span class="kt">callable</span> <span class="nv">$callback</span><span class="p">):</span> <span class="kt">int</span>
<span class="p">{</span>
    <span class="nv">$start</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="p">(</span><span class="nb">microtime</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000</span><span class="p">);</span>

    <span class="nv">$callback</span><span class="p">();</span>

    <span class="nv">$end</span> <span class="o">=</span> <span class="p">(</span><span class="n">int</span><span class="p">)</span> <span class="p">(</span><span class="nb">microtime</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span> <span class="o">*</span> <span class="mi">1_000</span><span class="p">);</span>

    <span class="k">return</span> <span class="nv">$end</span> <span class="o">-</span> <span class="nv">$start</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// pack &amp;&amp; unpack</span>
<span class="c1">// ===================================================</span>

<span class="nv">$elapsed</span> <span class="o">=</span> <span class="nf">benchmark</span><span class="p">(</span><span class="k">function</span> <span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="mi">1_000_000</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="nb">unpack</span><span class="p">(</span>
            <span class="s1">'J'</span><span class="p">,</span> <span class="nb">pack</span><span class="p">(</span><span class="s1">'H*'</span><span class="p">,</span> <span class="s1">'995DC9BBDF1939FA'</span><span class="p">)</span>
        <span class="p">);</span>
    <span class="p">}</span>
<span class="p">});</span>

<span class="nb">printf</span><span class="p">(</span><span class="s1">'pack &amp; unpack: %dms'</span><span class="mf">.</span><span class="kc">PHP_EOL</span><span class="p">,</span> <span class="nv">$elapsed</span><span class="p">);</span>

<span class="c1">// bitwise</span>
<span class="c1">// ===================================================</span>

<span class="nv">$elapsed</span> <span class="o">=</span> <span class="nf">benchmark</span><span class="p">(</span><span class="k">function</span> <span class="p">()</span> <span class="p">{</span>
    <span class="k">for</span> <span class="p">(</span><span class="nv">$i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nv">$i</span> <span class="o">&lt;</span> <span class="mi">1_000_000</span><span class="p">;</span> <span class="nv">$i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="mh">0x995DC9BB</span> <span class="o">&lt;&lt;</span> <span class="mi">32</span> <span class="o">|</span> <span class="mh">0xDF1939FA</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">});</span>

<span class="nb">printf</span><span class="p">(</span><span class="s1">'bitwise      : %dms'</span><span class="mf">.</span><span class="kc">PHP_EOL</span><span class="p">,</span> <span class="nv">$elapsed</span><span class="p">);</span>
</code></pre></div></div>

<p>On my 2019 Intel iMac, the bitwise approach took 5ms, while the <code class="language-plaintext highlighter-rouge">pack</code>/<code class="language-plaintext highlighter-rouge">unpack</code>
method took 133ms – a 26x difference. This difference in performance is not
surprising. The functions involve several layers of overhead,
<a href="https://github.com/php/php-src/blob/a9a1ac2f8db80f1718d09f0f733aee388272e929/ext/standard/pack.c#L255-L375">format checking</a>,
<a href="https://github.com/php/php-src/blob/a9a1ac2f8db80f1718d09f0f733aee388272e929/ext/standard/pack.c#L486-L527">parsing the hexadecimal string</a>,
and ultimately, they also rely on bitwise operations
<a href="https://github.com/php/php-src/blob/a9a1ac2f8db80f1718d09f0f733aee388272e929/ext/standard/pack.c#L109-L115">internally</a>
to represent the unsigned integer. These extra steps contribute to the increased execution time.</p>

<p>Given that I’m dealing with some files around 50MiB, performance is crucial,
making the bitwise approach the preferred choice.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Recently, while implementing a CRC-64 checksum for cloud file existence checks (to avoid redundant uploads), I encountered an interesting issue with large hexadecimal values in PHP. My usual approach involves a precomputed table for faster calculations, and the polynomial result is represented in hexadecimal. However, I noticed unexpected behavior when dealing with specific values.]]></summary></entry><entry><title type="html">EdgeOS Cheatsheet</title><link href="https://zhineng.li/edgeos-cheatsheet.html" rel="alternate" type="text/html" title="EdgeOS Cheatsheet" /><published>2024-10-31T00:00:00+00:00</published><updated>2024-10-31T00:00:00+00:00</updated><id>https://zhineng.li/edgeos-cheatsheet</id><content type="html" xml:base="https://zhineng.li/edgeos-cheatsheet.html"><![CDATA[<p>The WebUI has been disabled on my router from the start, and occasionally I
need to tinker with it via the terminal. Since these tasks aren’t frequent
(thanks to the router’s stability), noting down essential commands can save
time and effort in the long run. Hopefully, they will be helpful to you
as well.</p>

<h2 id="configuration-mode">Configuration Mode</h2>

<p>EdgeOS CLI operates in two distinct modes: <strong>configuration</strong> mode and
<strong>operational</strong> mode. To enter configuration mode, simply type
<code class="language-plaintext highlighter-rouge">configure</code>, and when you’re done, use <code class="language-plaintext highlighter-rouge">exit</code> to return to operational mode.</p>

<h3 id="connect-to-the-internet-via-pppoe-on-eth0">Connect to the Internet via PPPoE on eth0</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>edit interfaces ethernet eth0
<span class="nb">set </span>description <span class="s2">"Internet (PPPoE)"</span>
<span class="nb">set </span>pppoe 0 user-id &lt;PPPoE Username&gt;
<span class="nb">set </span>pppoe 0 password &lt;PPPoE Password&gt;
</code></pre></div></div>

<h3 id="assign-a-static-ip-to-a-device-via-mac-address">Assign a Static IP to a Device via MAC Address</h3>

<p>Be sure to replace the subnet CIDR with your LAN’s configuration.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>edit service dhcp-server shared-network-name LAN subnet 192.168.10.0/24
<span class="nb">set </span>static-mapping &lt;name&gt; ip-address &lt;ip-address&gt;
<span class="nb">set </span>static-mapping &lt;name&gt; mac-address &lt;mac-address&gt;
</code></pre></div></div>

<h2 id="operational-mode">Operational Mode</h2>

<p>When you log in to your router, you’ll start in operational mode, identified
by the dollar sign (<code class="language-plaintext highlighter-rouge">$</code>). To switch back from configuration mode, just use
the <code class="language-plaintext highlighter-rouge">exit</code> command.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lizhineng@ubnt:~<span class="err">$</span>
</code></pre></div></div>

<h3 id="re-establish-pppoe-connection-on-pppoe0">Re-establish PPPoE Connection on pppoe0</h3>

<p>This is handy when you want to manually reconnect to the Internet, especially
since some ISPs forcefully terminate connections every 7 days. By picking your
hours, you can avoid this happening during a critical bug fix!</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>disconnect interface pppoe0
connect interface pppoe0
</code></pre></div></div>

<h3 id="list-dhcp-client-leases">List DHCP Client Leases</h3>

<p>You can optionally specify a pool. Without arguments, it lists clients
from all pools.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>show dhcp leases
</code></pre></div></div>

<p>For clients from the LAN pool only:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>show dhcp leases pool LAN
</code></pre></div></div>

<h3 id="list-all-network-interfaces">List All Network Interfaces</h3>

<p>A simplified version of <code class="language-plaintext highlighter-rouge">ip a</code>, giving a quick overview of network interfaces.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>show interfaces
</code></pre></div></div>

<h3 id="backup-and-restore-configuration">Backup and Restore Configuration</h3>

<p>Since the <code class="language-plaintext highlighter-rouge">save</code> command is broken in the official firmware (not a big deal,
it uses <code class="language-plaintext highlighter-rouge">scp</code> under the hood), you can still manually back up your
configuration. Don’t forget to set up SSH credentials beforehand. The backup
file will be suffixed with the date and time.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp /config/config.boot &lt;user&gt;@&lt;host&gt;:/path/to/config.boot.<span class="si">$(</span><span class="nb">date</span> <span class="s1">'+%Y%m%d%H%M%S'</span><span class="si">)</span>
</code></pre></div></div>

<p>To restore from a backup, use the <code class="language-plaintext highlighter-rouge">load</code> command:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>configure
load scp://&lt;user&gt;@&lt;host&gt;/path/to/config.boot
compare
commit<span class="p">;</span> save
</code></pre></div></div>

<h3 id="display-hardware-and-firmware-information">Display Hardware and Firmware Information</h3>

<p>Displays router model, serial number, firmware version, and uptime.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>show version
</code></pre></div></div>

<h2 id="useful-links">Useful Links</h2>

<ul>
  <li><a href="https://help.ui.com/hc/en-us/sections/360008075214-EdgeRouter">EdgeRouter Help Center</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[The WebUI has been disabled on my router from the start, and occasionally I need to tinker with it via the terminal. Since these tasks aren’t frequent (thanks to the router’s stability), noting down essential commands can save time and effort in the long run. Hopefully, they will be helpful to you as well.]]></summary></entry><entry><title type="html">Alibaba Cloud Edge Security Accelerator (ESA)</title><link href="https://zhineng.li/alibaba-cloud-esa.html" rel="alternate" type="text/html" title="Alibaba Cloud Edge Security Accelerator (ESA)" /><published>2024-10-14T07:27:00+00:00</published><updated>2024-10-14T07:27:00+00:00</updated><id>https://zhineng.li/alibaba-cloud-esa</id><content type="html" xml:base="https://zhineng.li/alibaba-cloud-esa.html"><![CDATA[<p>Alibaba Cloud recently launched a new CDN service, Edge Security Accelerator
(ESA), which is very similiar to Cloudflare. ESA includes built-in DDoS
protection, page rules, and a web application firewall (WAF). It also
provides edge computing, so you can write Javascript applications that run
on edge servers close to your users.</p>

<p>Unlike Cloudflare, which doesn’t charge for CDN traffic, ESA charges 0.198
RMB per gigabyte—lower than the DCDN rate. If I understand correctly, the data
charges are uniform regardless of the client’s traffic region.</p>

<p><img src="/assets/2024-10-14-alibaba-cloud-esa/dcdn-data-charges.webp" alt="The screenshot lists the DCDN traffic charges by region, with rates ranging from 0.15 Yuan/GB to 1.31 Yuan/GB." title="Screenshot of Alibaba Cloud DCDN data charges" /></p>

<p>The screenshot lists the DCDN data charges by region, with rates ranging
from 0.15 Yuan/GB to 1.31 Yuan/GB.</p>

<p><img src="/assets/2024-10-14-alibaba-cloud-esa/esa-overages.webp" alt="ESA data overage charges: 0.198 Yuan/GB for Basic Plan, 0.75 Yuan/GB for Standard Plan, and 1.2 Yuan/GB for Advanced Plan." title="Screenshot of Alibaba Cloud ESA overage charges" /></p>

<p>ESA data overage charges: 0.198 Yuan/GB for Basic Plan, 0.75 Yuan/GB for
Standard Plan, and 1.2 Yuan/GB for Advanced Plan.</p>

<p>I couldn’t find the data overage charges in the official documentation in
English, this screenshot is originally from the
<a href="https://help.aliyun.com/document_detail/2701851.html">Chinese version</a>
and translated into English.</p>

<p>While ESA doesn’t offer a free plan, the 9.9 RMB monthly subscription made
it tempting to give it a try. However, I found that setting up a site with
a subdomain requires the Enterprise plan—similar to Cloudflare’s approach,
where partial setups also require an Enterprise plan, but ESA allows you
to add a site with CNAME setup on the Basic Plan.</p>

<p><img src="/assets/2024-10-14-alibaba-cloud-esa/esa-subdomain-enterprise-plan.webp" alt="The website that you entered is a subdomain, for which you can only select the Enterprise plan." title="Screenshot of adding a new site to Alibaba Cloud ESA" /></p>

<p>So that may have to wait until my next project, with root domain.</p>

<h2 id="useful-links">Useful Links</h2>

<ul>
  <li><a href="https://www.alibabacloud.com/help/en/dcdnnext">ESA Documentation - English</a></li>
  <li><a href="https://www.alibabacloud.com/help/en/dcdnnext/latest/package-comparison">ESA Plan Comparison - English</a></li>
  <li><a href="https://help.aliyun.com/product/2673927.html">ESA Documentation - Chinese</a></li>
  <li><a href="https://help.aliyun.com/document_detail/2701845.html">ESA Plan Comparison - Chinese</a></li>
</ul>]]></content><author><name></name></author><summary type="html"><![CDATA[Alibaba Cloud recently launched a new CDN service, Edge Security Accelerator (ESA), which is very similiar to Cloudflare. ESA includes built-in DDoS protection, page rules, and a web application firewall (WAF). It also provides edge computing, so you can write Javascript applications that run on edge servers close to your users.]]></summary></entry><entry><title type="html">Setting up a local Boulder to test integration with ACME</title><link href="https://zhineng.li/setup-local-boulder.html" rel="alternate" type="text/html" title="Setting up a local Boulder to test integration with ACME" /><published>2024-04-09T09:55:00+00:00</published><updated>2024-04-09T09:55:00+00:00</updated><id>https://zhineng.li/setup-local-boulder</id><content type="html" xml:base="https://zhineng.li/setup-local-boulder.html"><![CDATA[<p>Certificate Authorities (CAs) like Let’s Encrypt, ZeroSSL, and Google Trust
Services offer <a href="https://datatracker.ietf.org/doc/html/rfc8555">ACME</a> support.
Of particular interest is Let’s Encrypt; they open source their CA software,
<a href="https://github.com/letsencrypt/boulder">Boulder</a>, on GitHub.</p>

<p>Let’s Encrypt has a staging API for testing purposes. During development, it’s
easy to mess around with the immature code. By having a local version of
Boulder, we could experiment freely without affecting the CA servers.</p>

<p>We could use the ACME staging API for the staging environment and the local
Boulder for deployment.</p>

<p>Another advantage of having our own Boulder is that we can set the rate limit
to ridiculously low to better stimulate when the extreme situation happens.</p>

<h2 id="boulder-setup">Boulder Setup</h2>

<p>The <a href="https://github.com/letsencrypt/boulder?tab=readme-ov-file#setting-up-boulder">Boulder repository</a>
is well documented on how to set it up with Docker.</p>

<h3 id="1-clone-the-repository">1: Clone the repository</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/letsencrypt/boulder/
<span class="nb">cd </span>boulder
</code></pre></div></div>

<h3 id="2-tweak-configurations">2: Tweak Configurations</h3>

<p>If you’re on a Mac, the Docker server runs inside a VM and has its isolated
network. We need to expose some ports, take DNS challenge as an example. Before
issuing the certificate, we need to go through a challenge to prove the
ownership of the domain name, the ACME server sends back a token, we need to do
some calculation, the result should be put into a challenge TXT record.</p>

<p>While we’re in a development environment, you definitely don’t want to provision
the real DNS records on your domain name. Behind the scenes, Boulder has a Pebble
challenge test server to help us easily mock the challenge result.</p>

<p>The test server with HTTP management API listens on port <strong>8055</strong>.</p>

<ol>
  <li>Open <strong>docker-compose.yml</strong>.</li>
  <li>Append “8055:8055” to the field <strong>services:boulder:ports</strong>.</li>
</ol>

<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># ...</span>
<span class="na">services</span><span class="pi">:</span>
  <span class="na">boulder</span><span class="pi">:</span>
    <span class="c1"># ...</span>
    <span class="na">ports</span><span class="pi">:</span>
      <span class="pi">-</span> <span class="s">4001:4001</span> <span class="c1"># ACMEv2</span>
      <span class="pi">-</span> <span class="s">4002:4002</span> <span class="c1"># OCSP</span>
      <span class="pi">-</span> <span class="s">4003:4003</span> <span class="c1"># OCSP</span>
      <span class="pi">-</span> <span class="s">8055:8055</span> <span class="c1"># Pebble Challenge Test Server &lt;- Add this line</span>
    <span class="c1"># ...</span>
<span class="c1"># ...</span>
</code></pre></div></div>

<p>If you want to customize the rate limits as well, edit the file
<strong>tests/rate-limit-policies.yml</strong> to adjust thresholds and windows.</p>

<h3 id="3-launch-boulder">3: Launch Boulder</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker compose up
</code></pre></div></div>

<p>Wait for a few moments, after everything is up and running, the endpoint should
be at <strong>http://localhost:4001/directory</strong>.</p>

<h2 id="mocking">Mocking</h2>

<p>Create a Pebble implementation to handle the record manipulation while
provisioning challenge resources. In the DNS challenge, all we need to do is
mock the TXT record response.</p>

<p>Check out the <a href="https://github.com/letsencrypt/pebble/blob/629e80b8edcd46b7788a58e2752890e68a0cae23/cmd/pebble-challtestsrv/README.md">Pebble documentation</a>
to explore additional mocking abilities.</p>

<h3 id="add-txt-record">Add TXT Record</h3>

<p>To mock a “DNS-01” challenge response for “_acme-challenge.example.com” with
the value “foo”:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-d</span> <span class="s1">'{"host":"_acme-challenge.example.com.", "value": "foo"}'</span> http://localhost:8055/set-txt
</code></pre></div></div>

<h3 id="remove-txt-record">Remove TXT Record</h3>

<p>To remove the mocking of the “DNS-01” challenge response for
“_acme-challenge.example.com”:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-d</span> <span class="s1">'{"host":"_acme-challenge.example.com."}'</span> http://localhost:8055/clear-txt
</code></pre></div></div>

<p>Note that the host name must be in complete domain name (FQDN) format with a
trailing dot.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Certificate Authorities (CAs) like Let’s Encrypt, ZeroSSL, and Google Trust Services offer ACME support. Of particular interest is Let’s Encrypt; they open source their CA software, Boulder, on GitHub.]]></summary></entry><entry><title type="html">Hello, 2019!</title><link href="https://zhineng.li/hello-2019.html" rel="alternate" type="text/html" title="Hello, 2019!" /><published>2019-01-02T09:18:15+00:00</published><updated>2019-01-02T09:18:15+00:00</updated><id>https://zhineng.li/hello-2019</id><content type="html" xml:base="https://zhineng.li/hello-2019.html"><![CDATA[<p>I was totally a loser in the past, I messed up all the things, life, work, finance, family, and have no sense of time. The plan I made and the goals I set was almost failed with no exception. I wanted to learn something, made some progress or build something interesting, but my determination was always a flash in the pan even I realized that would destroy my career. I always see some people on the Internet, they have a strong passion to achieve their projects or goals, that’s really inspiring me.</p>

<p>This year, I think I must make a change, I wanna be those people who could keep their words and turns idea become reality, instead of a talker. I’ve been learning English for many years, but still can’t be speaking and writing it efficiently, so this year I made a decision to subscribe Unlimited English program on <a href="https://eslpod.com">ESLPod.com</a> and learn the whole lessons in a year. Everybody likes to make their annual plan at the beginning of the year, but due to my procrastination, I need to overcome it first, build good habits and stick to them. This year, no specific goals are set, no more goals like “I wanna read 36 books in a year” or something like “I wanna go jogging at least 180km this year”. Just build habits and make them stick. Life is a journey.</p>

<p>Enough sleeping time is also another important thing and for good health. Lack of sleep can make me dizzy, impatient,  short-tempered, irritable, affects working efficiency and hurts my brain. Health is everything and is the most valuable asset for us, also the time. In the past, I cannot measure the value of time and money properly, always wasting lots of precious time doing unimportant, petty little things, instead of getting things done. Sometimes paying a little, it could save my day.</p>

<p>In 2019, I’ll mainly dive into three aspects: 1) English learning, 2) Laravel (PHP) and 3) Mathematics. I hadn’t learned the systematic CS courses which I also need to make up AOSP, even so, thanks to my boss which gave me the great opportunity and more freedom to create value at Guoyou Trading. My belief is “Diligence can compensate for lack of natural talent（勤能补拙）”.</p>

<blockquote>
  <p>Don’t compare your chapter 1 to someone else’s chapter 20.</p>
</blockquote>

<p>Hope everyone could be the best version of themselves. Happy new year, and thank you for stopping by.</p>]]></content><author><name></name></author><category term="personal" /><summary type="html"><![CDATA[I was totally a loser in the past, I messed up all the things, life, work, finance, family, and have no sense of time. The plan I made and the goals I set was almost failed with no exception. I wanted to learn something, made some progress or build something interesting, but my determination was always a flash in the pan even I realized that would destroy my career. I always see some people on the Internet, they have a strong passion to achieve their projects or goals, that’s really inspiring me.]]></summary></entry></feed>