Using Kaitai WebIDE with user process implemented in JS

Just a quick reminder what to do, if you are trying to write a kaitai struct and want to use a “user process” to modify the bytestream before parsing:

  • write your kaitai struct
  • implement you user process in your preferred language
  • implement the same functionality in Javascript (JS)

I ran through this process with the ELFIN decoder and implemented the following struct:

meta:
  id: elfin
  endian: be

seq:
  - id: ax25_header
    type: ax25_hdr
    doc-ref: 'https://www.tapr.org/pub_ax25.html'
    size: 16
  - id: ax25_info
    process: elfin_pp
    size-eos: true
    type:
      switch-on: (_io.size > 78)
      cases:
        true: elfin_tlm_data
        false: elfin_hkp_data

types:
  ax25_hdr:
    seq:
      - id: dest_callsign
        type: dest_callsign
      - id: dest_ssid
        type: u1
      - id: src_callsign
        type: src_callsign
      - id: src_ssid
        type: u1
      - id: ctl
        type: u1
      - id: pid
        type: u1
  dest_callsign:
    seq:
      - id: dest_callsign
        process: ror(1)
        size: 6
  src_callsign:
    seq:
      - id: src_callsign
        process: ror(1)
        size: 6

  elfin_tlm_data:
    doc-ref: 'https://elfin.igpp.ucla.edu/s/Beacon-Format_v2.xlsx'
    seq:
      - id: elfin_frame_start
        type: u1
        doc: |
          0x94 marks the framestart
          Any data byte following after this framestart must not contain:
          0x27, 0x5e or 0x93, as 0x93 is the framestart and 0x5e is the
          frameend! If a value reaches 0x93 or 0x5e, this byte is escaped by
          0x27. This also throws an exception on values containing 0x27 itself.
          An additional escape character (0x27) is added to escape the escape
          character.
          Substitutions:
          b'\x2727' -> b'\x27'
          b'\x275e' -> b'\x5e'
          b'\x2793' -> b'\x93'
          With some values containing 0x27 the maximum size of an ax.25 UI
          frame is exceeded (254 bytes).
          The 'preprocessing' to remove the escapoe sequences is done by an 
          external process called 'elfin_pp' and is implemented in a separate
          file.

      - id: elfin_beacon_setting
        type: u1
      - id: elfin_status_1_safe_mode
        type: b1
      - id: elfin_status_1_reserved
        type: b3
      - id: elfin_status_1_early_orbit
        type: b4
        doc: 'Safe mode (first bit), early orbit flags (last 4 bits)'
        
      - id: elfin_status_2_payload_power
        type: b1
      - id: elfin_status_2_9v_boost
        type: b1
      - id: elfin_status_2_bat_htr_allow
        type: b1
      - id: elfin_status_2_htr_force
        type: b1
      - id: elfin_status_2_htr_alert
        type: b1
      - id: elfin_status_2_reserved
        type: b3
        doc: |
          Bits 7 to 3 (in order):
          Payload Power, 9V Boost, battery heater allow, heater force,
          heater alert
          
      - id: elfin_reserved
        type: u1

      - id: elfin_hskp_pwr1_rtcc_year
        type: u1
      - id: elfin_hskp_pwr1_rtcc_month
        type: u1
      - id: elfin_hskp_pwr1_rtcc_day
        type: u1
      - id: elfin_hskp_pwr1_rtcc_hour
        type: u1
      - id: elfin_hskp_pwr1_rtcc_minute
        type: u1
      - id: elfin_hskp_pwr1_rtcc_second
        type: u1
      - id: elfin_hskp_pwr1_adc_data_adc_sa_volt_12
        type: u2
      - id: elfin_hskp_pwr1_adc_data_adc_sa_volt_34
        type: u2
      - id: elfin_hskp_pwr1_adc_data_adc_sa_volt_56
        type: u2
      - id: elfin_hskp_pwr1_adc_data_sa_short_circuit_current
        type: u2
      - id: elfin_hskp_pwr1_adc_data_bat_2_volt
        type: u2
      - id: elfin_hskp_pwr1_adc_data_bat_1_volt
        type: u2
      - id: elfin_hskp_pwr1_adc_data_reg_sa_volt_1
        type: u2
      - id: elfin_hskp_pwr1_adc_data_reg_sa_volt_2
        type: u2
      - id: elfin_hskp_pwr1_adc_data_reg_sa_volt_3
        type: u2
      - id: elfin_hskp_pwr1_adc_data_power_bus_current_1
        type: u2
      - id: elfin_hskp_pwr1_adc_data_power_bus_current_2
        type: u2
      - id: elfin_hskp_pwr1_bat_mon_1_avg_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_temperature_register
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_volt_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_acc_curr_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_avg_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_temperature_register
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_volt_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_acc_curr_reg
        type: s2
      - id: elfin_hskp_pwr1_bv_mon
        type: u2
      - id: elfin_hskp_pwr1_tmps_tmp1
        type: s2
      - id: elfin_hskp_pwr1_tmps_tmp2
        type: s2
      - id: elfin_hskp_pwr1_tmps_tmp3
        type: s2
      - id: elfin_hskp_pwr1_tmps_tmp4
        type: s2
      - id: elfin_hskp_pwr1_accumulated_curr_bat1_rarc
        type: u1
      - id: elfin_hskp_pwr1_accumulated_curr_bat1_rsrc
        type: u1
      - id: elfin_hskp_pwr1_accumulated_curr_bat2_rarc
        type: u1
      - id: elfin_hskp_pwr1_accumulated_curr_bat2_rsrc
        type: u1

      - id: elfin_hskp_pwr2_rtcc_year
        type: u1
      - id: elfin_hskp_pwr2_rtcc_month
        type: u1
      - id: elfin_hskp_pwr2_rtcc_day
        type: u1
      - id: elfin_hskp_pwr2_rtcc_hour
        type: u1
      - id: elfin_hskp_pwr2_rtcc_minute
        type: u1
      - id: elfin_hskp_pwr2_rtcc_second
        type: u1
      - id: elfin_hskp_pwr2_adc_data_adc_sa_volt_12
        type: u2
      - id: elfin_hskp_pwr2_adc_data_adc_sa_volt_34
        type: u2
      - id: elfin_hskp_pwr2_adc_data_adc_sa_volt_56
        type: u2
      - id: elfin_hskp_pwr2_adc_data_sa_short_circuit_current
        type: u2
      - id: elfin_hskp_pwr2_adc_data_bat_2_volt
        type: u2
      - id: elfin_hskp_pwr2_adc_data_bat_1_volt
        type: u2
      - id: elfin_hskp_pwr2_adc_data_reg_sa_volt_1
        type: u2
      - id: elfin_hskp_pwr2_adc_data_reg_sa_volt_2
        type: u2
      - id: elfin_hskp_pwr2_adc_data_reg_sa_volt_3
        type: u2
      - id: elfin_hskp_pwr2_adc_data_power_bus_current_1
        type: u2
      - id: elfin_hskp_pwr2_adc_data_power_bus_current_2
        type: u2
      - id: elfin_hskp_pwr2_bat_mon_1_avg_cur_reg
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_1_temperature_register
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_1_volt_reg
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_1_cur_reg
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_1_acc_curr_reg
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_2_avg_cur_reg
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_2_temperature_register
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_2_volt_reg
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_2_cur_reg
        type: s2
      - id: elfin_hskp_pwr2_bat_mon_2_acc_curr_reg
        type: s2
      - id: elfin_hskp_pwr2_bv_mon
        type: s2
      - id: elfin_hskp_pwr2_tmps_tmp1
        type: s2
      - id: elfin_hskp_pwr2_tmps_tmp2
        type: s2
      - id: elfin_hskp_pwr2_tmps_tmp3
        type: s2
      - id: elfin_hskp_pwr2_tmps_tmp4
        type: s2
      - id: elfin_hskp_pwr2_accumulated_curr_bat1_rarc
        type: u1
      - id: elfin_hskp_pwr2_accumulated_curr_bat1_rsrc
        type: u1
      - id: elfin_hskp_pwr2_accumulated_curr_bat2_rarc
        type: u1
      - id: elfin_hskp_pwr2_accumulated_curr_bat2_rsrc
        type: u1

      - id: elfin_acb_pc_data1_rtcc_year
        type: u1
      - id: elfin_acb_pc_data1_rtcc_month
        type: u1
      - id: elfin_acb_pc_data1_rtcc_day
        type: u1
      - id: elfin_acb_pc_data1_rtcc_hour
        type: u1
      - id: elfin_acb_pc_data1_rtcc_minute
        type: u1
      - id: elfin_acb_pc_data1_rtcc_second
        type: u1
      - id: elfin_acb_pc_data1_acb_mrm_x
        type: s2
      - id: elfin_acb_pc_data1_acb_mrm_y
        type: s2
      - id: elfin_acb_pc_data1_acb_mrm_z
        type: s2
      - id: elfin_acb_pc_data1_ipdu_mrm_x
        type: s2
      - id: elfin_acb_pc_data1_ipdu_mrm_y
        type: s2
      - id: elfin_acb_pc_data1_ipdu_mrm_z
        type: s2
      - id: elfin_acb_pc_data1_tmps_tmp1
        type: u2
      - id: elfin_acb_pc_data1_tmps_tmp2
        type: u2
      - id: elfin_acb_pc_data1_tmps_tmp3
        type: u2
      - id: elfin_acb_pc_data1_tmps_tmp4
        type: u2

      - id: elfin_acb_pc_data2_rtcc_year
        type: u1
      - id: elfin_acb_pc_data2_rtcc_month
        type: u1
      - id: elfin_acb_pc_data2_rtcc_day
        type: u1
      - id: elfin_acb_pc_data2_rtcc_hour
        type: u1
      - id: elfin_acb_pc_data2_rtcc_minute
        type: u1
      - id: elfin_acb_pc_data2_rtcc_second
        type: u1
      - id: elfin_acb_pc_data2_acb_mrm_x
        type: s2
      - id: elfin_acb_pc_data2_acb_mrm_y
        type: s2
      - id: elfin_acb_pc_data2_acb_mrm_z
        type: s2
      - id: elfin_acb_pc_data2_ipdu_mrm_x
        type: s2
      - id: elfin_acb_pc_data2_ipdu_mrm_y
        type: s2
      - id: elfin_acb_pc_data2_ipdu_mrm_z
        type: s2
      - id: elfin_acb_pc_data2_tmps_tmp1
        type: u2
      - id: elfin_acb_pc_data2_tmps_tmp2
        type: u2
      - id: elfin_acb_pc_data2_tmps_tmp3
        type: u2
      - id: elfin_acb_pc_data2_tmps_tmp4
        type: u2

      - id: elfin_acb_sense_adc_data_current
        type: u2le
      - id: elfin_acb_sense_adc_data_voltage
        type: u2le

      - id: elfin_fc_counters_cmds_recv
        type: u1
      - id: elfin_fc_counters_badcmds_recv
        type: u1
      - id: elfin_fc_counters_badpkts_fm_radio
        type: u1
      - id: elfin_fc_counters_fcpkts_fm_radio
        type: u1
      - id: elfin_fc_counters_errors
        type: u1
      - id: elfin_fc_counters_reboots
        type: u1
      - id: elfin_fc_counters_intrnl_wdttmout
        type: u1
      - id: elfin_fc_counters_brwnouts
        type: u1
      - id: elfin_fc_counters_wdpicrst
        type: u1
      - id: elfin_fc_counters_porst
        type: u1
      - id: elfin_fc_counters_uart1_recvpkts
        type: u1
      - id: elfin_fc_counters_uart1_parseerrs
        type: u1
      - id: elfin_fc_counters_sips_ovcur_evts
        type: u1
      - id: elfin_fc_counters_vu1_on
        type: u1
      - id: elfin_fc_counters_vu1_off
        type: u1
      - id: elfin_fc_counters_vu2_on
        type: u1
      - id: elfin_fc_counters_vu2_off
        type: u1

      - id: elfin_radio_tlm_rssi
        type: u1
      - id: elfin_radio_tlm_bytes_rx
        type: u4
      - id: elfin_radio_tlm_bytes_tx
        type: u4

      - id: elfin_radio_cfg_read_radio_palvl
        type: u1

      - id: elfin_errors_error1_day
        type: u1
      - id: elfin_errors_error1_hour
        type: u1
      - id: elfin_errors_error1_minute
        type: u1
      - id: elfin_errors_error1_second
        type: u1
      - id: elfin_errors_error1_error
        type: u1
      - id: elfin_errors_error2_day
        type: u1
      - id: elfin_errors_error2_hour
        type: u1
      - id: elfin_errors_error2_minute
        type: u1
      - id: elfin_errors_error2_second
        type: u1
      - id: elfin_errors_error2_error
        type: u1
      - id: elfin_errors_error3_day
        type: u1
      - id: elfin_errors_error3_hour
        type: u1
      - id: elfin_errors_error3_minute
        type: u1
      - id: elfin_errors_error3_second
        type: u1
      - id: elfin_errors_error3_error
        type: u1
      - id: elfin_errors_error4_day
        type: u1
      - id: elfin_errors_error4_hour
        type: u1
      - id: elfin_errors_error4_minute
        type: u1
      - id: elfin_errors_error4_second
        type: u1
      - id: elfin_errors_error4_error
        type: u1
      - id: elfin_errors_error5_day
        type: u1
      - id: elfin_errors_error5_hour
        type: u1
      - id: elfin_errors_error5_minute
        type: u1
      - id: elfin_errors_error5_second
        type: u1
      - id: elfin_errors_error5_error
        type: u1
      - id: elfin_errors_error6_day
        type: u1
      - id: elfin_errors_error6_hour
        type: u1
      - id: elfin_errors_error6_minute
        type: u1
      - id: elfin_errors_error6_second
        type: u1
      - id: elfin_errors_error6_error
        type: u1
      - id: elfin_errors_error7_day
        type: u1
      - id: elfin_errors_error7_hour
        type: u1
      - id: elfin_errors_error7_minute
        type: u1
      - id: elfin_errors_error7_second
        type: u1
      - id: elfin_errors_error7_error
        type: u1
        
      - id: elfin_fc_salt
        size: 4

      - id: elfin_fc_crc
        type: u1

      - id: elfin_frame_end
        type: u1
        doc: '0x5e marks the end of a frame'

  elfin_hkp_data:
    seq:
      - id: elfin_hskp_pwr1_rtcc_year
        type: u1
      - id: elfin_hskp_pwr1_rtcc_month
        type: u1
      - id: elfin_hskp_pwr1_rtcc_day
        type: u1
      - id: elfin_hskp_pwr1_rtcc_hour
        type: u1
      - id: elfin_hskp_pwr1_rtcc_minute
        type: u1
      - id: elfin_hskp_pwr1_rtcc_second
        type: u1
      - id: elfin_hskp_pwr1_pwr_board_id
        type: u1
      - id: elfin_hskp_pwr1_adc_data_adc_sa_volt_12
        type: u2
      - id: elfin_hskp_pwr1_adc_data_adc_sa_volt_34
        type: u2
      - id: elfin_hskp_pwr1_adc_data_adc_sa_volt_56
        type: u2
      - id: elfin_hskp_pwr1_adc_data_sa_short_circuit_current
        type: u2
      - id: elfin_hskp_pwr1_adc_data_bat_2_volt
        type: u2
      - id: elfin_hskp_pwr1_adc_data_bat_1_volt
        type: u2
      - id: elfin_hskp_pwr1_adc_data_reg_sa_volt_1
        type: u2
      - id: elfin_hskp_pwr1_adc_data_reg_sa_volt_2
        type: u2
      - id: elfin_hskp_pwr1_adc_data_reg_sa_volt_3
        type: u2
      - id: elfin_hskp_pwr1_adc_data_power_bus_current_1
        type: u2
      - id: elfin_hskp_pwr1_adc_data_power_bus_current_2
        type: u2
      - id: elfin_hskp_pwr1_bat_mon_1_avg_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_temperature_register
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_volt_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_1_acc_curr_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_avg_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_temperature_register
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_volt_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_cur_reg
        type: s2
      - id: elfin_hskp_pwr1_bat_mon_2_acc_curr_reg
        type: s2
      - id: elfin_hskp_pwr1_bv_mon
        type: u2
      - id: elfin_hskp_pwr1_tmps_tmp1
        type: s2
      - id: elfin_hskp_pwr1_tmps_tmp2
        type: s2
      - id: elfin_hskp_pwr1_tmps_tmp3
        type: s2
      - id: elfin_hskp_pwr1_tmps_tmp4
        type: s2
      - id: elfin_hskp_pwr1_accumulated_curr_bat1_rsrc
        type: u1
      - id: elfin_hskp_pwr1_accumulated_curr_bat1_rarc
        type: u1
      - id: elfin_fc_status_safe_mode
        type: b1
      - id: elfin_fc_status_reserved
        type: b3
      - id: elfin_fc_status_early_orbit
        type: b4
        doc: 'Safe mode (first bit), early orbit flags (last 4 bits)'
        type: u1

Then I got some help on implementing the “pre-processor” to remove the escape sequences, the flight computer adds if some special values are transmitted in the AX.25 data frame:

import binascii

class ElfinPp:
    def decode(self, bindata):
        i = 0
        binlen = len(bindata)
        out = b''
        while i < binlen:
            ch = ord(bindata[i])
            if ch == 0x27 and i + 1 < binlen:
                next_ch = ord(bindata[i + 1])
                if next_ch == 0x27 or next_ch == 0x5e or next_ch == 0x9e:
                    i += 1
            out += bindata[i]
            i += 1
        return out

…and tried to implement the same functionality to test the kaitai struct in the kaitai WebIDE, which is the “devel” Version which supports this functionality.

class ElfinPp {
    constructor(_key) {
        this.key = _key;
    }
    _read() {
        this._io = {};
        this._debug = {};
    }
    decode(bindata)
    {
        var binlen = bindata.length;
        var ch, next_ch, i = 0;
        var out = [];
        
        while (i < binlen) {
            ch = bindata[i];
            if ((ch == 0x27) && ((i + 1) < binlen))
                next_ch = bindata[i + 1];
                if ((next_ch == 0x27) || (next_ch == 0x5e) || (next_ch == 0x9e))
                    i += 1;
            out = out.concat(bindata[i]);
            ch = 0, next_ch = 0;
            i += 1;
        }
        
        var rbuf = new Uint8Array(out.length);
        i = 0;
        while (i < out.length)
        {
            rbuf[i] = out[i];
            i += 1;
        }
        
        return rbuf;
    }
} this.ElfinPp = ElfinPp;

This might not be the most elegant implementation in JS…

Now you have to “load” this code into the IDE following the instructions here.

In short:

  • open kaitai WebIDE devel
  • inside your browser open the java console (CTRL+SHIFT+i)
  • paste your script enclosed in localStorage.setItem("userTypes", `<your_code>`);
  • edit this code in the “JS code (debug)” tab as you need
  • reload the input data file to see it working
4 Likes