// Various reverse-engineered versions of the allocation algorithms // used by different countries to allocate 24-bit ICAO addresses based // on the aircraft registration. // // These were worked out by looking at the allocation patterns and // working backwards to an algorithm that generates that pattern, // spot-checking aircraft to see if it worked. // YMMV. registration_from_hexid = (function () { // hide the guts in a closure var limited_alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ"; // 24 chars; no I, O var full_alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; // 26 chars // handles 3-letter suffixes assigned with a regular pattern // // start: first hexid of range // s1: major stride (interval between different first letters) // s2: minor stride (interval between different second letters) // prefix: the registration prefix // // optionally: // alphabet: the alphabet to use (defaults full_alphabet) // first: the suffix to use at the start of the range (default: AAA) // last: the last valid suffix in the range (default: ZZZ) var stride_mappings = [ { start: 0x008011, s1: 26*26, s2: 26, prefix: "ZS-" }, { start: 0x390000, s1: 1024, s2: 32, prefix: "F-G" }, { start: 0x398000, s1: 1024, s2: 32, prefix: "F-H" }, { start: 0x3C4421, s1: 1024, s2: 32, prefix: "D-A", first: 'AAA', last: 'OZZ' }, { start: 0x3C0001, s1: 26*26, s2: 26, prefix: "D-A", first: 'PAA', last: 'ZZZ' }, { start: 0x3C8421, s1: 1024, s2: 32, prefix: "D-B", first: 'AAA', last: 'OZZ' }, { start: 0x3C2001, s1: 26*26, s2: 26, prefix: "D-B", first: 'PAA', last: 'ZZZ' }, { start: 0x3CC000, s1: 26*26, s2: 26, prefix: "D-C" }, { start: 0x3D04A8, s1: 26*26, s2: 26, prefix: "D-E" }, { start: 0x3D4950, s1: 26*26, s2: 26, prefix: "D-F" }, { start: 0x3D8DF8, s1: 26*26, s2: 26, prefix: "D-G" }, { start: 0x3DD2A0, s1: 26*26, s2: 26, prefix: "D-H" }, { start: 0x3E1748, s1: 26*26, s2: 26, prefix: "D-I" }, { start: 0x448421, s1: 1024, s2: 32, prefix: "OO-" }, { start: 0x458421, s1: 1024, s2: 32, prefix: "OY-" }, { start: 0x460000, s1: 26*26, s2: 26, prefix: "OH-" }, { start: 0x468421, s1: 1024, s2: 32, prefix: "SX-" }, { start: 0x490421, s1: 1024, s2: 32, prefix: "CS-" }, { start: 0x4A0421, s1: 1024, s2: 32, prefix: "YR-" }, { start: 0x4B8421, s1: 1024, s2: 32, prefix: "TC-" }, { start: 0x740421, s1: 1024, s2: 32, prefix: "JY-" }, { start: 0x760421, s1: 1024, s2: 32, prefix: "AP-" }, { start: 0x768421, s1: 1024, s2: 32, prefix: "9V-" }, { start: 0x778421, s1: 1024, s2: 32, prefix: "YK-" }, { start: 0xC00001, s1: 26*26, s2: 26, prefix: "C-F" }, { start: 0xC044A9, s1: 26*26, s2: 26, prefix: "C-G" }, { start: 0xE01041, s1: 4096, s2: 64, prefix: "LV-" } ]; // numeric registrations // start: start hexid in range // first: first numeric registration // count: number of numeric registrations // template: registration template, trailing characters are replaced with the numeric registration var numeric_mappings = [ { start: 0x140000, first: 0, count: 100000, template: "RA0000" }, { start: 0x0B03E8, first: 1000, count: 1000, template: "CUT0000" } ]; // fill in some derived data for (var i = 0; i < stride_mappings.length; ++i) { var mapping = stride_mappings[i]; if (!mapping.alphabet) { mapping.alphabet = full_alphabet; } if (mapping.first) { var c1 = mapping.alphabet.indexOf(mapping.first.charAt(0)); var c2 = mapping.alphabet.indexOf(mapping.first.charAt(1)); var c3 = mapping.alphabet.indexOf(mapping.first.charAt(2)); mapping.offset = c1 * mapping.s1 + c2 * mapping.s2 + c3; } else { mapping.offset = 0; } if (mapping.last) { var c1 = mapping.alphabet.indexOf(mapping.last.charAt(0)); var c2 = mapping.alphabet.indexOf(mapping.last.charAt(1)); var c3 = mapping.alphabet.indexOf(mapping.last.charAt(2)); mapping.end = mapping.start - mapping.offset + c1 * mapping.s1 + c2 * mapping.s2 + c3 - mapping.offset; } else { mapping.end = mapping.start - mapping.offset + (mapping.alphabet.length - 1) * mapping.s1 + (mapping.alphabet.length - 1) * mapping.s2 + (mapping.alphabet.length - 1); } } for (var i = 0; i < numeric_mappings.length; ++i) { numeric_mappings[i].end = numeric_mappings[i].start + numeric_mappings[i].count - 1; } function lookup(hexid) { var hexid = +("0x" + hexid); reg = n_reg(hexid); if (reg) return reg; reg = ja_reg(hexid); if (reg) return reg; reg = hl_reg(hexid); if (reg) return reg; reg = numeric_reg(hexid); if (reg) return reg; reg = stride_reg(hexid); if (reg) return reg; return null; } function stride_reg(hexid) { // try the mappings in stride_mappings var i; for (i = 0; i < stride_mappings.length; ++i) { var mapping = stride_mappings[i]; if (hexid < mapping.start || hexid > mapping.end) continue; var offset = hexid - mapping.start + mapping.offset; var i1 = Math.floor(offset / mapping.s1); offset = offset % mapping.s1; var i2 = Math.floor(offset / mapping.s2); offset = offset % mapping.s2; var i3 = offset; if (i1 < 0 || i1 >= mapping.alphabet.length || i2 < 0 || i2 >= mapping.alphabet.length || i3 < 0 || i3 >= mapping.alphabet.length) continue; return mapping.prefix + mapping.alphabet.charAt(i1) + mapping.alphabet.charAt(i2) + mapping.alphabet.charAt(i3); } // nothing return null; } function numeric_reg(hexid) { // try the mappings in numeric_mappings var i; for (i = 0; i < numeric_mappings.length; ++i) { var mapping = numeric_mappings[i]; if (hexid < mapping.start || hexid > mapping.end) continue; var reg = (hexid - mapping.start + mapping.first) + ""; return mapping.template.substring(0, mapping.template.length - reg.length) + reg; } } // // US N-numbers // function n_letters(rem) { if (rem == 0) return ""; --rem; return limited_alphabet.charAt(Math.floor(rem / 25)) + n_letter(rem % 25); } function n_letter(rem) { if (rem == 0) return ""; --rem; return limited_alphabet.charAt(rem); } function n_reg(hexid) { var offset = hexid - 0xA00001; if (offset < 0 || offset >= 915399) { return null; } var digit1 = Math.floor(offset / 101711) + 1; var reg = "N" + digit1; offset = offset % 101711; if (offset <= 600) { // Na, NaA .. NaZ, NaAA .. NaZZ return reg + n_letters(offset); } // Na0* .. Na9* offset -= 601; var digit2 = Math.floor(offset / 10111); reg += digit2; offset = offset % 10111; if (offset <= 600) { // Nab, NabA..NabZ, NabAA..NabZZ return reg + n_letters(offset); } // Nab0* .. Nab9* offset -= 601; var digit3 = Math.floor(offset / 951); reg += digit3; offset = offset % 951; if (offset <= 600) { // Nabc, NabcA .. NabcZ, NabcAA .. NabcZZ return reg + n_letters(offset); } // Nabc0* .. Nabc9* offset -= 601; var digit4 = Math.floor(offset / 35); reg += digit4.toFixed(0); offset = offset % 35; if (offset <= 24) { // Nabcd, NabcdA .. NabcdZ return reg + n_letter(offset); } // Nabcd0 .. Nabcd9 offset -= 25; return reg + offset.toFixed(0); } // South Korea function hl_reg(hexid) { if (hexid >= 0x71BA00 && hexid <= 0x71bf99) { return "HL" + (hexid - 0x71BA00 + 0x7200).toString(16); } if (hexid >= 0x71C000 && hexid <= 0x71C099) { return "HL" + (hexid - 0x71C000 + 0x8000).toString(16); } if (hexid >= 0x71C200 && hexid <= 0x71C299) { return "HL" + (hexid - 0x71C200 + 0x8200).toString(16); } return null; } // Japan function ja_reg(hexid) { var offset = hexid - 0x840000; if (offset < 0 || offset >= 229840) return null; var reg = "JA"; var digit1 = Math.floor(offset / 22984); if (digit1 < 0 || digit1 > 9) return null; reg += digit1; offset = offset % 22984; var digit2 = Math.floor(offset / 916); if (digit2 < 0 || digit2 > 9) return null; reg += digit2; offset = offset % 916; if (offset < 340) { // 3rd is a digit, 4th is a digit or letter var digit3 = Math.floor(offset / 34); reg += digit3; offset = offset % 34; if (offset < 10) { // 4th is a digit return reg + offset; } // 4th is a letter offset -= 10; return reg + limited_alphabet.charAt(offset); } // 3rd and 4th are letters offset -= 340; var letter3 = Math.floor(offset / 24); return reg + limited_alphabet.charAt(letter3) + limited_alphabet.charAt(offset % 24); } return lookup; })(); // make nodejs happy: if (module) { module.exports = registration_from_hexid; }