<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title></title>
    <link rel="self" type="application/atom+xml" href="https://hamzah.syedahmed.net/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://hamzah.syedahmed.net"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-11-08T00:00:00+00:00</updated>
    <id>https://hamzah.syedahmed.net/atom.xml</id>
    <entry xml:lang="en">
        <title>How we placed first in RoboCup Junior NSW State</title>
        <published>2026-11-08T00:00:00+00:00</published>
        <updated>2026-11-08T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hamzah.syedahmed.net/posts/robocup/"/>
        <id>https://hamzah.syedahmed.net/posts/robocup/</id>
        
        <content type="html" xml:base="https://hamzah.syedahmed.net/posts/robocup/">&lt;p&gt;Over the past 2 years, I&#x27;ve led a team building robots that play soccer
autonomously to compete in the RoboCup Junior competition. In August, our team
placed first in the Opens Soccer division at the RoboCup Junior Australia NSW
State competition. We did not pursue nationals due to being busy with our HSC
exams. My role primarily involved working on the software and
firmware for the robot, as well as reviewing and integrating hardware and
software components to create a functioning system. In this blog post I&#x27;ll
discuss my contribution to this project as well as some technical details that
could help future teams.&lt;&#x2F;p&gt;
&lt;p&gt;But first, here is a highlights video from the state competition:&lt;&#x2F;p&gt;
&lt;div style=&quot;width: 100%; display: flex; flex-direction: column; align-items: center;&quot;&gt;
&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https:&#x2F;&#x2F;www.youtube.com&#x2F;embed&#x2F;YMHtccQzc7o?si=wODEcH7y0DGiB2YJ&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen&gt;&lt;&#x2F;iframe&gt;
&lt;&#x2F;div&gt;
&lt;!--

Goals:
| Scored | Conceded |
| 4 | 1 |
| 5 | 1 |
| 2 | 4 |
SF: | 5 | 1 |
F: | 4 | 3 |

Total: | 20 | 10 |

--&gt;
&lt;p&gt;Overall, our robots scored 20 goals throughout the competition while only
conceding 10. Some things you may notice from this video:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The robots can both rotate and translate, tending to &quot;drift&quot; laterally to receive the ball&lt;&#x2F;li&gt;
&lt;li&gt;Our robots are not particularly fast and in some cases significantly slower than the other robots,
so speed was not our main advantage&lt;&#x2F;li&gt;
&lt;li&gt;Many times either our robots or the opposing teams&#x27; robots are not fully functioning&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;I will explain both the factors leading towards our success and improvements that we can
make and in many cases had already made partial progress on later in this post. Before that,
I will explain the overall robot design to contextualise these points.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;robot-architecture&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#robot-architecture&quot; aria-label=&quot;Anchor link for: robot-architecture&quot;&gt;Robot architecture&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;poster.png&quot; alt=&quot;Team High Scorers RoboCup Poster&quot; &#x2F;&gt;
This poster describes our overall robot design. My primary role was working on
the control software, computer vision pipeline and PCB (printed circuit board)
design but some other aspects of the robot are described here for context.
Notably:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Mecanum wheels allow the robot to move in any direction, moving in any combination
of forwards, sideways or spinning in place. This allows the robot to quickly face
the ball. This is similar to the omniwheel arrangement used by many other teams.
We mainly chose mecanum wheels for their low cost.
&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;strafing.gif&quot; alt=&quot;Robot strafing&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;The dribbler is a rotating drum designed to impart backspin on the ball. This
dribbler, as well as the chassis was designed by another team member
responsible for mechanical design. This way the robot can maintain possession
of the ball. The dribbler drum is also driven by N20 motors from the same
motor controller PCB, with the other side of the drum connected to a bearing.
The 3D-printed drum has LEGO tires fitted over it for better grip. This is
quite effective, as the robot can dribble the ball backwards to defend its
goal:
&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;reverse_dribbling.gif&quot; alt=&quot;Robot dribbling backwards&quot; &#x2F;&gt;
No other team at the state competition had a dribbler, and the more
effective and precise control of the ball enabled by it was a significant
contributing factor to our success.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;The kicker mentioned on the poster was designed last-minute and had some interference
issues with the dribbler so it had to be removed.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;When the robot possesses the ball it is out of view of the camera, so a VL53L0X
time of flight (ToF) sensor allows the robot to ensure it really does have possession
of the ball. When the ball enters the dribbler zone, it shortens the distance detected
by the ToF sensor allowing the robot to register that it has successfully captured the ball.
&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;tof_vis.gif&quot; alt=&quot;Time of Flight sensor graph visualisation&quot; &#x2F;&gt;
The graph on the right in the above image shows the measurable decrease in
distance measured by the sensor when the ball enters the dribbler.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;pcb-design&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pcb-design&quot; aria-label=&quot;Anchor link for: pcb-design&quot;&gt;PCB design&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;div style=&quot;display: flex; max-width: 100%; align-items: center; flex-wrap: wrap;&quot;&gt;
&lt;img alt=&quot;PCB schematic&quot; src=&quot;pcb_schematic.png&quot; style=&quot;width:300px;&quot; &#x2F;&gt;
&lt;img alt=&quot;PCB layout&quot; src=&quot;pcb_layout.png&quot; style=&quot;width:300px;&quot; &#x2F;&gt;
&lt;img alt=&quot;PCB during initial testing&quot; src=&quot;pcb_irl.jpg&quot; style=&quot;width:300px;&quot; &#x2F;&gt;
&lt;&#x2F;div&gt;
&lt;p&gt;I designed the motor controller PCBs for these robots in KiCAD.
These motor controller PCBs perform closed-loop motor control for the mecanum wheels,
control the dribbler and distribute power from the battery with a fuse in case
of short circuit.&lt;&#x2F;p&gt;
&lt;p&gt;Our robots use four individually controlled motors for the mecanum wheels
which are connected to motor drivers (which each need 2 connections from the motor
terminals to the driver, and PWM and direction connections from the driver to
the MCU) and each of these motors have 4 further connections from the quadrature
encoders reporting motor rotation to the MCU. That gives a total of 32 connections
for locomotion alone.&lt;&#x2F;p&gt;
&lt;p&gt;Hence, the reasons I designed the PCBs were:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;To avoid needing to manually wire these connections during assembly, affording
us the ability to rapidly assemble and disassemble portions of the robot to
debug issues&lt;&#x2F;li&gt;
&lt;li&gt;For consistency in pinout between the two robots&lt;&#x2F;li&gt;
&lt;li&gt;For greater reliability: jumper wires can come loose or snap at joints if soldered&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The first revision of this board as the first PCB I had ever designed, and the
boards on our state competition robots were the second. To reduce the
opportunity for error, this PCB uses breakout boards such as a Raspberry Pi Pico
(a development board for the RP2040 MCU) and DRI0044 (a breakout for the
TB6612FNG motor driver IC) boards which each hold those difficult-to-solder
surface-mount ICs as well as their support circuitry.&lt;&#x2F;p&gt;
&lt;p&gt;These reliability improvements are a major theme of our team&#x27;s work in 2026,
and our success in the state competition was largely due to this: our robots
worked more consistently, and when they were working they could quickly gain
possession of the ball to easily score a goal.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;firmware&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#firmware&quot; aria-label=&quot;Anchor link for: firmware&quot;&gt;Firmware&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;The Raspberry Pi Pico on the aforementioned PCB runs Rust firmware I wrote. This
firmware takes movement commands from the main Raspberry Pi board over USB,
determines motor speeds needed to achieve that movement, and performs closed
loop control to keep motors at that speed. It uses the
&lt;a href=&quot;https:&#x2F;&#x2F;embassy.dev&#x2F;&quot;&gt;Embassy&lt;&#x2F;a&gt; framework which provides a hardware abstraction
layer and async runtime.&lt;&#x2F;p&gt;
&lt;img alt=&quot;Firmware diagram: USB CDC sends to COBS decoder sends to Inverse kinematics sends to PID control sends to Motor driver sends to Motor sends to Quadrature encoder inputs sends to Forward kinematics sends to Integrate velocity to get displacement sends to COBS encode sends to USB CDC&quot; src=&quot;firmware_diagram.png&quot; class=&quot;diagram&quot; &#x2F;&gt;
&lt;p&gt;The diagram above describes the general architecture for the firmware. The
kinematics and closed-loop control portions of this system are rather standard
in mechatronic systems, however this level of closed-loop control is rare
in RoboCup Junior. Recall that the robot has 3 axes of motion: longitudinal,
lateral and yaw. Inverse kinematics takes a velocity for each of
these 3 axes and converts it into appropriate mecanum wheel rotational
velocities to achieve that chassis velocity. Forwards kinematics conversely
takes the reported rotations from the motor encoder and converts it into a
chassis velocity. The combination of these two allow us to make any planar
movement on the robot.&lt;&#x2F;p&gt;
&lt;p&gt;The parts of the system I have described so far were already in place in the
original MicroPython implementation I wrote in 2025. The reason I rewrote this
firmware was related to the interface between the Raspberry Pi Pico (the
microcontroller on our motor controller PCB) and the Raspberry Pi 4 (the main
&quot;brains&quot; of the robot, processing camera data and making decisions). These two
components are linked by a micro-USB cable, carrying power and data. Vanilla
MicroPython does support taking input over USB (by &lt;code&gt;sys.stdin&lt;&#x2F;code&gt; or &lt;code&gt;input&lt;&#x2F;code&gt;) but
this same serial channel is used for the MicroPython REPL and there is no native
support for separating out serial channels or having any serious control over
USB configuration. Overall this led to a more brittle system that was difficult
to debug. I also made the mistake of trying to have odometry be reported
asynchronously from velocity commands, but the MicroPython setup is really not
designed for this in mind so I had lots of reliability issues.&lt;&#x2F;p&gt;
&lt;p&gt;Instead, in the Rust rewrite I created two separate USB communication device
class (CDC) channels: one for debug logging and one synchronous request&#x2F;response
interface for sending commands. The Embassy embedded framework made this
straightforward as I had complete control over USB packet processing on the
device. In the diagram above you see COBS decode&#x2F;encode blocks. COBS is a packet
framing scheme (basically an encoding that allows both sides to know where
commands start and end) so you may be wondering why a packet framing is needed
when USB already involves sending packets. For simplicity, I chose to use USB
CDC ACM (basically a serial interface) for the command channel, and this device
class abstracts over the packets and rather represents data sent as a stream of
bytes. Hence, a framing scheme is needed to delineate this stream.&lt;&#x2F;p&gt;
&lt;p&gt;The advantage of using CDC ACM is that on the Raspberry Pi side, the device
appears as a pair of standard TTYs which can be read and written to with
&lt;code&gt;pyserial&lt;&#x2F;code&gt;. In retrospect, I consider using CDC ACM to be a mistake. Alongside
being conceptually inelegant, this scheme means data sent and received passes
through the Linux kernel&#x27;s TTY layer (which is highly non-deterministic) which
has the right to buffer data arbitrarily. Despite my attempts to write resilient
retry logic to resend commands if no response is seen, competition day exposed
new link failure modes that our extensive testing could not find. If I were to
build this interface again I would create a custom device class and write a
user-space driver for it, to have greater visibility into hotplug and disconnect
events.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;control-software&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#control-software&quot; aria-label=&quot;Anchor link for: control-software&quot;&gt;Control software&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;h2 id=&quot;multi-process-architecture&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#multi-process-architecture&quot; aria-label=&quot;Anchor link for: multi-process-architecture&quot;&gt;Multi-process architecture&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I wrote the control software that runs on Raspberry Pi OS. The architectural
diagram below is from the repository README:&lt;&#x2F;p&gt;
&lt;img src=&quot;control_diagram.png&quot; alt=&quot;Architecture diagram showing a robot&#x27;s software components across two boards. On the Raspberry Pi, three boxes sit side by side. On the left, camera_streamer&#x2F; uses picamera2 to access the wide angle camera frames. In the centre, controller&#x2F; holds the main behaviour tree, containing the robot strategy and computer vision code, and includes a nested box, serialdebug.py, for debug logging for the Pico. On the right, ui&#x2F; reads GPIOs for buttons and switches and controls status LEDs. An arrow labelled Shared memory buffer points from camera_streamer to controller. An arrow labelled ZeroMQ Pub&#x2F;sub channel points from ui to controller. Below, on the Raspberry Pi Pico, a box labelled pico&#x2F; contains the Rust&#x2F;Embassy motor controller firmware. A two-way arrow between controller and pico is labelled Control&#x2F;odometry&#x2F;ToF and line sensor values from PCB. A second arrow labelled Debug logs runs from pico up to serialdebug.py inside controller.&quot; class=&quot;diagram&quot;&#x2F;&gt;
&lt;p&gt;As shown, there are four different processes running in parallel:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;camera_streamer&lt;&#x2F;code&gt;: This process accesses camera frames using the &lt;code&gt;picamera2&lt;&#x2F;code&gt;
library, writing them to a &lt;code&gt;numpy&lt;&#x2F;code&gt; array mapped in shared memory. I wrote it
as a separate process because &lt;code&gt;picamera2&lt;&#x2F;code&gt; is distributed by the Raspberry Pi
OS package repositories and is dependent on shared libraries distributed that
way. As I wanted to use virtual environments to manage dependencies for the
&lt;code&gt;controller&lt;&#x2F;code&gt; process this had to be separated.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;controller&lt;&#x2F;code&gt;: This is the main control process, sending commands to the motor
controller board and reading in camera frames from &lt;code&gt;camera_streamer&lt;&#x2F;code&gt;. There
is no synchronisation between &lt;code&gt;camera_streamer&lt;&#x2F;code&gt; and &lt;code&gt;controller&lt;&#x2F;code&gt; which means
that there can be tearing artefacts if this process reads while the streamer
is writing but this isn&#x27;t an issue for our computer vision workloads. The
robot strategy is organised in a behaviour tree using the &lt;code&gt;pytrees&lt;&#x2F;code&gt; library.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;serial_debug&lt;&#x2F;code&gt;: This is a process spawned by the &lt;code&gt;controller&lt;&#x2F;code&gt; process to
log data received through the USB debug channel to Rerun. This is primarily
useful for troubleshooting motor encoders or tuning the PID loop.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;ui&lt;&#x2F;code&gt;: This is kept separate from controller because of the need for the
&lt;code&gt;gpiozero&lt;&#x2F;code&gt; library, which also poses problems when installed from a virtualenv.
It handles button presses from the physical user interface on the robot
(used for starting, stopping, changing goals, and powering down the robot) an
publishes events over a ZeroMQ channel for the &lt;code&gt;controller&lt;&#x2F;code&gt; package to read.
This process also controls two important status LEDs: a pulsing LED to
show when the Raspberry Pi has completed boot, and another LED indicating
which goal the robot is configured to score into.&lt;&#x2F;p&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;code&gt;camera_streamer&lt;&#x2F;code&gt;, &lt;code&gt;controller&lt;&#x2F;code&gt;, and &lt;code&gt;ui&lt;&#x2F;code&gt; are all launched by their respective
&lt;code&gt;systemd&lt;&#x2F;code&gt; units on boot. This also enables these processes to be restarted
individually, and for their logs to be monitored using &lt;code&gt;journalctl&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;behaviour-tree&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#behaviour-tree&quot; aria-label=&quot;Anchor link for: behaviour-tree&quot;&gt;Behaviour tree&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;behaviour_tree.png&quot; alt=&quot;Behaviour tree diagram&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;The behaviour tree is the decision making engine for our robots, and determines
at a high level what the robot should do: for example whether the robot should
move towards the goal or towards the ball. I chose behaviour trees over other
approaches such as finite state machines mainly because they can very naturally
represent a notion of cascading priorities, where a higher priority task (like
gaining possession of the ball) can override a lower priority one (like
searching for the ball).&lt;&#x2F;p&gt;
&lt;p&gt;In the above diagram, every node with black outline is a behaviour, which can
briefly execute some code when they are &quot;ticked&quot;. After being ticked, behaviours
return &lt;code&gt;RUNNING&lt;&#x2F;code&gt; if the task they represent is ongoing or &lt;code&gt;SUCCESS&lt;&#x2F;code&gt; or &lt;code&gt;FAILURE&lt;&#x2F;code&gt;
upon completion.&lt;&#x2F;p&gt;
&lt;p&gt;Behaviour trees have three main composites (these are nodes in the
above diagram that point to multiple child nodes to combine their behaviours):&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parallel&lt;&#x2F;strong&gt; (parallelogram in the above diagram): will tick every node under
it every time it is ticked (overall effect of doing multiple tasks at once)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Sequence&lt;&#x2F;strong&gt; (The rectangle in the diagram): each time it is ticked
will tick each node under it in succession until any of its child nodes
return &lt;code&gt;RUNNING&lt;&#x2F;code&gt; or &lt;code&gt;FAILURE&lt;&#x2F;code&gt; (overall effect of doing one task after another)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Selector&lt;&#x2F;strong&gt; (The octagon in the diagram): each time it is ticked it will try
each child node under it until any of its child nodes return &lt;code&gt;RUNNING&lt;&#x2F;code&gt; or
&lt;code&gt;SUCCESS&lt;&#x2F;code&gt; (overall effect of trying different tasks until one of them works)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Despite the above behaviour tree being rather basic, it was reliable and this
contributed to our robots&#x27; performance. In early stages of robotics development,
high-level planning software is seldom a bottleneck, rather it is the hardware
and low-level control software that determines a system&#x27;s success. For almost
all of our two year development, improving the reliability and performance of
the lower levels of our stack was a better use of time than implementing a
sophisticated strategy, because these strategies are contingent on reliable and
effective hardware and control.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;computer-vision-and-localisation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#computer-vision-and-localisation&quot; aria-label=&quot;Anchor link for: computer-vision-and-localisation&quot;&gt;Computer vision and localisation&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Our robots&#x27; camera setup is unconventional, as we use a wide-angle forward
facing camera rather than a 360-degree mirror setup like most RoboCup Junior
robots. The purpose of a RoboCup computer vision system is to identify the
locations of the ball, goals and possibly field lines or even other robots.
More sophisticated strategies become possible when robots can determine
the physical relative coordinates of objects (in real-life units e.g. in
millimetres), which is called localisation. Localisation is a prerequisite
for proper trajectory planning and navigation, coordination between robots,
and deduction of where the ball is (by Bayesian inference, for example).&lt;&#x2F;p&gt;
&lt;p&gt;I wrote our robots&#x27; computer vision system, and localisation by fusing camera
data and odometry was my ultimate goal. However, as mentioned earlier, these
more sophisticated techniques are contingent on having basic hardware
functionality which I was also responsible for. In the time I had, I only
implemented a partial localisation system which I will describe below.
Localisation systems are really only seen at the international level in RoboCup
Junior. The main alternative approach I have heard of relies on using LiDAR to
locate the field walls. My approach is purely camera-based and not even
requiring an additional mirror, thus bringing the potential to make localisation
systems more accessible for less experienced teams.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;vision_raw.png&quot; alt=&quot;Raw wide angle image from camera&quot; &#x2F;&gt;
This is an unprocessed camera frame from the wide angle camera. If you look at
the white field lines, you will notice that they curve slightly due to a slight
fisheye effect.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;camera_ids.png&quot; alt=&quot;Goal, lines and ball identification&quot; &#x2F;&gt;
On this raw frame, HSV colour thresholds are used to identify objects, since the
RCJA soccer rules standardise the goals to be yellow and cyan, the ball to be
orange and field lines to be white with particular dimensions. Some teams use
more complex methods such as a machine learning model to do this, but I found
simple colour thresholding to produce acceptable results at our stage of
development. I also did further tuning after this screenshot is taken. The
purple magenta lines drawn are detected by a sequence of white thresholding,
canny edge detection, and then a probabilistic Hough line transform to detect
field lines. At this step in the pipeline, you can see a lot of garbage lines
detected above the field. These lines, alongside any other detections outside
the field, will be filtered out in a later step of the process.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;fisheye_undistort.png&quot; alt=&quot;Wide angle image undistorted so straight lines are straight in image&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is a view of the camera after undistortion. In &lt;code&gt;controller&#x2F;calibrate.py&lt;&#x2F;code&gt;, a
simple script calibrates the camera by fitting a model of fisheye distortion to
the coordinates of a flat checkerboard pattern. The distortion coefficients,
image resolution and camera matrix are written to a JSON file for later use. The
undistorted image above was produced using these coefficients, and as you can
see, the white lines are no longer curved as in the original image. Fully
undistorting the image is very computationally expensive and is only done for
debugging and visualisation purposes. Instead my code uses
&lt;code&gt;cv2.fisheye.undistortPoints&lt;&#x2F;code&gt; to undistort only specific coordinates needed to
be undistorted, as identified in the previous step. This undistortion step is
required to be able to accurately determine real world coordinates of objects in
the image.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;fisheye_flattened.png&quot; alt=&quot;Image projected to appear as a top-down view&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This is probably the most interesting step and is one that I have not ever seen
in RoboCup Junior before. In this step, I project points on the field from the
camera frame into real-world coordinates in millimetres, relative to the camera.
This is effectively partial localisation, and in our control software this
enables a much more effective ball acquisition tactic. I call this partial
localisation because it can find the relative coordinates of any object on the
field relative to the robot, but full localisation would give positions of
objects in a shared coordinate frame relative to the field itself.&lt;&#x2F;p&gt;
&lt;img alt=&quot;Diagram of ray-field intersection&quot; src=&quot;projection_diagram.png&quot; class=&quot;diagram&quot; &#x2F;&gt;
&lt;p&gt;The above is a visualisation of all points being projected this way, but again
the deployed code only projects the points that are needed for efficiency&#x27;s
sake. The principle that makes this projection possible is related to degrees
of freedom. In the camera frame seen by a robot, a pixel has two degrees of
freedom (row and column in image). For an object on the field, there are also
two degrees of freedom (since the objects only move on the plane, they cannot be
in the air). Thus, there is a mathematical function that transforms from one space
to the other.&lt;&#x2F;p&gt;
&lt;p&gt;More specifically each pixel corresponds to a point on an image plane (an
imaginary plane distance 1 away from the camera). &lt;code&gt;cv2.fisheye.undistortPoints&lt;&#x2F;code&gt;
can give us coordinates on this plane. Draw a ray from the camera origin through
this point on the image plane, extending it until it hits the field. The robot
is a fixed height in millimetres above the field (the robot moves only in planar
motion, it never travels up or down), so this intersection point between the
field and this plane in the field can be easily found. Since the distance from
the camera to the field is in millimetres, the coordinates of the intersection
point on the field is also in millimetres. Thus, any object &lt;em&gt;on the field&lt;&#x2F;em&gt; that
has been detected by the camera can be converted into coordinates on the field
relative to the robot.&lt;&#x2F;p&gt;
&lt;p&gt;You may also notice a slight inaccuracy between the computed location between
the projected coordinates of the ball and the real coordinates of the ball, as
shown in the diagram. This could be compensated by using a z-plane offset by
21mm so the coordinates actually lie centered in the ball, but real-life testing
found this inaccuracy to be negligible.&lt;&#x2F;p&gt;
&lt;details&gt;
    &lt;summary&gt;&lt;b&gt;Math details (click to expand)&lt;&#x2F;b&gt;&lt;&#x2F;summary&gt;
&lt;p&gt;Calling &lt;code&gt;cv2.fisheye.undistortPoints&lt;&#x2F;code&gt; without the &lt;code&gt;P&lt;&#x2F;code&gt; keyword parameter yields
the coordinates of each point on the image plane (the plane one unit distance
away in the direction the camera is pointing in). If we treat the camera point
as the origin of our coordinate system, and +Z points up then the ray has
parametric form:&lt;&#x2F;p&gt;
&lt;p&gt;$$ \vec{P} = t\vec{D} $$
where \(\vec{D}\) is the vector given by \(\bigl( \begin{smallmatrix} u \\
1 \\ v \end{smallmatrix} \bigr)\) (if our camera points in the +Y direction,
and &lt;code&gt;(u, v)&lt;&#x2F;code&gt; is the normalised cooridnates returned by &lt;code&gt;undistortPoints&lt;&#x2F;code&gt;).&lt;&#x2F;p&gt;
&lt;p&gt;As 3 separate equations:&lt;&#x2F;p&gt;
&lt;p&gt;$$x = ut$$
$$y = t$$
$$z = vt$$
We want \(z = -H\) where \(H\) is the height of the camera above the ground in
millimetres. So:
$$t = \frac{-H}{v}$$
Notice that \(t\) must be greater than zero for there to be an intersection.&lt;&#x2F;p&gt;
&lt;p&gt;Then we can say:
$$\vec{P} = -\frac{H}{D_z}\vec{D}$$&lt;&#x2F;p&gt;
&lt;p&gt;Though since the camera on our robot points below the horizontal, we need to apply
a rotation matrix to \(\vec{D}\) first. &lt;code&gt;scipy&lt;&#x2F;code&gt; conveniently provides a function
to do this for us:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;py&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-py &quot;&gt;&lt;code class=&quot;language-py&quot; data-lang=&quot;py&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;from &lt;&#x2F;span&gt;&lt;span&gt;scipy.spatial.transform &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span&gt;Rotation &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span&gt;R
&lt;&#x2F;span&gt;&lt;span&gt;rotation = R.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;from_euler&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;x&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, -&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;CAMERA_ELEVATION_ANGLE&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;span&gt;ray = rotation.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;apply&lt;&#x2F;span&gt;&lt;span&gt;(ray)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;My code initially did this essentially, however there is an optimisation that we
can do to make it much more efficient to do these operations in bulk (which was
how the projection on the entire image above was possible in less than a second).&lt;&#x2F;p&gt;
&lt;p&gt;A homography is a way to perform any perspective transformation between two
planes. To understand homographies, we must first define &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Homogeneous_coordinates&quot;&gt;homogenous
coordinates&lt;&#x2F;a&gt;. These are
coordinates introduce an extra dimension to points: so (x, y) is represented by
(x, y, 1). These coordinates are scale-invariant, meaning (2x, 2y, 2) is the
same 2D point as (x, y, 1). Homogenous coordinates are useful because they allow
more transformations to be represented by matrices compared to cartesian
coordinates.&lt;&#x2F;p&gt;
&lt;p&gt;A homography is an invertible matrix that operates in this coordinate system.
The ray-plane intersection operation can be performed as single 3x3 homography:&lt;&#x2F;p&gt;
&lt;p&gt;First obtain the rotation matrix \(\mathbf{R}\) to represent the rotation of the
camera. Then we want a homography that does:
$$x&#x27; = -\frac{H}{z} x$$
$$y&#x27; = -\frac{H}{z} y$$
$$z&#x27; = -H$$
This already looks like an operation on homogenous coordinates because any homogenous
coordinate \((x, y, z)\) is converted into the cartesian coordinates \((\frac{x}{z}, \frac{y}{z})\).
So we want a matrix that turns \((x, y, z)\) into \((-Hx, -Hy, z)\) for this to match
our desired transformation. This matrix is trivially:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\begin{bmatrix}
-H &amp;amp; 0 &amp;amp; 0 \\
0 &amp;amp; -H &amp;amp; 0 \\
0 &amp;amp; 0 &amp;amp; 1
\end{bmatrix}
$$&lt;&#x2F;p&gt;
&lt;p&gt;You can check this yourself by reading the matrix as columns left to right: each column
describes where its corresponding coordinate is mapped to. After converting the homography
into cartesian coordinates, the two coordinates will be coordinates on the plane of the field.
So the one homography matrix is:&lt;&#x2F;p&gt;
&lt;p&gt;$$
\mathbf{Q} = \begin{bmatrix}
-H &amp;amp; 0 &amp;amp; 0 \\
0 &amp;amp; -H &amp;amp; 0 \\
0 &amp;amp; 0 &amp;amp; 1
\end{bmatrix}\mathbf{R}
$$&lt;&#x2F;p&gt;
&lt;p&gt;This is effectively what the code does, apart from the coordinate system being
slightly different (+Y-up instead of +Z-up). You can read it &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;debater-coder&#x2F;robocup-26&#x2F;blob&#x2F;702d602f83700a072e3fd0a9fd023463864b91ad&#x2F;controller&#x2F;vision&#x2F;vision.py&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;&#x2F;details&gt;
&lt;p&gt;Looking at the projection above, you will notice the large amount of garbage
in the lower half of the image. This corresponds to the cases where the ray does
not intersect with the field, meaning the object is above the horizon line.
Mathematically this yields a computed location &lt;em&gt;behind&lt;&#x2F;em&gt; the camera, and the code
that projects points individually discards those points. This image was produced
by applying the homography matrix directly to the image which is why they are
shown. Conveniently, this provides a reliable method to discard ball or goal
detections outside the field.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;vision_field_detection.png&quot; alt=&quot;Field lines and ball being detected in real-world coordinates&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;Thus, with only a single forward-facing camera, my algorithm can compute
relative coordinates of any visible object on the field. In the time
we had, I was only able to take advantage of this to better capture the ball,
but as can be seen in the above image, the robot can get quite a lot of information
about its field location based on this data.&lt;&#x2F;p&gt;
&lt;p&gt;This is a classic state estimation problem: sensor data provides incomplete and
noisy data, and our goal is to infer the true state of the system. It was always
my goal to implement an extended Kalman filter to fuse odometry and vision data
to get good state estimates of robot positions and velocity. This would enable
true localisation, which would enable far more sophisticated strategies. I studied
this &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rlabbe&#x2F;Kalman-and-Bayesian-Filters-in-Python&quot;&gt;free online book&lt;&#x2F;a&gt;
to learn about these filters, written by the author of &lt;code&gt;filterpy&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;All bayesian filters operate around a loop of predict and measurement steps.
IMU, odometry and velocity command data would be used during the predict steps
to update the robot&#x27;s state forward in time based on a motion of how these
relate to the robot&#x27;s state variables (position and velocity). Kalman filters
keep track of error with a multivariate Gaussian distribution, and the error
covariance grows during this step. Then, in the measurement step, data from
field lines constitute absolute measurements of a robot&#x27;s position. Different
types of measurements can be made based on the type of &#x27;feature&#x27; visible of
field lines, for example seeing only two parallel field lines a given distance
apart may provide a measurement of only the &lt;em&gt;y&lt;&#x2F;em&gt; and &lt;em&gt;θ&lt;&#x2F;em&gt; axes, while seeing a
corner would update &lt;em&gt;x&lt;&#x2F;em&gt;, &lt;em&gt;y&lt;&#x2F;em&gt; and &lt;em&gt;θ&lt;&#x2F;em&gt;. Measurements can also incorporate
uncertainties propagated from the vision system: for example a small segment of
field line would propagate a greater uncertainty than a larger one.&lt;&#x2F;p&gt;
&lt;p&gt;Another useful application of filters that I wanted to implement was ball
deduction. If the location of both robots is known, the ball not being visible
actually conveys a large amount of information as to the location of the ball. A
discrete bayesian filter breaks up the field into a number of rectangular
&#x27;cells&#x27;, and assigns a probability of the ball being in that location. This is
initially set to be a uniform probability distribution. Then, if the robot does
not observe the ball in a cell, it can scale the probability for that cell down
by a large factor (the probability remaining would be nonzero due to the
uncertainty of measurement). This is the measure step. When the ball is seen,
high probability can be assigned to those regions and a different state
estimation technique such as a Kalman filter would be used instead. The update
step is applying a &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Convolution&quot;&gt;convolution&lt;&#x2F;a&gt; to
the grid to propagate beliefs forward through time, as when time passes it
becomes more uncertain whether those measurements in the past still apply.&lt;&#x2F;p&gt;
&lt;p&gt;This algorithm can quickly deduce where the ball is based on the area of the
field that is visible (and the projection techniques mentioned earlier give us
exactly this data), so the robot can effectively gain a sense of &lt;em&gt;object
permanence&lt;&#x2F;em&gt; and infer things about the ball location even when it is not
visible. It is a &lt;em&gt;multi-hypothesis&lt;&#x2F;em&gt; state estimation algorithm,
making it suitable for this type of deduction as multiple possible locations
for the ball can be considered.&lt;&#x2F;p&gt;
&lt;p&gt;This also gives rise to more complex defensive strategies. A popular
RoboCup Junior strategy seen in international games is &#x27;hiding the ball&#x27; where
the ball is controlled by a dribbler to face an angle where it is not visible to
other robots. This technique would defeat that, which is why we wanted to
implement it. However, we did not see this technique in use at our state
competition. Technically, the partial localisation data is enough to implement
a &#x27;hiding the ball&#x27; strategy, but I only achieved partial localisation a few
days before the competition, and the ball acquisition improvements to follow
were higher value per unit effort.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;ball-acquisition-tactic&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#ball-acquisition-tactic&quot; aria-label=&quot;Anchor link for: ball-acquisition-tactic&quot;&gt;Ball acquisition tactic&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The next problem is getting the robot to the ball, specifically such that it
is within the possession of the dribbler. This is a control theory problem:
I had to write some sort of controller that determines the appropriate command
velocities for &lt;em&gt;x&lt;&#x2F;em&gt; (longitudinal movement), &lt;em&gt;y&lt;&#x2F;em&gt; (lateral movement) and &lt;em&gt;θ&lt;&#x2F;em&gt; (yaw
rotation). In my initial implementation before partial localisation, I implemented
it in the simplest way I could think of:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;If the ball is to the left, turn left&lt;&#x2F;li&gt;
&lt;li&gt;If the ball is to the right turn right&lt;&#x2F;li&gt;
&lt;li&gt;If the ball is within a central region in the image, go forwards&lt;&#x2F;li&gt;
&lt;li&gt;If the ball is not visible spin in circles&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The last step could be improved with the discrete Bayesian logic described above,
but most of the time the ball is visible to at least one robot. Thus, improving
how the robot gets to the ball when it is visible is the most useful part to
optimise. Notice that the logic above takes no advantage of the lateral movement
capability of the robot, so its path is necessarily more indirect.&lt;&#x2F;p&gt;
&lt;p&gt;Thinking about translational movement, the robot should always move in a vector
towards the ball. Thus, the translational velocity should always be equal to the
relative displacement of the ball (as determined by the projection algorithm
described earlier) multiplied by some scalar. We want the robot to be aligned
with the ball for it to enter the dribbler, so this is something a PID
controller can do. At first glance, the translation also seems like something
suitable for PID, however PID does exactly the opposite of what we want to do.
PID control tries to get a system&#x27;s state to a certain value and have it stay at
that value, while we want to move towards the ball with some forwards velocity
in order for it to be captured by the dribbler. Thus, the displacement vector
approach to translation is the better approach. The code for this policy is
rather simple:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;py&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-py &quot;&gt;&lt;code class=&quot;language-py&quot; data-lang=&quot;py&quot;&gt;&lt;span&gt;theta = np.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;arctan2&lt;&#x2F;span&gt;&lt;span&gt;(target[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;], target[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;])
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;vel_theta = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;pid_theta&lt;&#x2F;span&gt;&lt;span&gt;(theta)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;# assume vel_scale is a scalar constant for now
&lt;&#x2F;span&gt;&lt;span&gt;velocity = np.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;array&lt;&#x2F;span&gt;&lt;span&gt;([target[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;], target[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;]]) * &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;vel_scale&lt;&#x2F;span&gt;&lt;span&gt;(theta)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;# Clip velocity vector length to 300mm&#x2F;s max
&lt;&#x2F;span&gt;&lt;span&gt;vel_length = np.linalg.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;norm&lt;&#x2F;span&gt;&lt;span&gt;(velocity)
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;vel_length != &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;:
&lt;&#x2F;span&gt;&lt;span&gt;    velocity *= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;min&lt;&#x2F;span&gt;&lt;span&gt;(vel_length, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;300&lt;&#x2F;span&gt;&lt;span&gt;) &#x2F; vel_length
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;# X-forwards coordinates
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.command.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;send_command&lt;&#x2F;span&gt;&lt;span&gt;(velocity[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;], velocity[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;], vel_theta, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;)
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;See the full code for the chase behaviour
&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;debater-coder&#x2F;robocup-26&#x2F;blob&#x2F;702d602f83700a072e3fd0a9fd023463864b91ad&#x2F;controller&#x2F;behaviours&#x2F;ChaseBehaviour.py&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;In the days before the competition, I just hardcoded a constant translational
velocity, and this policy was used through most of the competition (I was busy
debugging hardware issues). This was generally successful at capturing the ball
at long range, but when the robot started out close to the ball but misaligned,
it would move towards the ball with not enough time to properly capture it in
the dribbler.&lt;&#x2F;p&gt;
&lt;p&gt;So in the minutes before a match, I decided to fix it. The robot&#x27;s speed should
be a function of its alignment with the ball, with the speed being maximum when
it is perfectly aligned, and zero when it is fully out of alignment (so that the
robot has time to turn towards the ball). This reeks of \(y = 3\cos\theta\),
however I thought this would not be &#x27;aggressive&#x27; enough. The dribbler is quite
wide, so I wanted to prefer speed over alignment slightly more than what that
graph suggested. So I opened up &lt;a href=&quot;https:&#x2F;&#x2F;www.desmos.com&#x2F;&quot;&gt;Desmos&lt;&#x2F;a&gt; and as a team
we quickly found the parabola through those points. In retrospect I should have
fitted a quartic, since this graph is only slightly more aggressive than cosine.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;hamzah.syedahmed.net&#x2F;posts&#x2F;robocup&#x2F;trajectory_control.png&quot; alt=&quot;graph of parabola with x-intercepts at plus&#x2F;minus half-pi, with y-intercept at 3&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;This improved the robots&#x27; ability to capture the ball at short range.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;open-source-release&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#open-source-release&quot; aria-label=&quot;Anchor link for: open-source-release&quot;&gt;Open source release&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;Mechanical CAD, PCB designs and code are all released under open source licenses
in our &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;debater-coder&#x2F;robocup-26&#x2F;&quot;&gt;GitHub repository&lt;&#x2F;a&gt;. I hope
this can be of use for future teams.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Writing a Unix-like Operating System: Part 1</title>
        <published>2025-09-29T00:00:00+00:00</published>
        <updated>2025-09-29T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hamzah.syedahmed.net/posts/benchix-osdev/"/>
        <id>https://hamzah.syedahmed.net/posts/benchix-osdev/</id>
        
        <content type="html" xml:base="https://hamzah.syedahmed.net/posts/benchix-osdev/">&lt;p&gt;&lt;strong&gt;Scope:&lt;&#x2F;strong&gt; To build an operating system &lt;strong&gt;kernel&lt;&#x2F;strong&gt; that runs on &lt;strong&gt;&lt;em&gt;real hardware&lt;&#x2F;em&gt;&lt;&#x2F;strong&gt; (my Dell Inspiron 13 5330) &lt;strong&gt;resembling an early console-based Unix&lt;&#x2F;strong&gt; (such as Sixth Edition Research Unix) in kernel functionality.&lt;&#x2F;p&gt;
&lt;p&gt;This post documents my progress in building a Unix-like operating system for
the x86-64 platform.
It contains a brief overview of the state at the time of writing, followed by more technical
discussion of the design decisions I&#x27;ve made. The source code for this operating
system is available &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;debater-coder&#x2F;benchix&#x2F;tree&#x2F;master&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;This OS is still a work in progress, and is missing some key Unix features:&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Signals&lt;&#x2F;li&gt;
&lt;li&gt;Persistent filesystem&lt;&#x2F;li&gt;
&lt;li&gt;Working directory and relative paths in system calls&lt;&#x2F;li&gt;
&lt;li&gt;Environment variables&lt;&#x2F;li&gt;
&lt;li&gt;Many system calls pertaining to creation and deletion of files and directories&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;&lt;strong&gt;This kernel is currently highly unstable, and tends to crash. There is much
work to be done on error handling.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;I plan to write about these features in a Part 2, once I have implemented them.&lt;&#x2F;strong&gt;&lt;&#x2F;p&gt;
&lt;h1 id=&quot;overview&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#overview&quot; aria-label=&quot;Anchor link for: overview&quot;&gt;Overview&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;benchix&#x2F;screengrab.png&quot; alt=&quot;screen capture of benchix output on QEMU&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;benchix&#x2F;photo_2025-09-29_18-00-34.jpg&quot; alt=&quot;benchix on Dell Inspiron 13 5330, showing scrolling&quot; &#x2F;&gt;
&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;benchix&#x2F;photo_2025-09-29_17-59-38.jpg&quot; alt=&quot;Close-up of benchix on Dell Inspiron 13 5330&quot; &#x2F;&gt;&lt;&#x2F;p&gt;
&lt;p&gt;To learn more about modern operating systems, I&#x27;ve built an OS kernel
for the x86-64 platform (also known as AMD64). It
is written in the Rust programming language, and runs on
bare-metal. Current features:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A simple userspace shell&lt;&#x2F;li&gt;
&lt;li&gt;A kernel-mode text console device (controlled by reading and writing to
&lt;code&gt;&#x2F;dev&#x2F;console&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;A rudimentary VFS (Virtual file system) supporting different file systems
(including a device file system which contains &lt;code&gt;&#x2F;dev&#x2F;console&lt;&#x2F;code&gt;) under a single
root file system&lt;&#x2F;li&gt;
&lt;li&gt;A subset of Linux syscalls following Linux syscall calling conventions&lt;&#x2F;li&gt;
&lt;li&gt;Loading of static 64-bit ELF executables.&lt;&#x2F;li&gt;
&lt;li&gt;Preemptive multitasking on a single processor&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;fork()&lt;&#x2F;code&gt; and &lt;code&gt;exec()&lt;&#x2F;code&gt; based process spawning&lt;&#x2F;li&gt;
&lt;li&gt;Drivers for the GOP framebuffer (the graphical interface provided by the UEFI
firmware), the APIC interrupt controller, and for the emulated PS2 keyboard
connected through the IOAPIC&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;A detailed explanation of these features will follow.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;references&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#references&quot; aria-label=&quot;Anchor link for: references&quot;&gt;References&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;wiki.osdev.org&#x2F;Expanded_Main_Page&quot;&gt;OSDev Wiki&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;cdrdv2-public.intel.com&#x2F;858440&#x2F;325462-088-sdm-vol-1-2abcd-3abcd-4.pdf&quot;&gt;Intel® 64 and IA-32 Architectures Software Developer&#x27;s Manual&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;x64.syscall.sh&#x2F;&quot;&gt;Linux syscall reference | x64.syscall.sh&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;Linux man pages (eg: &lt;code&gt;man 2 read&lt;&#x2F;code&gt;)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;os.phil-opp.com&#x2F;&quot;&gt;Philipp Oppermann&#x27;s blog | Writing an OS in Rust&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;blog.wesleyac.com&#x2F;posts&#x2F;ioapic-interrupts&quot;&gt;Wesley Aptekar-Cassels | How to set up the APIC to get keyboard interrupts&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;x86-psABIs&#x2F;x86-64-ABI&#x2F;-&#x2F;jobs&#x2F;9388606854&#x2F;artifacts&#x2F;raw&#x2F;x86-64-ABI&#x2F;abi.pdf&quot;&gt;System V ABI&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mit-pdos&#x2F;xv6-public&quot;&gt;mit-pdos&#x2F;xv6-public: xv6 OS&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.redox-os.org&#x2F;redox-os&#x2F;kernel&quot;&gt;redox-os &#x2F; kernel&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;torvalds&#x2F;linux&#x2F;blob&#x2F;master&#x2F;include&#x2F;uapi&#x2F;asm-generic&#x2F;errno-base.h&quot;&gt;Linux kernel UAPI headers&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;pages.cs.wisc.edu&#x2F;~remzi&#x2F;OSTEP&#x2F;&quot;&gt;Operating Systems: Three Easy Pieces&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;nfil.dev&#x2F;kernel&#x2F;rust&#x2F;coding&#x2F;rust-kernel-to-userspace-and-back&#x2F;&quot;&gt;Rust-OS Kernel - To userspace and back! - nikofil’s blog&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;danluu.com&#x2F;malloc-tutorial&#x2F;&quot;&gt;Malloc tutorial&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;previous-attempts&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#previous-attempts&quot; aria-label=&quot;Anchor link for: previous-attempts&quot;&gt;Previous attempts&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;This is not my first attempt at building an OS. Soon after learning Rust in December 2022, I came across Philipp Oppermann&#x27;s
excellent &lt;a href=&quot;https:&#x2F;&#x2F;os.phil-opp.com&#x2F;&quot;&gt;Rust OS tutorial series&lt;&#x2F;a&gt;, which was my initial exposure to OS development. The series provides a
thorough walkthrough of the initial setup process of creating an OS for x86-64 (including text output,
interrupts, paging and heap allocation). This lead me to several attempts at creating an operating system:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;debater-coder&#x2F;bench&quot;&gt;bench&lt;&#x2F;a&gt;: An x86-64 C kernel using Limine bootloader (BIOS-only)
&lt;ul&gt;
&lt;li&gt;In this project I applied what I had learned from the Rust tutorials in a different context&lt;&#x2F;li&gt;
&lt;li&gt;Most progress here was with understanding
interrupts with the IDT (Interrupt Descriptor Table) and the legacy 8259 Programmable Interrupt Controller&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;debater-coder&#x2F;bench2&quot;&gt;bench2&lt;&#x2F;a&gt;: An x86-64 Rust kernel using the &lt;code&gt;bootloader&lt;&#x2F;code&gt; crate with
support for UEFI booting.
&lt;ul&gt;
&lt;li&gt;In this project, I mostly followed the Rust tutorials for implementing core structures (IDT, GDT, page table, heap),
but focused on getting keyboard support on UEFI. At the time of this writing, the Rust OS tutorials
rely on some functionality only available when booting from the legacy BIOS (8259 PIC and VGA text mode), which now
have more complex replacements in UEFI. In this attempt, I implemented keyboard input by following overall steps from
&lt;a href=&quot;https:&#x2F;&#x2F;blog.wesleyac.com&#x2F;posts&#x2F;ioapic-interrupts&quot;&gt;this post&lt;&#x2F;a&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h1 id=&quot;why-build-a-unix-clone&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#why-build-a-unix-clone&quot; aria-label=&quot;Anchor link for: why-build-a-unix-clone&quot;&gt;Why build a Unix clone?&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;I started &lt;strong&gt;benchix&lt;&#x2F;strong&gt; (this project) because I found myself getting stuck on architectural decisions in bench2. As a
beginner to OS development, I wanted to focus on the mechanisms of operating systems rather than the policies. This led me to re-scope the project to specifically implement a Unix-like kernel. In benchix, this means a few things:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;A virtual file system allowing devices to be accessed as files&lt;&#x2F;li&gt;
&lt;li&gt;Unix-like system calls (&lt;code&gt;read&lt;&#x2F;code&gt;, &lt;code&gt;write&lt;&#x2F;code&gt;,  &lt;code&gt;open&lt;&#x2F;code&gt;, &lt;code&gt;close&lt;&#x2F;code&gt;, &lt;code&gt;brk&lt;&#x2F;code&gt;, &lt;code&gt;fork&lt;&#x2F;code&gt;, &lt;code&gt;execve&lt;&#x2F;code&gt;, etc.)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;However, benchix isn&#x27;t a direct port or rewrite of any particular kernel. Where possible, I based my implementation on descriptions of kernel features from man pages or OSTEP (Operating Systems: Three Easy Pieces). The kernels I did reference were xv6 and Redox (linked to in references).
I mention areas where my implementation is based on one of these
kernels.&lt;&#x2F;p&gt;
&lt;h1 id=&quot;implementation-details&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#implementation-details&quot; aria-label=&quot;Anchor link for: implementation-details&quot;&gt;Implementation details&lt;&#x2F;a&gt;&lt;&#x2F;h1&gt;
&lt;p&gt;This is a roughly chronological record of my implementation, grouped by feature.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-bootloader&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-bootloader&quot; aria-label=&quot;Anchor link for: the-bootloader&quot;&gt;The bootloader&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The Rust &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-osdev&#x2F;bootloader&quot;&gt;bootloader&lt;&#x2F;a&gt; crate provides a way to create a bootable disk image from
a kernel. They have a &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;rust-osdev&#x2F;bootloader&#x2F;blob&#x2F;main&#x2F;docs&#x2F;create-disk-image.md&quot;&gt;guide&lt;&#x2F;a&gt; on setting up the two crates needed to boot.&lt;&#x2F;p&gt;
&lt;p&gt;The bootloader provides an &lt;code&gt;entry_point!&lt;&#x2F;code&gt; macro for use in the kernel and will pass a &lt;code&gt;boot_info&lt;&#x2F;code&gt; struct to the annotated function. This contains some important information such as:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The offset that physical memory is mapped at (memory mappings discussed later)&lt;&#x2F;li&gt;
&lt;li&gt;A pointer to the GOP framebuffer&lt;&#x2F;li&gt;
&lt;li&gt;The physical memory layout (used to determine what physical memory frames are available for allocation)&lt;&#x2F;li&gt;
&lt;li&gt;A pointer to the RSDP. The RSDP is the root pointer to the boot-time ACPI (Advanced Configuration and Power Interface) tables. They are very verbose so I use the &lt;code&gt;acpi&lt;&#x2F;code&gt; crate to parse these structures. I need this information to set up the interrupt controllers to receive keypresses.&lt;&#x2F;li&gt;
&lt;li&gt;The address the ramdisk is mapped at&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The other part of the bootloader crate runs in a &lt;code&gt;build.rs&lt;&#x2F;code&gt; at the root of the workspace. This file builds the C userspace into a &lt;code&gt;.tar&lt;&#x2F;code&gt; archive, and uses Cargo&#x27;s binary dependency feature to get the kernel binary. Then, it invokes the bootloader crate to create a GPT (GUID Partition Table) disk image.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;framebuffer-and-panic-handling&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#framebuffer-and-panic-handling&quot; aria-label=&quot;Anchor link for: framebuffer-and-panic-handling&quot;&gt;Framebuffer and panic handling&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;I use the &lt;a href=&quot;https:&#x2F;&#x2F;docs.rs&#x2F;noto-sans-mono-bitmap&#x2F;latest&#x2F;noto_sans_mono_bitmap&#x2F;&quot;&gt;noto_sans_mono_bitmap&lt;&#x2F;a&gt; crate to display monospaced text on the framebuffer.&lt;&#x2F;p&gt;
&lt;p&gt;I have a separate console (&lt;code&gt;PanicConsole&lt;&#x2F;code&gt;) for kernel panic that does not rely on heap allocated memory. My main console uses a heap-allocated backing text-buffer to hold text to avoid having to read from frame-buffer memory when scrolling (which can be slower). The panic console uses a &lt;code&gt;static mut&lt;&#x2F;code&gt; variable storing a pointer to the buffer and re-initialises the display from there. This is necessary because if the framebuffer was stored behind a lock, the panic handler may deadlock if the panicking thread was holding that lock.&lt;&#x2F;p&gt;
&lt;p&gt;After the heap is available, my main console is initialised from the framebuffer, and it is owned by the boot thread until the initialisation of the VFS. After this it is passed to the device filesystem to be accessed at &lt;code&gt;&#x2F;dev&#x2F;console&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;idt-and-gdt&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#idt-and-gdt&quot; aria-label=&quot;Anchor link for: idt-and-gdt&quot;&gt;IDT and GDT&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Initially, I was able to bring over my IDT (interrupt descriptor table) and GDT
setup from my old bench2 kernel (which itself matches closely with the Rust
OS tutorials).&lt;&#x2F;p&gt;
&lt;p&gt;To manage these structures I use the &lt;code&gt;x86_64&lt;&#x2F;code&gt; crate, which contains useful
wrappers around Intel 64 registers, structures and instructions. In the original
&lt;code&gt;bench&lt;&#x2F;code&gt; kernel (in C), I implemented some of these by hand, but delegating these
operations to a crate lets me narrow my scope to focusing more on the specifics
of building a kernel (threading, system calls, scheduling, concurrency) rather
than spending time on Intel 64 peculiarities.&lt;&#x2F;p&gt;
&lt;p&gt;I did have to make some modifications for userspace:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Adding &quot;flat&quot; code and data segment descriptors for userspace (these are mandatory but since I am in Long Mode they don&#x27;t actually set any segmentation limits).&lt;&#x2F;li&gt;
&lt;li&gt;The TSS (task state segment, which used to hold the task state in 32-bit mode, but in 64-bit mode now holds the interrupt stack table) gets a new entry (RSP0) to describe the stack to use when an interrupt occurs in Ring 3 (since interrupts are handled in Ring 0). In this case I use the current kernel stack for that thread. If I had one global stack to use for interrupts, then that stack would get overwritten if I do a context switch and then another interrupt occurs. This would mean that the execution would not be able to return to the previous thread, since the return address on the stack would have been overwritten.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Interestingly, long mode supports segmentation for the FS and GS segments. Most kernels use this to store per-processor information (like the current kernel stack). I don&#x27;t have multiprocessing yet so I have just been using what is effectively a &lt;code&gt;static mut&lt;&#x2F;code&gt; to store this information. However, I will consider using this approach when I add multiprocessing.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;memory-management&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#memory-management&quot; aria-label=&quot;Anchor link for: memory-management&quot;&gt;Memory management&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;bootloader-mappings&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#bootloader-mappings&quot; aria-label=&quot;Anchor link for: bootloader-mappings&quot;&gt;Bootloader mappings&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;&lt;img src=&quot;https:&#x2F;&#x2F;upload.wikimedia.org&#x2F;wikipedia&#x2F;commons&#x2F;thumb&#x2F;2&#x2F;2a&#x2F;AMD64-canonical--48-bit.svg&#x2F;400px-AMD64-canonical--48-bit.svg.png?20080703055030&quot; alt=&quot;Diagram of x86-64 address space&quot; &#x2F;&gt;
&lt;a href=&quot;https:&#x2F;&#x2F;commons.wikimedia.org&#x2F;wiki&#x2F;File:AMD64-canonical--48-bit.svg&quot;&gt;Vladsinger&lt;&#x2F;a&gt;, &lt;a href=&quot;https:&#x2F;&#x2F;creativecommons.org&#x2F;licenses&#x2F;by-sa&#x2F;3.0&quot;&gt;CC BY-SA 3.0&lt;&#x2F;a&gt;, via Wikimedia Commons&lt;&#x2F;p&gt;
&lt;p&gt;My kernel is a higher-half kernel. On x86-64, there are only 48 usable bits of the virtual address, and the rest are a sign-extension. This essentially breaks the address space into two &quot;halves&quot;, which can be distinguished between eachother using only a single bit check. Since virtual address space is so large (256 TiB!), I use the entire higher half for my kernel&#x27;s memory. This allows syscall handlers to quickly check whether a given buffer belongs to the process, to avoid the process reading the kernel&#x27;s address space (however, I do not currently mitigate against speculative execution exploits).&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;bootloader&lt;&#x2F;code&gt; crate supports configuring the addresses mappings it creates. This saves a bit of work configuring the address-space manually (and the physical offset mappings are needed to actually access the page tables).&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;HEAP_START&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0x_ffff_9000_0000_0000&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;KERNEL_STACK_START&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0xffff_f700_0000_0000&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;KERNEL_STACK_SIZE&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;80 &lt;&#x2F;span&gt;&lt;span&gt;* &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1024&lt;&#x2F;span&gt;&lt;span&gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; 80 KiB
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;LAPIC_START_VIRT&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0xffff_8fff_ffff_0000&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;IOAPIC_START_VIRT&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64 &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0xffff_a000_0000_0000&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub static &lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;BOOTLOADER_CONFIG&lt;&#x2F;span&gt;&lt;span&gt;: BootloaderConfig = {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; config = BootloaderConfig::new_default();
&lt;&#x2F;span&gt;&lt;span&gt;    config.kernel_stack_size = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;KERNEL_STACK_SIZE&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;    config.mappings.kernel_stack = Mapping::FixedAddress(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;KERNEL_STACK_START&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    config.mappings.physical_memory = Some(Mapping::FixedAddress(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0xffff_e000_0000_0000&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;    config.mappings.dynamic_range_start = Some(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0xffff_8000_0000_0000&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    config.mappings.dynamic_range_end = Some(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0xffff_8fff_fffe_ffff&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    config
&lt;&#x2F;span&gt;&lt;span&gt;};
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This configuration ensures that all kernel mappings created by the bootloader
are in the higher-half. The bootloader is configured to map the entire physical
address space at a fixed offset (&lt;code&gt;0xffff_e000_0000_0000&lt;&#x2F;code&gt;) to allow us to access
the page-tables (which store physical addresses). I have separate mappings for
&lt;code&gt;IOAPIC_START_VIRT&lt;&#x2F;code&gt; and &lt;code&gt;LAPIC_START_VIRT&lt;&#x2F;code&gt; which are used elsewhere to create
no-cache mappings for these memory-mapped interfaces. The dynamic range gives
the bootloader a range to place mappings which I haven&#x27;t specified (for example
the ramdisk).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;physical-memory-management&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#physical-memory-management&quot; aria-label=&quot;Anchor link for: physical-memory-management&quot;&gt;Physical Memory Management&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The physical memory manager allocates frames of physical memory for the kernel to use. These frames can be then mapped to virtual memory, or used through the physical offset mappings. Apart from the mappings allocated by the bootloader, all mappings are created by allocating one of these frames and creating a virtual address mappings.&lt;&#x2F;p&gt;
&lt;p&gt;One feature my allocator includes that wasn&#x27;t in the OS tutorials is deallocation. This allows physical frames to be reused after they are done with.
This requires the memory manager to keep track of free memory separately from the
structures provided by the bootloader. I use a bitmap structure for this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;derive&lt;&#x2F;span&gt;&lt;span&gt;(Debug)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;PhysicalMemoryManager&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;bitmap&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;&amp;#39;a mut&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;], &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; 0 for free, 1 for used
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;physical_offset&lt;&#x2F;span&gt;&lt;span&gt;: VirtAddr,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Each 0 or 1 in the bitmap corresponds to the availability of a 4KiB physical frame,
starting at physical address 0 up to the highest physical address.&lt;&#x2F;p&gt;
&lt;p&gt;Creating this structure involves the following process:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Find the highest address in the bootloader mappings to determine the bitmap size.&lt;&#x2F;li&gt;
&lt;li&gt;Find the first available memory region in the bootloader mappings that is large enough to store such a bitmap.&lt;&#x2F;li&gt;
&lt;li&gt;Get a &lt;code&gt;&amp;amp;&#x27;a mut [u64]&lt;&#x2F;code&gt; reference to that region (using the physical offset to get a virtual address to store in the base pointer) and store that as our bitmap.&lt;&#x2F;li&gt;
&lt;li&gt;Mark any reserved regions as used (the bootloader specifies this) &lt;strong&gt;as well as marking the frames backing the bitmap as used&lt;&#x2F;strong&gt;.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Now, frames can be allocated quite simply:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;unsafe impl&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; FrameAllocator&amp;lt;Size4KiB&amp;gt; for &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;PhysicalMemoryManager&lt;&#x2F;span&gt;&lt;span&gt;&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;&amp;#39;a&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;allocate_frame&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;mut &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Option&amp;lt;PhysFrame&amp;lt;Size4KiB&amp;gt;&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;for &lt;&#x2F;span&gt;&lt;span&gt;(idx, entry) in &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.bitmap.iter().enumerate() {
&lt;&#x2F;span&gt;&lt;span&gt;            &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;*entry != &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;MAX &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; frame = PhysFrame::containing_address(PhysAddr::new(
&lt;&#x2F;span&gt;&lt;span&gt;                    (idx as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64 &lt;&#x2F;span&gt;&lt;span&gt;* &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;64 &lt;&#x2F;span&gt;&lt;span&gt;+ entry.trailing_ones() as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;) * &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;4096&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;                ));
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;.set_frame(frame);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;                &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;Some(frame);
&lt;&#x2F;span&gt;&lt;span&gt;            }
&lt;&#x2F;span&gt;&lt;span&gt;        }
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;        None
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The advantage of the bitmap approach is its simplicity and memory efficiency.
If I wanted faster allocation without searching through the bitmap every time, I could have a frame stack and allocate in bulk, but this is simple enough for now.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;page-table-management&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#page-table-management&quot; aria-label=&quot;Anchor link for: page-table-management&quot;&gt;Page table management&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Here I use the &lt;code&gt;OffsetPageTable&lt;&#x2F;code&gt; from the &lt;code&gt;x86_64&lt;&#x2F;code&gt; crate to create the actual page table entries to map between a specific page and frame. The &lt;code&gt;OffsetPageTable&lt;&#x2F;code&gt; stores an offset to physical memory, which allows it to access the page table entries through virtual memory.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;kernel-heap&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kernel-heap&quot; aria-label=&quot;Anchor link for: kernel-heap&quot;&gt;Kernel Heap&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This is another place where I used an external crate. I use the &lt;code&gt;linked_list_allocator&lt;&#x2F;code&gt; crate to implement a &lt;a href=&quot;https:&#x2F;&#x2F;doc.rust-lang.org&#x2F;beta&#x2F;std&#x2F;alloc&#x2F;trait.GlobalAlloc.html&quot;&gt;GlobalAlloc&lt;&#x2F;a&gt;. This allows me to use Rust&#x27;s &lt;code&gt;alloc&lt;&#x2F;code&gt; types like &lt;code&gt;Vec&lt;&#x2F;code&gt; and &lt;code&gt;Box&lt;&#x2F;code&gt;. These types do have the disadvantage of having kernel panics when allocation fails.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;virtual-file-system&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#virtual-file-system&quot; aria-label=&quot;Anchor link for: virtual-file-system&quot;&gt;Virtual File System&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;ul&gt;
&lt;li&gt;My VFS is informed by &lt;a href=&quot;http:&#x2F;&#x2F;www.jamesmolloy.co.uk&#x2F;tutorial_html&#x2F;8.-The%20VFS%20and%20the%20initrd.html&quot;&gt;James Molloy&#x27;s Tutorial&lt;&#x2F;a&gt; &lt;a href=&quot;https:&#x2F;&#x2F;archive.is&#x2F;yPyWE&quot;&gt;(archived link)&lt;&#x2F;a&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The virtual file system (VFS) allows for multiple different file systems to be mounted together in a single file system.
My VFS only supports mount points as direct children of the root directory &lt;code&gt;&#x2F;&lt;&#x2F;code&gt;. This is sufficient to have the two mappings I currently use:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;The device FS mounted at &lt;code&gt;&#x2F;dev&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;The ramdisk mounted at &lt;code&gt;&#x2F;bin&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Of particular interest is the VFS inode struct:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub enum &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;FileType &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    File,
&lt;&#x2F;span&gt;&lt;span&gt;    Directory,
&lt;&#x2F;span&gt;&lt;span&gt;    Device, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Just block devices for now, we don&amp;#39;t have a good distinction between buffered&#x2F;unbuffered devices
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub struct &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;Inode &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;dev&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;,   &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; File system that the file belongs to
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; inode number
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;file_type&lt;&#x2F;span&gt;&lt;span&gt;: FileType,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;size&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;,        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; 0 for devices
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;major&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Device driver
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;minor&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Specific device that belongs to driver
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inner&lt;&#x2F;span&gt;&lt;span&gt;: Option&amp;lt;Box&amp;lt;dyn Any + Send + Sync&amp;gt;&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;(dev, inode)&lt;&#x2F;code&gt; tuple uniquely identifies an inode on the file system. The &lt;code&gt;major&lt;&#x2F;code&gt; and &lt;code&gt;minor&lt;&#x2F;code&gt; numbers are used by the device filesystem to identify particular devices, it isn&#x27;t actually necessary with a VFS but it mirrors the way xv6 does it.&lt;&#x2F;p&gt;
&lt;p&gt;These inodes are the VFS&#x27; view of the actual inodes which the drivers retrieve. All of my current filesystems actually store these inodes in memory, but a real persistent file system would retrieve these inodes from the disk. The information stored inside the inode &lt;code&gt;inner&lt;&#x2F;code&gt; field (in persistent filesystems these are usually references to different blocks that form the file) are an implementation detail of the specific driver.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub trait &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;Filesystem&lt;&#x2F;span&gt;&lt;span&gt;: Send + Sync {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;open&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;Inode&amp;gt;) -&amp;gt; Result&amp;lt;(), FilesystemError&amp;gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Usually increments a reference counter or starts caching inodes -- doesn&amp;#39;t lock the file
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;close&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;Inode&amp;gt;) -&amp;gt; Result&amp;lt;(), FilesystemError&amp;gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Decrements the reference counter, or removes from cache
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;read&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;Inode&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;buffer&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;mut&lt;&#x2F;span&gt;&lt;span&gt; [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;],
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, FilesystemError&amp;gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; A locking operation
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;write&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;        &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;Inode&amp;gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;        &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;buffer&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u8&lt;&#x2F;span&gt;&lt;span&gt;],
&lt;&#x2F;span&gt;&lt;span&gt;    ) -&amp;gt; Result&amp;lt;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;usize&lt;&#x2F;span&gt;&lt;span&gt;, FilesystemError&amp;gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; A locking operation
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;readdir&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;Inode&amp;gt;) -&amp;gt; Result&amp;lt;Vec&amp;lt;DirectoryEntry&amp;gt;, FilesystemError&amp;gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Get directory entries
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;dev&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;inode&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Arc&amp;lt;Inode&amp;gt;, FilesystemError&amp;gt;; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; An inode lookup
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;fn &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;traverse_fs&lt;&#x2F;span&gt;&lt;span&gt;(&amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;self&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;root&lt;&#x2F;span&gt;&lt;span&gt;: Arc&amp;lt;Inode&amp;gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;path&lt;&#x2F;span&gt;&lt;span&gt;: &amp;amp;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;str&lt;&#x2F;span&gt;&lt;span&gt;) -&amp;gt; Result&amp;lt;Arc&amp;lt;Inode&amp;gt;, FilesystemError&amp;gt; {
&lt;&#x2F;span&gt;&lt;span&gt;        ... &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; default implementation omitted for brevity
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;inode()&lt;&#x2F;code&gt; interface allows for retrieving of inodes based on the &lt;code&gt;(dev, inode)&lt;&#x2F;code&gt; pair. It returns an &lt;code&gt;Arc&amp;lt;Inode&amp;gt;&lt;&#x2F;code&gt; which is reference counted. This is useful for persistent file systems where the inodes are not already in memory, because they can be held in a cache and dropped when the reference count goes to 0.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;VirtualFileSystem&lt;&#x2F;code&gt; (which itself implements this trait), allows for mounting of these filesystems. It is initialised as follows:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;VFS&lt;&#x2F;span&gt;&lt;span&gt;.init_once(|| {
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let mut&lt;&#x2F;span&gt;&lt;span&gt; vfs = VirtualFileSystem::new();
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; devfs = Devfs::init(console, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; ramdisk = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;unsafe &lt;&#x2F;span&gt;&lt;span&gt;{ Ramdisk::from_tar(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, &amp;amp;binary) };
&lt;&#x2F;span&gt;&lt;span&gt;    vfs.mount(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;, Box::new(devfs), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;dev&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;).unwrap();
&lt;&#x2F;span&gt;&lt;span&gt;    vfs.mount(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;, Box::new(ramdisk), &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;bin&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;).unwrap();
&lt;&#x2F;span&gt;&lt;span&gt;    vfs
&lt;&#x2F;span&gt;&lt;span&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This initialises the &lt;code&gt;&#x2F;bin&lt;&#x2F;code&gt; filesystem from the uncompressed tar ramdisk loaded into memory by the bootloader, and the &lt;code&gt;&#x2F;dev&lt;&#x2F;code&gt; filesystem which currently only holds &lt;code&gt;&#x2F;dev&#x2F;console&lt;&#x2F;code&gt; (implementing console writes on write, and keyboard input on reads).&lt;&#x2F;p&gt;
&lt;h2 id=&quot;userspace&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#userspace&quot; aria-label=&quot;Anchor link for: userspace&quot;&gt;Userspace&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Relevant reference: &lt;a href=&quot;https:&#x2F;&#x2F;nfil.dev&#x2F;kernel&#x2F;rust&#x2F;coding&#x2F;rust-kernel-to-userspace-and-back&#x2F;&quot;&gt;Rust-OS Kernel - To userspace and back! - nikofil’s blog&lt;&#x2F;a&gt;&lt;&#x2F;p&gt;
&lt;p&gt;I use the fast system call instructions: &lt;code&gt;SYSCALL&lt;&#x2F;code&gt; and &lt;code&gt;SYSRET&lt;&#x2F;code&gt; to switch between Ring 0 (kernel mode) and Ring 3 (user mode).&lt;&#x2F;p&gt;
&lt;p&gt;Some setup is required before these instructions can be used:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Enabling system call extensions&lt;&#x2F;li&gt;
&lt;li&gt;Writing the syscall handler address to the LStar MSR (model‑specific register)&lt;&#x2F;li&gt;
&lt;li&gt;Specifying that interrupts should be disabled on entry to the system call handler (using the SFMask MSR)&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;unsafe &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    Efer::write(Efer::read() | EferFlags::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;SYSTEM_CALL_EXTENSIONS&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;LStar::write(VirtAddr::from_ptr(handle_syscall as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;*const &lt;&#x2F;span&gt;&lt;span&gt;()));
&lt;&#x2F;span&gt;&lt;span&gt;SFMask::write(RFlags::&lt;&#x2F;span&gt;&lt;span style=&quot;color:#7697d6;&quot;&gt;INTERRUPT_FLAG&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Once this is done, I can enter user mode using a single &lt;code&gt;sysret&lt;&#x2F;code&gt; instruction.
This instruction uses the &lt;code&gt;rcx&lt;&#x2F;code&gt; register to store the userspace entry point
and the &lt;code&gt;r11&lt;&#x2F;code&gt; register to hold the new flags upon entering.
A specially crafted thread entry point to enter into user space will be covered
later.&lt;&#x2F;p&gt;
&lt;p&gt;To use this, I need to load a user space executable.
My kernel loads static ELF64 executables, and the loader implementation is almost entirely
based on the format&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;en.wikipedia.org&#x2F;wiki&#x2F;Executable_and_Linkable_Format&quot;&gt;description on Wikipedia&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Loading a static executable involves the following steps:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;Validating the ELF header (checking the magic and executable type matches is expected)&lt;&#x2F;li&gt;
&lt;li&gt;Using the ELF header to locate the program headers.&lt;&#x2F;li&gt;
&lt;li&gt;Use the information in the program headers to load the program segments into memory.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;The program headers contain information to load the actual program segments in the executable.
A program header entry looks like this:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span&gt;#[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;repr&lt;&#x2F;span&gt;&lt;span&gt;(C)]
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;struct &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;ProgramHeaderEntry &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;segment_type&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; contains both p_type and p_flags
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;offset&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;virtual_address&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;unused&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;image_size&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;mem_size&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pub&lt;&#x2F;span&gt;&lt;span&gt;(super) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;align&lt;&#x2F;span&gt;&lt;span&gt;: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u64&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;segment_type&lt;&#x2F;code&gt; field actually contains to 32-bit fields inside:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; segment_type = header.segment_type as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; segment_flags = (header.segment_type &amp;gt;&amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;32&lt;&#x2F;span&gt;&lt;span&gt;) as &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;u32&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;We filter for headers that have &lt;code&gt;segment_type == 1&lt;&#x2F;code&gt; (P_LOAD) as they indicate
segments to actually load into memory.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; executable = (segment_flags &amp;amp; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;) &amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; writable = (segment_flags &amp;amp; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;) &amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; readable = (segment_flags &amp;amp; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;) &amp;gt; &lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The segment flags tell us whether the segment is &lt;code&gt;executable&lt;&#x2F;code&gt;, &lt;code&gt;writable&lt;&#x2F;code&gt; and &lt;code&gt;readable&lt;&#x2F;code&gt;, which can be applied to the actual memory mappings.&lt;&#x2F;p&gt;
&lt;p&gt;To create these mappings, I allocate new physical frames, and then
map these to the pages behind the address range &lt;code&gt;virtual_address..virtual_address+mem_size&lt;&#x2F;code&gt;.
Note that &lt;code&gt;mem_size&lt;&#x2F;code&gt; can differ from &lt;code&gt;image_size&lt;&#x2F;code&gt;, which is the size of the
data to copy. This is used by the exectuable for the &lt;code&gt;.bss&lt;&#x2F;code&gt; section
(storing uninitialised variables), to inform the loader to reserve space
for these variables but not actually copy any data into it.&lt;&#x2F;p&gt;
&lt;p&gt;Next, I allocate the stack. The contents of the stack on program
initialisation is defined by the &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;x86-psABIs&#x2F;x86-64-ABI&#x2F;-&#x2F;jobs&#x2F;9388606854&#x2F;artifacts&#x2F;raw&#x2F;x86-64-ABI&#x2F;abi.pdf&quot;&gt;System V ABI&lt;&#x2F;a&gt; in &lt;code&gt;Section 3.4.1 Initial Stack and Register State&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;This involves allocating:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;An information block (containing argument strings, environment strings)&lt;&#x2F;li&gt;
&lt;li&gt;Auxiliary variables (used by libc for things such as TLS (thread local storage), however
I removed any auxiliary variables after not having any success with setting this up)&lt;&#x2F;li&gt;
&lt;li&gt;Pointers to environment variables (null since I don&#x27;t support environment variables yet)&lt;&#x2F;li&gt;
&lt;li&gt;Finally, &lt;code&gt;argc&lt;&#x2F;code&gt; and the argument pointers:
&lt;ul&gt;
&lt;li&gt;The argument pointers point to the argument strings in the information block, since the SysV ABI expects the argument array to actually be contiguous&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The userspace entry point (&lt;code&gt;_start&lt;&#x2F;code&gt;) actually retrieves these values from the stack, and passes them to &lt;code&gt;main&lt;&#x2F;code&gt; as &lt;code&gt;int argc&lt;&#x2F;code&gt; and &lt;code&gt;char** argv&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The &lt;code&gt;char** argv&lt;&#x2F;code&gt; actually points to the contiguous array of argument pointers on to the stack, and each &lt;code&gt;char*&lt;&#x2F;code&gt; entry points to a null-terminated string in the information block.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;system-calls&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#system-calls&quot; aria-label=&quot;Anchor link for: system-calls&quot;&gt;System calls&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;h3 id=&quot;system-call-handler&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#system-call-handler&quot; aria-label=&quot;Anchor link for: system-call-handler&quot;&gt;System call handler&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;I&#x27;ll now describe the &lt;code&gt;handle_syscall&lt;&#x2F;code&gt; function from earlier.
This uses Rust&#x27;s &lt;a href=&quot;https:&#x2F;&#x2F;blog.rust-lang.org&#x2F;2025&#x2F;07&#x2F;03&#x2F;stabilizing-naked-functions&#x2F;&quot;&gt;naked functions&lt;&#x2F;a&gt; feature
to create a bare assembly handler which doesn&#x27;t include the normal function prologue and epilogue
generated by the compiler.&lt;&#x2F;p&gt;
&lt;p&gt;What&#x27;s important to note is that the &lt;code&gt;syscall&lt;&#x2F;code&gt; instruction doesn&#x27;t change the current stack or save
any registers from the calling function. Unlike interrupt handlers, the CPU doesn&#x27;t do any of this
automatically. Instead, the stack switching has to happen manually.&lt;&#x2F;p&gt;
&lt;p&gt;To understand the desired semantics of a system call, the &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.com&#x2F;x86-psABIs&#x2F;x86-64-ABI&#x2F;-&#x2F;jobs&#x2F;9388606854&#x2F;artifacts&#x2F;raw&#x2F;x86-64-ABI&#x2F;abi.pdf&quot;&gt;System V ABI&lt;&#x2F;a&gt; can be consulted.
In Section A.2.1, the system call interface for Linux is listed as an informative appendix.&lt;&#x2F;p&gt;
&lt;p&gt;My system call interface is very similar except that my syscalls are limited to 4 arguments instead of 6.&lt;&#x2F;p&gt;
&lt;p&gt;At this point, it’s important to understand the semantics of a &lt;code&gt;sysv64&lt;&#x2F;code&gt; function call. These are the callee-preserved
registers for the System V ABI:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rflags&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rbx&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r12&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r13&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r14&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r15&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rbp&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rsp&lt;&#x2F;code&gt;
What this means is that upon the function return, the contents of these registers should be the same as they
were when called. Any other registers can be freely modified, and if the caller needs to keep the values,
they should be saved before calling the function.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;My system call interface follows these semantics for preserving of registers. This differs from the Linux
kernel, which preserves all registers except &lt;code&gt;rcx&lt;&#x2F;code&gt;, &lt;code&gt;r11&lt;&#x2F;code&gt; and &lt;code&gt;rax&lt;&#x2F;code&gt; (at least according to this non-normative section of the ABI).&lt;&#x2F;p&gt;
&lt;p&gt;The main part of the system call handler is as follows (split up and as excerpts):&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;The syscall handler saves &lt;code&gt;rip&lt;&#x2F;code&gt; into &lt;code&gt;rcx&lt;&#x2F;code&gt;, and &lt;code&gt;rflags&lt;&#x2F;code&gt; into &lt;code&gt;r11&lt;&#x2F;code&gt;. These
values are used by &lt;code&gt;sysret&lt;&#x2F;code&gt; to return to userspace, so they are saved to the
stack. I also save &lt;code&gt;rbp&lt;&#x2F;code&gt; and &lt;code&gt;rbx&lt;&#x2F;code&gt;, which will be used soon.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rcx
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r11
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbp&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; will store old sp
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; will store new sp
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;To switch stacks, I need to get the current thread&#x27;s kernel stack. Currently, this involves a separate function call to some Rust code
which gets it from a &lt;code&gt;PerCPU&lt;&#x2F;code&gt; struct (which is itself a wrapper around an &lt;code&gt;UnsafeCell&lt;&#x2F;code&gt; because I don&#x27;t have multiple processor support yet).
I wrote this code before I knew about &lt;code&gt;FSGSBASE&lt;&#x2F;code&gt; which allows code to set the base of the &lt;code&gt;fs&lt;&#x2F;code&gt; and &lt;code&gt;gs&lt;&#x2F;code&gt; segments. Since these are per-processor,
they can be used to store information such as the current kernel stack to be accessed without a function call. But because I do use a function call, I need to backup the syscall arguments, since they are in caller-saved registers. After the function call, I use &lt;code&gt;rbx&lt;&#x2F;code&gt; to store the new
stack, allowing for the syscall params to be restored.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rax&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; syscall number
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdi&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rsi&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg2
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r10&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg3
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;call &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;get_kernel_stack&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; Return value is now in rax
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbx&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rax&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; RBX = new sp
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; Restore syscall params
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r10
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdx
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rsi
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdi
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rax
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;3&quot;&gt;
&lt;li&gt;Switch kernel stacks.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbp&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rsp&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; backup userspace stack
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rsp&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; switch to new stack
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;4&quot;&gt;
&lt;li&gt;Call an inner function to handle the system call in Rust. To do so, reshuffle the arguments to follow the sysv64 calling convention.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rax&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; syscall number
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdi&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg0
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rsi&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg1
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdx&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg2
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r10&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; arg3
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;; Pop to follow normal sysv64 calling convention
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r8
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rcx
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdx
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rsi
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rdi
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;call &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;handle_syscall_inner
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ol start=&quot;5&quot;&gt;
&lt;li&gt;Finally, return from the system call. The return value is now in &lt;code&gt;rax&lt;&#x2F;code&gt; from the previous function.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;mov &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rsp&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbp&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt; ; Restore userspace stack
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbx
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbp
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r11
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;pop &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rcx
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;sysretq
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;system-call-implementation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#system-call-implementation&quot; aria-label=&quot;Anchor link for: system-call-implementation&quot;&gt;System call implementation&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;From here, most system calls are not particularly complicated. I&#x27;ve implemented
&lt;code&gt;check_addr&lt;&#x2F;code&gt; and &lt;code&gt;check_buffer&lt;&#x2F;code&gt; utilities to ensure that pointers and buffers
passed to system calls actually point to lower‑half memory. However, I don&#x27;t
have any validation past this, so a process can easily crash the kernel
by passing it an invalid pointer.&lt;&#x2F;p&gt;
&lt;p&gt;I&#x27;ve implemented these system calls:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;read&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;write&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;open&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;close&lt;&#x2F;code&gt; (a no-op for now)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;exit&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;arch_prctl&lt;&#x2F;code&gt; (I used this when trying to integrate the musl libc. I&#x27;m using my own pseudo-libc for now, since that was taking too long to integrate.)&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;execve&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;brk&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;fork&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;waitid&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;kernel-multithreading&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#kernel-multithreading&quot; aria-label=&quot;Anchor link for: kernel-multithreading&quot;&gt;Kernel Multithreading&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Kernel multithreading is the prerequisite which allows for preemptive
multitasking of userspace threads. To be able to switch between
threads on a timer interrupt, it is far easier to just swap
out the kernel context in a fairly controlled way compared
to having one kernel thread somehow returning to different
userspace stacks. Almost all kernels use this approach
of having one kernel-stack per userspace thread.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;context-switching&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#context-switching&quot; aria-label=&quot;Anchor link for: context-switching&quot;&gt;Context switching&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Once a thread is in kernel mode, the userspace stack and saved registers live at
the bottom of the kernel stack (placed there by the system call handler to
return to later). This means that to switch between threads, only the kernel
stack needs to be swapped out.&lt;&#x2F;p&gt;
&lt;p&gt;Take another look at the callee-preserved registers:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rflags&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rbx&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r12&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r13&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r14&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;r15&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rbp&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;li&gt;&lt;code&gt;rsp&lt;&#x2F;code&gt;&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;To actually execute &lt;code&gt;ret&lt;&#x2F;code&gt; from a function and continue executing the rest of the
thread, these are the only registers which need to be saved. Note that &lt;code&gt;rip&lt;&#x2F;code&gt;
is not on that stack, since the processor will jump back to the return
address that is on the stack. Thus, a &quot;context switch&quot; involves saving and
swapping out these values to switch between threads.&lt;&#x2F;p&gt;
&lt;p&gt;I found two approaches to this.&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mit-pdos&#x2F;xv6-public&#x2F;blob&#x2F;eeb7b415dbcb12cc362d0783e41c3d1f44066b17&#x2F;swtch.S&quot;&gt;xv6&#x27;s approach&lt;&#x2F;a&gt; pushes all of these values (except &lt;code&gt;esp&lt;&#x2F;code&gt;) to the stack, and then
using the stack pointer as the only thing that needs to be swapped when context switching&lt;&#x2F;li&gt;
&lt;li&gt;&lt;a href=&quot;https:&#x2F;&#x2F;gitlab.redox-os.org&#x2F;redox-os&#x2F;kernel&#x2F;-&#x2F;blob&#x2F;master&#x2F;src&#x2F;context&#x2F;arch&#x2F;x86_64.rs?ref_type=heads&quot;&gt;Redox&#x27;s approach&lt;&#x2F;a&gt; uses a struct
containing all of the callee-saved registers, and then directly swaps from this struct when context switching&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;xv6&#x27;s approach is elegant because it stores context in a single value (&lt;code&gt;esp&lt;&#x2F;code&gt; which is the 32-bit equivalent of &lt;code&gt;rsp&lt;&#x2F;code&gt;).
However, to create a new context, a stack must be constructed manually. I didn&#x27;t like this, so I chose Redox&#x27;s approach
which allows for easy changing of these registers. Being able to change these registers directly also proved useful
for both &lt;code&gt;fork()&lt;&#x2F;code&gt; and &lt;code&gt;exec()&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;For the actual assembly to perform this swapping of contexts, I use &lt;a href=&quot;https:&#x2F;&#x2F;gitlab.redox-os.org&#x2F;redox-os&#x2F;kernel&#x2F;-&#x2F;blob&#x2F;master&#x2F;src&#x2F;context&#x2F;arch&#x2F;x86_64.rs?ref_type=heads&quot;&gt;Redox&#x27;s x86-64 &lt;code&gt;switch_to&lt;&#x2F;code&gt; handler&lt;&#x2F;a&gt; (with some modifications).&lt;&#x2F;p&gt;
&lt;h3 id=&quot;scheduler&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#scheduler&quot; aria-label=&quot;Anchor link for: scheduler&quot;&gt;Scheduler&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;To decide what threads to run, I wrote a round-robin scheduler.
My scheduler has a main queue called &lt;code&gt;READY&lt;&#x2F;code&gt;, where &lt;code&gt;Arc&amp;lt;Mutex&amp;lt;Thread&amp;gt;&amp;gt;&lt;&#x2F;code&gt;s get
added to. The scheduler picks the next thread to switch to by popping the front element
on this queue. If there are no such elements, then it switches to an idle thread (which halt loops).&lt;&#x2F;p&gt;
&lt;p&gt;The arrangement of locks in here is also interesting. As well as a &lt;code&gt;Mutex&lt;&#x2F;code&gt; on each of the threads,
there is a global mutex surrounding the &lt;code&gt;READY&lt;&#x2F;code&gt; queue. Since queue operations may happen inside
an interrupt handler (for example the pre-emptive multitasking caused by the timer interrupt),
interrupts must be disabled when handling this queue to avoid deadlock. After the context switch operation,
I must manually release the lock on the previous thread in &lt;code&gt;switch_finish_hook&lt;&#x2F;code&gt; (Rust code called by context
switch). This code also sets the page-table mappings for the new thread, and sets the IST such that interrupts
will be handled by the current kernel thread if they occur in user-mode.&lt;&#x2F;p&gt;
&lt;p&gt;One thing to note is that my &lt;code&gt;yield_execution&lt;&#x2F;code&gt; does not return the current thread to the queue. This is useful
for cases where the thread has to wait for something, where the thread itself can be pushed to a different queue,
and returned to the &lt;code&gt;READY&lt;&#x2F;code&gt; queue when the operation is done. This is used for both keyboard input and &lt;code&gt;waitid&lt;&#x2F;code&gt;.
For preemptive yields on a ready thread, a wrapper &lt;code&gt;yield_and_continue&lt;&#x2F;code&gt; can be used to requeue the current thread to
&lt;code&gt;READY&lt;&#x2F;code&gt;, and then yield.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;execve&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#execve&quot; aria-label=&quot;Anchor link for: execve&quot;&gt;&lt;code&gt;execve()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;For the most part, &lt;code&gt;execve&lt;&#x2F;code&gt; is a system call wrapper around the behaviour I already built for ELF64 loading.
Since &lt;code&gt;execve&lt;&#x2F;code&gt; overrides previous mappings, I added code to clear the entire lower half of page-table mappings
before creating any new ones.&lt;&#x2F;p&gt;
&lt;p&gt;Additionally, to reset the thread to begin at the desired entry point, I use a
neat hack. The &lt;code&gt;execve&lt;&#x2F;code&gt; code modifies the context struct itself (which is just a
struct of callee-saved registers), to store the userspace entry point and
initial stack pointer. Then, I set the kernel stack (which is itself allocated
on the kernel heap), to have a single element (a pointer to the
&lt;code&gt;enter_userspace&lt;&#x2F;code&gt; trampoline). Then, when the context switch calls &lt;code&gt;ret&lt;&#x2F;code&gt;, it
will return to the trampoline as if it was called from there. Once in the trampoline,
the user space stack and entry point can be moved into the appropriate registers for
&lt;code&gt;sysret&lt;&#x2F;code&gt;, now returning from the system call in a completely different process.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;fork&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#fork&quot; aria-label=&quot;Anchor link for: fork&quot;&gt;&lt;code&gt;fork()&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;This system call was quite challenging to implement. It would have been easier
just to spawn a fresh process (like some modern kernels), but I wanted to
understand how it works.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;attempt-1-cloning-the-kernel-stack&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#attempt-1-cloning-the-kernel-stack&quot; aria-label=&quot;Anchor link for: attempt-1-cloning-the-kernel-stack&quot;&gt;Attempt 1: Cloning the kernel stack&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Initially, I thought I&#x27;d implement this by cloning a thread struct, but changing
the parent process to point to a forked process.&lt;&#x2F;p&gt;
&lt;p&gt;The general steps for this would be:&lt;&#x2F;p&gt;
&lt;pre style=&quot;background-color:#151515;color:#e8e8d3;&quot;&gt;&lt;code&gt;&lt;span&gt;parent_pid = get_pid()
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;yield_execution()
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;if get_pid() == parent_pid() {
&lt;&#x2F;span&gt;&lt;span&gt;  &#x2F;&#x2F; Fork hasn&amp;#39;t happened yet
&lt;&#x2F;span&gt;&lt;span&gt;  child_pid = fork_process()
&lt;&#x2F;span&gt;&lt;span&gt;  child_thread = clone_current_thread() &#x2F;&#x2F; this would clone from the last yield point, so child execution would start from there
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  set_thread(child_pid, child_thread)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  enqueue(child_thread)
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  return child_pid
&lt;&#x2F;span&gt;&lt;span&gt;} else {
&lt;&#x2F;span&gt;&lt;span&gt;  return 0
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;I started implementing this, as it would avoid having to write more assembly to create a custom return for &lt;code&gt;fork()&lt;&#x2F;code&gt;.
However, I soon realised that I would have to clone the kernel stack to do to this. This is problematic as I have
the kernel stack allocated on the heap (which is itself problematic for different reasons), which is shared between
all kernel threads.&lt;&#x2F;p&gt;
&lt;p&gt;Cloning the kernel stack seemed much more complicated then just writing the
assembly returns, so I decided to go for that approach.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;attempt-2&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#attempt-2&quot; aria-label=&quot;Anchor link for: attempt-2&quot;&gt;Attempt 2&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;First, we fork the page table. This involves recursively (4 levels) cloning any pages that
were user‑accessible and present. Any kernel mappings could be safely shared.&lt;&#x2F;p&gt;
&lt;p&gt;Then, I assign the frame of the level 4 page table to this process, so that it
can be loaded into the &lt;code&gt;cr3&lt;&#x2F;code&gt; register by &lt;code&gt;switch_finish_hook&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Now that the new process has been created, the parent thread can easily return
the child PID. However, the child process needs to somehow return to the same
place, but returning 0. Kernels like xv6 do this by assigning to the trapframe
(registers saved by the CPU when entering the interrupt handler).&lt;&#x2F;p&gt;
&lt;p&gt;However, in x86-64, syscalls don&#x27;t have a trapframe! I couldn&#x27;t find
any information on implementing &lt;code&gt;fork()&lt;&#x2F;code&gt; with the &lt;code&gt;sysret&lt;&#x2F;code&gt; instructions x86-64.
To solve this, the information needed to return to userspace can be pushed on
the stack manually. The base of the kernel stack is stored in the &lt;code&gt;Thread&lt;&#x2F;code&gt; struct,
and since the kernel stack is empty at the start of a syscall, this can be easily
located.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;asm&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-asm &quot;&gt;&lt;code class=&quot;language-asm&quot; data-lang=&quot;asm&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbx
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r12
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r13
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r14
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;r15
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;push &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;rbp
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;call &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;handle_syscall_inner
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Note the omission of &lt;code&gt;rsp&lt;&#x2F;code&gt; here, since the kernel stack is changing anyway. The purpose of preserving
these registers is that some important information (eg: location of old stack and some callee-preserved registers from user space)
are stored there.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;rs&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-rs &quot;&gt;&lt;code class=&quot;language-rs&quot; data-lang=&quot;rs&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;let&lt;&#x2F;span&gt;&lt;span&gt; trapframe = current_thread.kstack.last_chunk::&amp;lt;6&amp;gt;().unwrap();
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; What we pushed last will have the lowest address (first in our slice)
&lt;&#x2F;span&gt;&lt;span&gt;thread.context.rbp = trapframe[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;0&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;thread.context.r15 = trapframe[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;1&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;thread.context.r14 = trapframe[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;2&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;thread.context.r13 = trapframe[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;3&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;thread.context.r12 = trapframe[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;4&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;span&gt;thread.context.rbx = trapframe[&lt;&#x2F;span&gt;&lt;span style=&quot;color:#cf6a4c;&quot;&gt;5&lt;&#x2F;span&gt;&lt;span&gt;];
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;These 6 registers can be transferred into the new thread using the &lt;code&gt;Context&lt;&#x2F;code&gt; struct.
Then, the fork trampoline simply clears the &lt;code&gt;rax&lt;&#x2F;code&gt; (return value) register and jumps to the normal
syscall return sequence.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to save users from their own network connection</title>
        <published>2023-12-20T00:00:00+00:00</published>
        <updated>2023-12-20T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hamzah.syedahmed.net/posts/save-users-from-network/"/>
        <id>https://hamzah.syedahmed.net/posts/save-users-from-network/</id>
        
        <content type="html" xml:base="https://hamzah.syedahmed.net/posts/save-users-from-network/">&lt;h2 id=&quot;the-dilemma&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-dilemma&quot; aria-label=&quot;Anchor link for: the-dilemma&quot;&gt;The dilemma&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Consider you have an app. It could be a web app, a native mobile app, or a
desktop app. If you have a web app, we are going to assume the app is already
client-side rendered and does not need to load from the server every time it is
opened, as it is cached in a service worker. I talk about this kind of web app
and its tradeoffs more in this &lt;a href=&quot;&#x2F;posts&#x2F;my-approach-to-web-apps&quot;&gt;article&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;Setting that aside, whenever you have some data to store or some computation to
do you have a critical decision:&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Server-side or client-side&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;This decision is not just for web apps, but also for mobile apps and desktop
apps, as they also might need to use cloud services. In this article, I justify
a particular rule of thumb to use for making these decisions, the exceptions to
this rule, and some general development tips which fit well with this rule.&lt;&#x2F;p&gt;
&lt;blockquote&gt;
&lt;p&gt;Do things on the client-side if you can, and on the server-side if you must&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;h2 id=&quot;the-network-connection&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-network-connection&quot; aria-label=&quot;Anchor link for: the-network-connection&quot;&gt;The network connection&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;When you fetch data over the network, you are passing over a gigantic void
of unpredictability, which &lt;strong&gt;you cannot control&lt;&#x2F;strong&gt;. Your users&#x27; devices are no
longer tethered to a relatively stable wired connection. Many users will be
using WiFi or cellular connections, and sometimes these connections can grow
slow, congested or drop out completely. Users no longer have the patience to
wait for slow network connections; we all know the
&lt;a href=&quot;https:&#x2F;&#x2F;www.thinkwithgoogle.com&#x2F;consumer-insights&#x2F;consumer-trends&#x2F;mobile-site-load-time-statistics&#x2F;&quot;&gt;three-second statistic&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
&lt;p&gt;The solution is to do everything that can be done on the client-side on the client. Here are some examples:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Usage&lt;&#x2F;strong&gt;&lt;&#x2F;th&gt;&lt;th&gt;&lt;strong&gt;Client&#x2F;Server&lt;&#x2F;strong&gt;&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Settings for a single device&lt;&#x2F;td&gt;&lt;td&gt;Client, only needed on that device&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Social Media post&lt;&#x2F;td&gt;&lt;td&gt;Server, needs to be stored centrally so many users can access it&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;QR code scan&lt;&#x2F;td&gt;&lt;td&gt;Client, most devices are powerful enough to run QR code scanning code without needing cloud support&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Access control&lt;&#x2F;td&gt;&lt;td&gt;Server, the client side is not a trusted environment and therefore cannot perform access control in a trustworthy way&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;Running an LLM&lt;&#x2F;td&gt;&lt;td&gt;...&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;h3 id=&quot;when-a-network-connection-is-faster-than-doing-the-computation-on-the-client&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#when-a-network-connection-is-faster-than-doing-the-computation-on-the-client&quot; aria-label=&quot;Anchor link for: when-a-network-connection-is-faster-than-doing-the-computation-on-the-client&quot;&gt;When a network connection is faster than doing the computation on the client&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Consider the last example in the table. With our rule, we would run it on the
client, as technically it &lt;em&gt;can&lt;&#x2F;em&gt; be run on the client, but on most devices it
would be far faster to run it on a server and wait for the dreaded network
connection. If, for a significant proportion of your users, it is faster to do a
computation on the server than on the client, then its best to do it on the
server if possible. Another approach if this proportion is not your entire user
population, is to start the operation on the client and server in parallel, and
when one finishes abort the other.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-to-make-the-network-more-bearable&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#how-to-make-the-network-more-bearable&quot; aria-label=&quot;Anchor link for: how-to-make-the-network-more-bearable&quot;&gt;How to make the network more bearable&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Sometimes you have to fetch across the network, like accessing a piece of shared
data or something that needs access control. When this happens, we can still
make some optimisations to make it feel more user-friendly. Here are some things
you can do to improve your user experience for fetching server-side data:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;Optimistic updates
&lt;ul&gt;
&lt;li&gt;After making a change to server side state, update it on the client side
assuming it succeeded and if it fails then revert it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Persistent caching
&lt;ul&gt;
&lt;li&gt;Cache server-side data between page loads, so data is shown immediately
(state data is better than no data)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Periodic sync
&lt;ul&gt;
&lt;li&gt;When offline, queue up changes to be made to the server-side and sync this
in the background when back online&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Validation
&lt;ul&gt;
&lt;li&gt;Validate data to avoid undefined behaviour when invalid data is given,
either because of an actual bug or a proxy (like on school or corporate
networks)&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Graceful error handling
&lt;ul&gt;
&lt;li&gt;Keep showing stale data even when an error occurs so the user can work with
the stale data until the issues stop&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;li&gt;Exponential backoff on retries
&lt;ul&gt;
&lt;li&gt;If an error is temporary it might resolve itself after some period of time,
so keep trying a certain number of times. Use exponential backoff to avoid
putting unnecessary load on the server.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;This is not easy to implement yourself. You may want to use a data-fetching library, sometimes called an async state manager. This library will be responsible for syncing server-side state with a client-side &quot;store&quot;, which your app can deal with, so you don&#x27;t need to worry about the network as much. I personally use &lt;a href=&quot;https:&#x2F;&#x2F;tanstack.com&#x2F;query&#x2F;latest&quot;&gt;TanStack Query&lt;&#x2F;a&gt; for web apps, and I&#x27;ve heard good things about &lt;a href=&quot;https:&#x2F;&#x2F;swr.vercel.app&#x2F;&quot;&gt;SWR&lt;&#x2F;a&gt;. I don&#x27;t currently have any recommendations for native. Also for things like periodic sync and persistence you may need to do some extra configuration. For validation of network requests on the Web, I use &lt;a href=&quot;https:&#x2F;&#x2F;zod.dev&#x2F;&quot;&gt;zod&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>How to use OAuth PKCE in client-side JS</title>
        <published>2023-05-22T00:00:00+00:00</published>
        <updated>2023-05-22T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hamzah.syedahmed.net/posts/oauth-pkce-client/"/>
        <id>https://hamzah.syedahmed.net/posts/oauth-pkce-client/</id>
        
        <content type="html" xml:base="https://hamzah.syedahmed.net/posts/oauth-pkce-client/">&lt;p&gt;You need to use some API that requires authentication. It doesn&#x27;t have an SDK. 2 years ago, I was in this position, I started building &lt;a href=&quot;https:&#x2F;&#x2F;www.timetabl.app&quot;&gt;Timetabl&lt;&#x2F;a&gt;. The API I was using uses &lt;a href=&quot;https:&#x2F;&#x2F;oauth.net&#x2F;&quot;&gt;OAuth&lt;&#x2F;a&gt;, a protocol for authorising third-party applications, to access a user&#x27;s data. I started digging through articles about OAuth. Looking at the available libraries for client side OAuth, I didn&#x27;t seem to have many options. Most JS client libraries were either not actively maintained, didn&#x27;t support PKCE (Proof Key for Code Exchange), didn&#x27;t run in the browser, or were too complicated for me to understand.&lt;&#x2F;p&gt;
&lt;p&gt;If you don&#x27;t understand much about OAuth, or just want a refresher, check out &lt;a href=&quot;https:&#x2F;&#x2F;aaronparecki.com&#x2F;oauth-2-simplified&#x2F;&quot;&gt;this article&lt;&#x2F;a&gt; for an explainer.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-not-to-do&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#what-not-to-do&quot; aria-label=&quot;Anchor link for: what-not-to-do&quot;&gt;What not to do&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Stubborn, I dug into the RFCs for several weeks, and tried to wrap my head around the complexities of OAuth. In around a month I got a basic prototype implementation working. As my application grew, I started to realise that ~90% of my bugs came from that OAuth implementation. As the complexity increased it became almost impossible to maintain. Eventually, I decided to start looking for OAuth libraries again.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;a-library-that-makes-oauth-easy&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#a-library-that-makes-oauth-easy&quot; aria-label=&quot;Anchor link for: a-library-that-makes-oauth-easy&quot;&gt;A library that makes OAuth easy&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;After some research, I found &lt;a href=&quot;https:&#x2F;&#x2F;github.com&#x2F;badgateway&#x2F;oauth2-client&quot;&gt;badgateway&#x2F;oauth2-client&lt;&#x2F;a&gt;. It is an excellent library that is actively maintained. I&#x27;m surprised it dosen&#x27;t have a bigger community, so give it a ⭐ and contribute if you can. This library has unit tests, and is fairly well-documented.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;registering-your-application&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#registering-your-application&quot; aria-label=&quot;Anchor link for: registering-your-application&quot;&gt;Registering your application&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Go to the dashboard of your OAuth provider, and create a new application. You will see a page like this:&lt;&#x2F;p&gt;
&lt;img src=&quot;&#x2F;assets&#x2F;images&#x2F;oauthsetup.png&quot; alt=&quot;OAuth setup dashboard&quot; &#x2F;&gt;
&lt;p&gt;You can ignore the &quot;Client Secret&quot; field, as we will be using &lt;a href=&quot;https:&#x2F;&#x2F;oauth.net&#x2F;2&#x2F;pkce&#x2F;&quot;&gt;PKCE&lt;&#x2F;a&gt; to authenticate. Note down the &quot;Client ID&quot; and &quot;Redirect URI&quot; fields as we will need them for later.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;setting-up-the-library&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#setting-up-the-library&quot; aria-label=&quot;Anchor link for: setting-up-the-library&quot;&gt;Setting up the library&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Install the library with npm:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;bash&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-bash &quot;&gt;&lt;code class=&quot;language-bash&quot; data-lang=&quot;bash&quot;&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;npm&lt;&#x2F;span&gt;&lt;span&gt; i @badgateway&#x2F;oauth2-client
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Next, setup the client class:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;import &lt;&#x2F;span&gt;&lt;span&gt;{ &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;OAuth2Client&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;generateCodeVerifier &lt;&#x2F;span&gt;&lt;span&gt;} &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;from &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;@badgateway&#x2F;oauth2-client&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Replace the following values with your own
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;client &lt;&#x2F;span&gt;&lt;span&gt;= new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;OAuth2Client&lt;&#x2F;span&gt;&lt;span&gt;({
&lt;&#x2F;span&gt;&lt;span&gt;  server: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;https:&#x2F;&#x2F;my-auth-server&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  clientId: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;...&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  tokenEndpoint: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;&#x2F;api&#x2F;token&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  authorizationEndpoint: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;&#x2F;api&#x2F;authorize&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The values for &lt;code&gt;server&lt;&#x2F;code&gt;, &lt;code&gt;tokenEndpoint&lt;&#x2F;code&gt;, and &lt;code&gt;authorizationEndpoint&lt;&#x2F;code&gt; will be in your OAuth provider&#x27;s documentation. The &lt;code&gt;clientId&lt;&#x2F;code&gt; is the &quot;Client ID&quot; you noted down earlier.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;logging-in&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#logging-in&quot; aria-label=&quot;Anchor link for: logging-in&quot;&gt;Logging in&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Add a login button to your page, and add the following code to the click handler:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;codeVerifier &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;generateCodeVerifier&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; You should really dynamically generate a random string, but this is fine for a demo
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;state &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;some-string&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;;
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;document.location = &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;client&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;authorizationCode&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;getAuthorizeUri&lt;&#x2F;span&gt;&lt;span&gt;({
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; The redirect uri you noted down earlier
&lt;&#x2F;span&gt;&lt;span&gt;  redirectUri: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;https:&#x2F;&#x2F;my-app.example&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;codeVerifier&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; see your OAuth provider&amp;#39;s documentation for available scopes
&lt;&#x2F;span&gt;&lt;span&gt;  scope: [&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;scope1&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;scope2&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;],
&lt;&#x2F;span&gt;&lt;span&gt;});
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Save the code verifier and state to the localStorage
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.setItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;codeVerifier&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;codeVerifier&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.setItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This code redirects the user to the OAuth provider&#x27;s login page. After the user logs in, they will be redirected back to your application. This code will extract the &lt;code&gt;code&lt;&#x2F;code&gt; and &lt;code&gt;state&lt;&#x2F;code&gt; from the URL, and exchange the &lt;code&gt;code&lt;&#x2F;code&gt; for an access token.&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Get query string
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;query &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;Object&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;fromEntries&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;  new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;URLSearchParams&lt;&#x2F;span&gt;&lt;span&gt;(window.location.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;search&lt;&#x2F;span&gt;&lt;span&gt;).entries()
&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Error check
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;query&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;error&lt;&#x2F;span&gt;&lt;span&gt;) {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Handle error
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; ...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Logout
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.setItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;loggedIn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, false);
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; If the server returned an authorization code, attempt to exchange it for an access token
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;if &lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;query&lt;&#x2F;span&gt;&lt;span&gt;.code) {
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;codeVerifier &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.getItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;codeVerifier&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;state &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.getItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;oauth2Token &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;client&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;authorizationCode&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;getTokenFromCodeRedirect&lt;&#x2F;span&gt;&lt;span&gt;(
&lt;&#x2F;span&gt;&lt;span&gt;    document.location &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;as &lt;&#x2F;span&gt;&lt;span&gt;any,
&lt;&#x2F;span&gt;&lt;span&gt;    {
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;**
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;       * The redirect URI is not actually used for any redirects, but MUST be the
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;       * same as what you passed earlier to &amp;quot;authorizationCode&amp;quot;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;       *&#x2F;
&lt;&#x2F;span&gt;&lt;span&gt;      redirectUri: &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;config&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;redirect_uri&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;state&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;      &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;codeVerifier&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;    }
&lt;&#x2F;span&gt;&lt;span&gt;  );
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Save the token object to localStorage
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.setItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;token&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, JSON.stringify(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;oauth2Token&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;}
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Clear query string
&lt;&#x2F;span&gt;&lt;span&gt;window.history.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;replaceState&lt;&#x2F;span&gt;&lt;span&gt;({}, null, &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;location&lt;&#x2F;span&gt;&lt;span&gt;.pathname);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;logging-out&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#logging-out&quot; aria-label=&quot;Anchor link for: logging-out&quot;&gt;Logging out&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;To log out, simply remove the token from localStorage and set &lt;code&gt;loggedIn&lt;&#x2F;code&gt; to &lt;code&gt;false&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.removeItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;token&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.setItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;loggedIn&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, false);
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h3 id=&quot;making-requests&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#making-requests&quot; aria-label=&quot;Anchor link for: making-requests&quot;&gt;Making requests&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;To do this we need to setup the &lt;code&gt;FetchWrapper&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;fetchWrapper &lt;&#x2F;span&gt;&lt;span&gt;= new &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;OAuth2Fetch&lt;&#x2F;span&gt;&lt;span&gt;({
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;client&lt;&#x2F;span&gt;&lt;span&gt;,
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;getNewToken&lt;&#x2F;span&gt;&lt;span&gt;: () &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; This method will only be called when refreshing the tokens fails
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; You should log the user in again here
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; `return null` if you want to fail this step
&lt;&#x2F;span&gt;&lt;span&gt;  },
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;storeToken&lt;&#x2F;span&gt;&lt;span&gt;: (&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;token&lt;&#x2F;span&gt;&lt;span&gt;) &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; This method will be called when the token is refreshed
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; You should save the token to localStorage here
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.setItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;token&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;, JSON.stringify(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;token&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;  },
&lt;&#x2F;span&gt;&lt;span&gt;  },
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span&gt;  &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;getStoredToken&lt;&#x2F;span&gt;&lt;span&gt;: () &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;=&amp;gt; &lt;&#x2F;span&gt;&lt;span&gt;{
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; This method will be called to get the token from storage
&lt;&#x2F;span&gt;&lt;span&gt;    &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;return &lt;&#x2F;span&gt;&lt;span&gt;JSON.parse(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;localStorage&lt;&#x2F;span&gt;&lt;span&gt;.getItem(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;token&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;));
&lt;&#x2F;span&gt;&lt;span&gt;  },
&lt;&#x2F;span&gt;&lt;span&gt;});
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;To send a request, simply call &lt;code&gt;fetchWrapper.fetch&lt;&#x2F;code&gt; which behaves similarly to plain old &lt;code&gt;fetch&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre data-lang=&quot;js&quot; style=&quot;background-color:#151515;color:#e8e8d3;&quot; class=&quot;language-js &quot;&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;response &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;await &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;fetchWrapper&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;fetch&lt;&#x2F;span&gt;&lt;span&gt;(&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#99ad6a;&quot;&gt;https:&#x2F;&#x2F;my-api.example&#x2F;&lt;&#x2F;span&gt;&lt;span style=&quot;color:#556633;&quot;&gt;&amp;quot;&lt;&#x2F;span&gt;&lt;span&gt;);
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#888888;&quot;&gt;&#x2F;&#x2F; Obviously do some error handling too...
&lt;&#x2F;span&gt;&lt;span&gt;
&lt;&#x2F;span&gt;&lt;span style=&quot;color:#8fbfdc;&quot;&gt;const &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;data &lt;&#x2F;span&gt;&lt;span&gt;= &lt;&#x2F;span&gt;&lt;span style=&quot;color:#ffb964;&quot;&gt;response&lt;&#x2F;span&gt;&lt;span&gt;.&lt;&#x2F;span&gt;&lt;span style=&quot;color:#fad07a;&quot;&gt;json&lt;&#x2F;span&gt;&lt;span&gt;();
&lt;&#x2F;span&gt;&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;This &lt;code&gt;FetchWrapper&lt;&#x2F;code&gt; will automatically refresh the token when it expires, and retry the request. It will also handle sending the access tokens.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;is-it-secure-to-store-the-token-in-localstorage&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#is-it-secure-to-store-the-token-in-localstorage&quot; aria-label=&quot;Anchor link for: is-it-secure-to-store-the-token-in-localstorage&quot;&gt;Is it secure to store the token in localStorage?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The main issue with localStorage is that it can be accessed by JS. In modern applications, you will often have thousands of dependencies, and if any of them are compromised then you could end up with an XSS attack. You can partially mitigate this risk with a strong Content Security Policy. The JS supply chain is quite resilient to compromised packages, with them being taken down quickly, and the community being notified. It is also very hard to pull off such a targeted attack, through a compromised package.&lt;&#x2F;p&gt;
&lt;p&gt;However, let&#x27;s assume that your app is already compromised through XSS. Even if the token was not stored in localStorage, there would be some other mechanism to make API requests (possibly through a proxy server which stores tokens as HTTP-only cookies), and the attacker could still use that by sending requests from the context of your app.&lt;&#x2F;p&gt;
&lt;p&gt;In our case, the attacker will be able to access the token from &lt;code&gt;localStorage&lt;&#x2F;code&gt;, and possibly use it even when the user does not have the app open. However, there is a measure against this called &lt;strong&gt;refresh token rotation&lt;&#x2F;strong&gt;.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;refresh-token-rotation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#refresh-token-rotation&quot; aria-label=&quot;Anchor link for: refresh-token-rotation&quot;&gt;Refresh Token Rotation&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Refresh token rotation ensures that only one party is using the tokens at one time. This prevents attackers from stealing the token and using it outside the application. It goes hand in hand with short-lived access tokens.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;short-lived-access-tokens&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#short-lived-access-tokens&quot; aria-label=&quot;Anchor link for: short-lived-access-tokens&quot;&gt;Short-lived access tokens&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;Access tokens should be short-lived, and expire after a short period of time. This could be anywhere from 5 minutes to 1 hour. This means that even if the attacker steals the token, they can only use it for a short period of time, before they have to use the refresh token to refresh it.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;the-security-advantage-of-refresh-token-rotation&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#the-security-advantage-of-refresh-token-rotation&quot; aria-label=&quot;Anchor link for: the-security-advantage-of-refresh-token-rotation&quot;&gt;The security advantage of refresh token rotation&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;When the client requests a new access token, it also receives a new refresh token. If a malcious client uses the same refresh token to retrieve a new access token, the server will detect that the refresh token has been used twice, and will revoke all refresh and access tokens. This means that the attacker cannot use the refresh token outside the application, as it will be revoked as soon as the user opens the app.&lt;&#x2F;p&gt;
&lt;p&gt;Read more about refresh token rotation &lt;a href=&quot;https:&#x2F;&#x2F;auth0.com&#x2F;docs&#x2F;secure&#x2F;tokens&#x2F;refresh-tokens&#x2F;refresh-token-rotation&quot;&gt;here&lt;&#x2F;a&gt;.&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>My approach to web app development</title>
        <published>2023-04-11T00:00:00+00:00</published>
        <updated>2023-04-11T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://hamzah.syedahmed.net/posts/my-approach-to-web-apps/"/>
        <id>https://hamzah.syedahmed.net/posts/my-approach-to-web-apps/</id>
        
        <content type="html" xml:base="https://hamzah.syedahmed.net/posts/my-approach-to-web-apps/">&lt;p&gt;This blog post outlines the way I build web applications. As the title suggests, this is &lt;strong&gt;&lt;em&gt;my&lt;&#x2F;em&gt;&lt;&#x2F;strong&gt; approach, and is what works for me. This is a heavily opinionated blog post, but I hope that at least some concepts suggested here will be helpful for others.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;web-apps-vs-websites&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#web-apps-vs-websites&quot; aria-label=&quot;Anchor link for: web-apps-vs-websites&quot;&gt;Web apps vs. websites&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The first thing to clarify is that web &lt;strong&gt;apps&lt;&#x2F;strong&gt; should be built differently from web&lt;strong&gt;sites&lt;&#x2F;strong&gt;. Web &lt;strong&gt;apps&lt;&#x2F;strong&gt; are focused on functionality, while web &lt;strong&gt;sites&lt;&#x2F;strong&gt; are focused on content. Web &lt;strong&gt;apps&lt;&#x2F;strong&gt; may have a small amount of content, and web &lt;strong&gt;sites&lt;&#x2F;strong&gt; may have a small amount of functionality, but the main focus is different. Additionally, web &lt;strong&gt;apps&lt;&#x2F;strong&gt; are designed to be used more than once, while for web &lt;strong&gt;sites&lt;&#x2F;strong&gt; they may or may not be used regularly. This distinction is important to consider in order to optimise the user experience.&lt;&#x2F;p&gt;
&lt;p&gt;In reality, there really is a spectrum between these two extremes. We will be focusing on the web &lt;strong&gt;app&lt;&#x2F;strong&gt; extreme of this spectrum.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img
  src=&quot;&#x2F;assets&#x2F;images&#x2F;web-apps-sites-spectrum.png&quot;
  alt=&quot;Web app to web site spectrum&quot;
&#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h2 id=&quot;to-server-render-or-not-to-server-render&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#to-server-render-or-not-to-server-render&quot; aria-label=&quot;Anchor link for: to-server-render-or-not-to-server-render&quot;&gt;To server-render or not to server-render?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;A lot of the development happening with frameworks right now is about server rendering. By server rendering I&#x27;m referring to any type of rendering that happens remotely (not on the client). This definition includes &lt;strong&gt;SSR&lt;&#x2F;strong&gt; (server-side rendering), &lt;strong&gt;SSG&lt;&#x2F;strong&gt; (static site generation), and &lt;strong&gt;ISR&lt;&#x2F;strong&gt; (incremental static regeneration). On the other hand, &lt;strong&gt;CSR&lt;&#x2F;strong&gt; (client-side rendering) is rendering that happens on the client, which is the &#x27;default&#x27; for most frameworks (React, Vue, Angular).&lt;&#x2F;p&gt;
&lt;p&gt;There are three combinations of server-rendering and client-rendering (excluding when there are none):&lt;&#x2F;p&gt;
&lt;p&gt;&lt;img
  src=&quot;&#x2F;assets&#x2F;images&#x2F;server-side-spectrum.png&quot;
  alt=&quot;server side spectrum&quot;
&#x2F;&gt;&lt;&#x2F;p&gt;
&lt;h3 id=&quot;server-rendered-no-client-rendering&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#server-rendered-no-client-rendering&quot; aria-label=&quot;Anchor link for: server-rendered-no-client-rendering&quot;&gt;Server-rendered (no client-rendering)&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This approach is reminiscent of frameworks such as PHP&#x2F;Django. The server renders the HTML and sends it to the client. The client then receives the HTML and displays it. The client does not render anything. Application is entirely stored on the server, through cookies or sessions. Data will not update without a refresh. Mutations are performed through forms which submit POST requests to the server, and cause the page to refresh.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;progressive-enhancement&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#progressive-enhancement&quot; aria-label=&quot;Anchor link for: progressive-enhancement&quot;&gt;Progressive Enhancement&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This is the approach taken by Next.js, Remix, etc. The server renders the HTML and sends it to the client. The client then receives the HTML and displays it. This is good for performance and SEO, as data is displayed without needing JS to load. The client then performs a process called &#x27;hydration&#x27;, which means that it takes the HTML and turns it into a fully interactive application. The application state is stored on the client. Data will update without a refresh (with proper data fetching libraries). Mutations can be performed without refreshing the page.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;client-rendered-no-server-rendering&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#client-rendered-no-server-rendering&quot; aria-label=&quot;Anchor link for: client-rendered-no-server-rendering&quot;&gt;Client-rendered (no server-rendering)&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;The server sends the client a blank HTML page, which references some scripts which render the page. The application state is stored on the client. Data will update without a refresh (with proper data fetching libraries). Mutations can be performed without refreshing the page. Often, the app uses client side routing, which means that navigation between pages happen without a refresh. These apps are often large, as the entire app is downloaded to the client, rather than a single page.&lt;&#x2F;p&gt;
&lt;p&gt;After looking at these three, Progressive Enhancement seems like the most obvious choice. In progressive enhancement, data is fetched on the server, and then hydrated on the client. This means that the user gets the best of both worlds: the performance and SEO benefits of server-rendering, and the interactivity of client-rendering.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;pwas&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#pwas&quot; aria-label=&quot;Anchor link for: pwas&quot;&gt;PWAs&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;Progressive Web Apps (PWAs) are applications built using web technologies to deliver experiences that rival those of native apps. A key capability of PWAs is that they use service workers. Service workers are a type of web worker that runs in a separate thread, and among their capabilities is the ability to cache and intercept network requests.&lt;&#x2F;p&gt;
&lt;p&gt;Web applications should behave like native apps. Native apps don&#x27;t download their entire codebase from a server &lt;em&gt;every single time&lt;&#x2F;em&gt; they are opened. Native apps don&#x27;t need to be connected to the internet to work. If web apps are to be viable alternatives to native apps, they need to have these capabilities.&lt;&#x2F;p&gt;
&lt;p&gt;The web applications I build rely extensively on service workers. On first load, the web app is downloaded from the server and cached. On subsequent loads, the web app is loaded exclusively from the cache. When there is an update, the new code is downloaded in the background. When the user refreshes the page, the new code is loaded from the cache.&lt;&#x2F;p&gt;
&lt;p&gt;For this to work, there needs to be some kind of client-side rendering, as the app is largely decoupled from the server. This automatically rules out the PHP-style approach, with no rendering on the client.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;progressive-enhancement-or-csr&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#progressive-enhancement-or-csr&quot; aria-label=&quot;Anchor link for: progressive-enhancement-or-csr&quot;&gt;Progressive Enhancement or CSR?&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The question now is whether to use progressive enhancement or client-side rendering. For the reasons outlined above, progessive enhancement will often provide the best user experience. However, for PWAs, progressive enhancement has diminishing returns. Consider that for devices supporting PWAs, the client only loads the app once, and then it is cached. This means that the benefits of progressive enhancement are only gained on first load. And since apps are used numerous times, these benefits are not as important, as for the vast majority of times, progressive enhancement has no benefit. &lt;a href=&quot;https:&#x2F;&#x2F;caniuse.com&#x2F;serviceworkers&quot;&gt;caniuse.com&lt;&#x2F;a&gt; estimates that &lt;strong&gt;96.4%&lt;&#x2F;strong&gt; of users globally support service workers. As nice as it would be to support the remaining 3.6%, for most developers, the cost of supporting these users is not worth it.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;architectural-divergence&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#architectural-divergence&quot; aria-label=&quot;Anchor link for: architectural-divergence&quot;&gt;Architectural Divergence&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;As outlined in &lt;a href=&quot;https:&#x2F;&#x2F;remix.run&#x2F;blog&#x2F;remix-vs-next#architectural-divergence&quot;&gt;Remix&#x27;s&lt;&#x2F;a&gt; article, architectural divergence leads to extra complexity, which leads to poor DX (developer experience). The architectural divergence Remix is talking about is about the divergence between SSG and CSR. Remix&#x27;s solution is to fetch data on the server through SSR &lt;strong&gt;only&lt;&#x2F;strong&gt;. This is a great solution for websites, but as outlined above it doesn&#x27;t work for PWAs. The architectural divergence in progressive enhancement is the initial fetching on the server, and the partial updates on the client. This leads to needing to write logic and data fetching code that runs both on the server and client, leading to an increase in complexity caused by architectural divergence.&lt;&#x2F;p&gt;
&lt;p&gt;As a result, I opt to go for CSR-only for PWAs. This removes the additional complexity of data fetching on the server, which leads to a better DX. Progressive enhancement may be an ideal solution, but unless you have the time and resources to support the 3.6% of users who don&#x27;t support service workers, it is not worth it.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;making-spas-work-well&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#making-spas-work-well&quot; aria-label=&quot;Anchor link for: making-spas-work-well&quot;&gt;Making SPAs work well&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;There are a lot of issues with SPAs. Here are the two most common issues:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SEO&lt;&#x2F;strong&gt; - Search engines can&#x27;t crawl SPAs.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;&#x2F;strong&gt; - SPAs are slow to load, and slow to run.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h3 id=&quot;seo&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#seo&quot; aria-label=&quot;Anchor link for: seo&quot;&gt;SEO&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;Many people point to SEO as an issue for SPAs. However, we are talking about using SPAs for apps, not websites. Since apps are about functionality, not content, there is not much for the search engine to crawl in your app anyway. As for discoverability, you should build a website landing page to promote your app, using SSR or SSG. This landing page should be discoverable by search engines, and should link to your app.&lt;&#x2F;p&gt;
&lt;h3 id=&quot;performance&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#performance&quot; aria-label=&quot;Anchor link for: performance&quot;&gt;Performance&lt;&#x2F;a&gt;&lt;&#x2F;h3&gt;
&lt;p&gt;This isssue has two sub-issues: initial load time, and runtime performance.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;initial-load-time&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#initial-load-time&quot; aria-label=&quot;Anchor link for: initial-load-time&quot;&gt;Initial load time&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;SPAs tend to be quite large, as they contain the entire app and all of its pages. This can easily lead to bundle sizes in excess of several megabytes. However, since we are building PWAs, this download only needs to happen once, as it is then cached. Additionally, through code-splitting, we can ensure that on first launch, only the code needed to render the current page is downloaded. This makes initial load time much better. And even if after this, load times are still bad, it only needs to happen once, so the impacts are minimal. After this initial load, service workers should start caching the rest of the app in the background.&lt;&#x2F;p&gt;
&lt;h4 id=&quot;runtime-performance&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#runtime-performance&quot; aria-label=&quot;Anchor link for: runtime-performance&quot;&gt;Runtime performance&lt;&#x2F;a&gt;&lt;&#x2F;h4&gt;
&lt;p&gt;Since the app is using CSR, all of the rendering logic happens on the client. This moves the bottleneck from the network to the CPU. This is usually good, since for most devices it is faster to do things locally than to wait for the network. However, for extremely slow devices, this can be an issue. There is no easy answer to this problem, but generally performance issues are caused by poorly written code, and can be solved by refactoring. Ultimately, you should tailor your apps to work well for most of your users. You should collect performance metrics such as Core Web Vitals, and use them to make informed decisions about what to optimise. If most of your users are on devices that cannot support an SPA approach, than you should consider another approach.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;data-fetching&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#data-fetching&quot; aria-label=&quot;Anchor link for: data-fetching&quot;&gt;Data fetching&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;One of the key principles I use when building apps is to have some data available as soon as possible, and try to keep that data up to date. Having data available as soon as possible, can be accomplished by having a cache, so that the data can loaded from the cache quickly. Keeping this data up to date could be accomplished by refreshing the data on a certain interval, and doing it whenever the user refocuses on a page. This can be quite hard to implement, so libraries such as &lt;a href=&quot;https:&#x2F;&#x2F;swr.vercel.app&#x2F;&quot;&gt;SWR&lt;&#x2F;a&gt; and &lt;a href=&quot;https:&#x2F;&#x2F;tanstack.com&#x2F;query&#x2F;latest&quot;&gt;TanStack Query&lt;&#x2F;a&gt; exist to make this easier.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#conclusion&quot; aria-label=&quot;Anchor link for: conclusion&quot;&gt;Conclusion&lt;&#x2F;a&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;In this article, I outlined the pros and cons of the three main approaches to building web apps: SSR, CSR, and progressive enhancement. I then outlined the pros and cons of using progressive enhancement for PWAs. I concluded that for PWAs, CSR is the best approach, as it removes the complexity of data fetching on the server, and leads to a better DX. I also outlined some of the issues with SPAs, and how to solve them. Finally, I outlined how to keep data up to date. Keep in mind that this is just how I build apps, and you should do what leads to the best user experience for your users.&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
