[{"content":"Ever since changing jobs and increasing my commute from 20-30 minutes to 60-90 minutes I\u0026rsquo;ve been searching for the best time to leave to minimize my time in traffic.\nI started out with using the Waze integration in Home Assistant to log the drive time according to Waze to and from work throughout the day:\nThis was then logged into InfluxDB and graphed with Grafana. However this only really gave me an idea of when the best time to leave was, not the best route to take. What I really wanted to see was how the traffic patterns changed over time because it often seemed that when I left there was almost no traffic and by the time I got halfway to work there was a lot of traffic.\nCapturing the Screenshots So from that I got the idea to try to make a timelapse of the traffic along the route. A quick Google search turned up this article from Browserless which was pretty much exactly what I wanted to do: https://www.browserless.io/blog/2022/08/03/how-to-create-time-lapse-traffic/\nHowever that was all Javascript and based on using their browser testing service so I decided to see if there was an easier way to do it locally. Basically what I wanted was to just setup a cron job that ran at a certain interval to save a screenshot of Google Maps with the traffic layer turned on.\nAfter some more research I found that Selenium is available for Python and had the ability to load a page in a windowless environment and then save a screenshot of the webpage.\nI\u0026rsquo;ve posted the Python script I wrote to handle the screenshots here: https://gist.github.com/KalenXI/4dcfb8df5f72f052b241930459e33b0f\nBasically it uses Selenium to load up Chrome in a windowless environment, load the Google Maps page, waits 5 seconds for the page to finish loading, runs some Javascript to hide parts of the Google Maps UI that I don\u0026rsquo;t want in the screenshot and adds a timestamp. Then saves a screenshot of the page to a PNG file.\nI then set this up as a cron job that runs every minute to take a screenshot of the current traffic. Afterwards you\u0026rsquo;ll end up with a folder full of PNGs with the Unix timestamp.\nConverting to Video To make this into a video I wrote a shell script that uses ffmpeg to convert the PNG image sequence into an MP4 video, then deletes all of the source images. And I have that shell script set to run every night at midnight via another cron job.\n1 2 3 4 5 6 7 8 #!/bin/bash cd /home/kevin/traffic date_dir=$(date +%F -d yesterday) ffmpeg -framerate 25 -pattern_type glob -i \u0026#39;*.png\u0026#39; -c:v libx264 -crf 22 -y ${date_dir}.mp4 rm *.png So every night at midnight I end up with a MP4 file showing the traffic from the previous day like this:\nYour browser doesn't support HTML5 video. Here is a link to the video instead. Analysis What\u0026rsquo;s especially interesting is that you can actually see the traffic waves as they propagate backwards from the direction of traffic.\nAnd you can also see the effect of heavy storms as they pass through.\nYour browser doesn't support HTML5 video. Here is a link to the video instead. ","date":"2023-09-17T08:16:00-05:00","image":"/p/traffic-analysis-and-timelapse/cover.png","permalink":"/p/traffic-analysis-and-timelapse/","title":"Traffic Analysis and Timelapse"},{"content":"Background Back in 2020 I wanted to find a way to integrate my car\u0026rsquo;s auto-start capability into my Home Assistant setup so that I could have Home Assistant auto-start my car when the Waze integration determined it was time for me to leave for work in order to arrive at a certain time.\nUnfortunately as far as I\u0026rsquo;m aware Nissan doesn\u0026rsquo;t provide any kind of official API to tie into what they call their telematics system which is what lets you remote control your car through their NissanConnect app.\nSo first I tried just sniffing the packets coming out of the app and doing a MITM attack. Fortunately for their security, but unfortunately for me they use SSL with key pinning so I wasn\u0026rsquo;t able to glean any useful information that way.\nThen it occurred to me that since it was available as a free Android app it\u0026rsquo;d be fairly trivial to decompile the apk file and from there I could just reverse engineer their entire protocol.\nLong story short it turns out the protocol was fairly simple and just required sending my NissanConnect username and password and getting a token in return. So I took what I learned and wrote it into a Python script I could call from Home Assistant to automate starting my car. I also added the ability for me to send my car commands through a Telegram bot.\nVersion 2.0 This worked great for 2 years then suddenly stopped working one day earlier this year. At first I thought maybe Nissan had just finally blocked the app-ID I was using since the auth API required sending cv.nissan.connect.us.android.25 in the CV-APPID header and this was hard-coded into their app. So once again I took the latest version of their Android app, decompiled it and pulled the file with the header constants. To my surprise though the app ID hadn\u0026rsquo;t changed, but the base URL for the API had. So I tried changing the base URL in my code as well to no avail.\nAt this point I began to suspect that the API itself had changed, but I was kind of surprised that they would\u0026rsquo;ve just cut off their own app\u0026rsquo;s access. So I tried downloading the previous version of the app from APK Mirror which was only a week or two older than the current version and sure enough that older version wasn\u0026rsquo;t even able to login.\nRe-Reverse Engineering So I began the process of re-reverse engineering how their API worked from the source code. Unfortunately their app appears to be a bit of a hodge-podge of different code bases and APIs so it can be hard to tell which calls go with which APIs.\nAfter spending a bit of time jumping around through Java calls and definitions I eventually found that they had rearchitected the way that authentication worked on the API to require the app to first retrieve an access token in order to place a call to the endpoint that would allow the user to login. This process involved having the app login itself with some credentials which were hard-coded into the executable.\nTracking Down the Right API This turned out to not be quite as easy as I had hoped. Simply passing the hard-coded client ID and secret along with the request just resulted in an \u0026ldquo;Invalid client\u0026rdquo; error. This sent me down a bit of a rabbit hole because I figured given the error message the problem must be with how I\u0026rsquo;m authenticating the app.\nAfter spending a few hours hitting dead end after dead end I decided to spend a bit more time tracing exactly what the app is calling for each step of the authentication process. This was when I noticed that the API base-URL I was using was not the only one defined in the BuildConfig. There were in fact 10 different base-URLs each going to a different API. This was a lot different from the older version of the app which only used two API base-urls: one for logging in, one for sending remote commands.\nFollowing a bit more digging I finally realized that I was actually using the wrong endpoint for the initial app authentication. I had been using BASE_URL which I suppose is the generic endpoint, what I should have been using was CV_BASE_URL. So I tried again with this new URL.\nNot Quite Authenticated Enough Close but no luck. This was at least a slightly more helpful error message. I knew I needed to provide a username and presumably a password. So I first tried providing the username and password I use to login to the NissanConnect app since this was previously all the old API required. This at least got rid of the missing username error, but just resulted in another error:\n1 2 3 4 { \u0026#34;error_description\u0026#34;: \u0026#34;Authentication Failed\u0026#34;, \u0026#34;error\u0026#34;: \u0026#34;invalid_grant\u0026#34; } So what username and password was it looking for? Luckily this was fairly easy to find, as this was also hard-coded into the app\u0026rsquo;s source code. As it turns out, not only do you need an OAuth-style client ID and client secret, but the app itself also has it\u0026rsquo;s own username and password that it uses to login to the API in order to get its initial auth token. Personally this seems incredibly redundant to me. Requiring what is essentially two usernames and two passwords to login to the API isn\u0026rsquo;t any more secure than just one ID and secret when both of them are hard-coded in plain text into the source code, but I guess it made sense to someone at SiriusXM.\nNewly equipped with my dual username, password, and a slightly lower level of respect for the API developers I tried again and got this:\n1 2 3 4 { \u0026#34;error_description\u0026#34;: \u0026#34;No scope requested and no default scope configured\u0026#34;, \u0026#34;error\u0026#34;: \u0026#34;invalid_scope\u0026#34; } Getting closer. Now there were a couple of different \u0026ldquo;scopes\u0026rdquo; available to choose from in the source code. At first I just tried passing openid as the scope since I figured that\u0026rsquo;s all I needed for logging in. Nope, I actually needed to include a few more keywords I don\u0026rsquo;t understand. Then I tried passing in openid tes cps. What are TES and CPS? No idea. But finally:\nLogin to the Login Now we\u0026rsquo;re getting somewhere. But I still hadn\u0026rsquo;t even logged into my account yet. This was all merely to get access to the login API, something that in the previous API version required no authentication at all to access.\nLuckily the actual login process was fairly similar to the old API. It just required figuring out the correct API base-URL (which for some reason was passed directly into the OpenID function in the session manager rather than being defined as a constant in the BuildConfig file like all of the other base_urls), passing in the auth bearer token from the first authentication step, and providing your actual username and password as a JSON-encoded body of the request.\nSending Some Commands Finally, equipped with the auth bearer token from the first step, and the id_token from the second step I could finally send commands to the car. This involved a third separate API, in this case the RTS API. And is just a matter of making a POST request to the particular endpoint for the command you want to send for instance: https://rts.cv000-telematics.net/telematicsservices/v0/vehicles/remote-horn-and-lights, and including the auth bearer token from the first step, the id_token from the second step, and your car\u0026rsquo;s VIN along with the command you want to execute in JSON in the request body:\n1 2 3 { \u0026#34;command\u0026#34;: \u0026#34;HORN_LIGHT\u0026#34; } To both mine, and probably my neighbors surprise since I did this at around midnight, the HORN_LIGHT command does not simply beep the horn once and blink the lights like when you lock the car from the key fob. It actually sets the car alarm off until you send an unlock command.\nConclusion At some point I\u0026rsquo;ll probably get around to updating my Python script to use the new API at which point you\u0026rsquo;ll be able to see a bit more about how exactly all of this works. But having spent 3 days figuring out the new API I wanted a bit of a break before delving into how to translate all of this into a Python CLI app. I\u0026rsquo;m also a bit concerned that Nissan will start blocking their client secrets and passwords or making it more difficult to obtain than simply including it as plain-text in the decompiled source code. Not like I wanted to have to decompile their app just to figure all of this out though. I\u0026rsquo;d much rather they provide official support for third-party API access, especially given I\u0026rsquo;m paying something like $12/mo for this service. But that\u0026rsquo;s almost certainly wishful thinking.\nUntil next time.\n","date":"2022-11-18T15:00:09-05:00","image":"/p/reverse-engineering-nissanconnect/app-screen.png","permalink":"/p/reverse-engineering-nissanconnect/","title":"Reverse Engineering NissanConnect"},{"content":" Been messing around with this 30 year old 386 PC I found hidden under a shelf at work.\nThe 40MB drive in it was still functional and had MS-DOS 6.20 on it along with some networking apps and the drivers for the network card in it.\nEnded up needing to make a DOS boot disk on a 5.25\u0026quot; floppy to format the CF card I\u0026rsquo;m replacing the HDD with as the 3.5\u0026quot; floppy drive is connected as the B: drive and it will only boot from the A: drive which is the 5.25\u0026quot; drive. Luckily I happen to have a single 1.2MB 5.25\u0026quot; disk in my obsolete media collection and it still worked.\nOnce I got the CF card formatted and working I was able to back up all of the contents on the HDD. Then I wanted to see if I could get it connected to the internet since it had a 10mbps Ethernet adapter. After a bunch of trial and error I finally figured out the right combination of commands to get the network driver loaded and a telnet client working. Was able to connect to some actual BBSes and connect to the terminal for my media server.\n","date":"2022-09-16T01:07:04-05:00","image":"/p/restoring-a-386/cover.jpg","permalink":"/p/restoring-a-386/","title":"Restoring a 386"},{"content":"My video site now has support for logins and video uploads and transcode progress through the web interface.\n","date":"2021-09-25T01:29:00-05:00","image":"/p/video-site-update-2/video%20site%20update%202.png","permalink":"/p/video-site-update-2/","title":"Video Site Update 2"},{"content":"After getting fed up with how long YouTube was taking to process my videos and wanting a way to present a video archive to clients without having to pay hundreds of dollars a year for Vimeo I decided to build my own Vimeo-esque video sharing site for myself.\nThe front end is built with Nuxt.js and the backend is written in Python using FastAPI, MongoDB, and Auth0.\n","date":"2021-09-16T22:32:00-05:00","image":"/p/video-site-update-1/video%20site%20update%201.png","permalink":"/p/video-site-update-1/","title":"Video Site Update 1"},{"content":"Original Reddit Post: https://www.reddit.com/r/blackmirror/comments/adbppu/reverse_engineering_the_bandersnatch_state_engine/\nSo I decided to spend the weekend reverse engineering the state engine that Netflix uses to drive their interactive videos. Unfortunately it got to the point where the boolean branching operations got too complex to do manually. So I figured I\u0026rsquo;d just present what I have so far in case anybody wants to pick up where I left off.\nThe repository with my work so far is here: https://github.com/KalenXI/Bandersnatch\nIt contains a program written in C# that will take the Bandersnatch JSON data and process it in a variety of ways. It\u0026rsquo;s what I used to generate the 3 text files in the root of the repository:\nBandersnatch.json - This is the original JSON metadata pulled from Netflix that defines all of the scenes and the direction each decision will take. Moments.txt - This is a list of all scenes alphabetically including which state variable get set once that scene is entered, the destination scene or scene group for each option, and any self-contained preconditions for entering that scene. States.txt - This is a list of every state variable, which scenes set that variable, and to what. Also includes some notes for what choice each state represents. A lot of them just indicate whether a certain scene has been seen. Preconditions.txt - This is a list of the global branching preconditions for every scene that has them. This is what the branching engine uses to chose which scene to go to when sent to a sequence group. I also started to make two flow charts defining the possible paths. Page 1 is a non-linear representation and page 2 is a very incomplete linear representation: https://www.draw.io/?lightbox=1\u0026amp;highlight=0000ff\u0026amp;edit=_blank\u0026amp;layers=1\u0026amp;nav=1\u0026amp;page=1\u0026amp;title=Bandersnatch.xml#Uhttps%3A%2F%2Fdrive.google.com%2Fuc%3Fid%3D1NKu-ES-ZXVRLN5cr1D1YEKZwZJGaYbo6%26export%3Ddownload\nThe non-linear representation breaks at each scene group and you\u0026rsquo;d have to go through the list of preconditions to determine which path would have been followed given the current state of the state variables. The linear representation was my attempt to actually follow all of the possible choices. You can see I only got partly down a single path before I started running into boolean operators for the precondition that looked like this: (((!(p_cm || p_ty) \u0026amp;\u0026amp; !(p_lsd \u0026amp;\u0026amp; p_2b) \u0026amp;\u0026amp; (!(p_2b) || (p_2b \u0026amp;\u0026amp; p_lsd)) \u0026amp;\u0026amp; p_3aj) || (!(p_cm || p_ty) \u0026amp;\u0026amp; !(p_lsd \u0026amp;\u0026amp; p_2b) \u0026amp;\u0026amp; p_2b \u0026amp;\u0026amp; !(p_lsd) \u0026amp;\u0026amp; p_3ah) || (p_cm || p_ty)) \u0026amp;\u0026amp; !(p_3ab || p_3ac || p_3aj || p_3ah || p_3ak || p_3al || p_3af)). At that point it was taking me 15 minutes per branch just to manually translate the prefix boolean JSON notation used in the metadata to the infix boolean notation that Javascript and C# used just to figure out which branch to take so I stopped.\nIn the flow chart:\nRectangles = Sequences, including title, sequence ID, and any state variable that are set by viewing said sequence. Ovals = Choice options branching from the preceding sequence Cylinders = Sequence groups. These are the major components of how the branching engine works. When a choice leads to a sequence group the engine will go down the options one by one comparing the current settings of the state variables to the preconditions for each sequence listed in the sequence group. Once it finds one that returns true it will follow that path. Hexagons = Precondition groups. Once you get to a sequence group you go down through the hexagons from top to bottom until you find a set of preconditions that matches the current state variable state at which point you should branch to the right and jump to that particular sequence. Hopefully someone else finds this interesting as well. The state engine that Netflix built for their interactive stories is amazingly flexible. As you can see from the flow chart there are some scenes that can branch in 12+ different directions depending on exactly what choices the user made. There\u0026rsquo;s (if I\u0026rsquo;m remembering right) 143 sequences with 2 choices each which gives you a theoretical maximum of 2^(143) possible paths. In practice there are fewer paths because some dead end and some loop back on themselves. There are 2^(59)ish or 576 quadrillion possible state engine states with 62 variables (again not all of which are used in practice).\n","date":"2019-01-06T19:10:58-05:00","image":"/p/bandersnatch-state-engine/header.png","permalink":"/p/bandersnatch-state-engine/","title":"Bandersnatch State Engine"}]