use super::beidou::legendre;
use super::tables::{L1cRow, L1coRow, L1C_D, L1C_O, L1C_P, L2C_STATES, L5_PHASES};
use super::{bipolar, Modulation, SignalCode, SignalError};
use crate::iq::DataModulation;
use crate::sdr::CaCode;
pub const L1_HZ: f64 = 1_575_420_000.0;
pub const L2_HZ: f64 = 1_227_600_000.0;
pub const L5_HZ: f64 = 1_176_450_000.0;
pub const L1C_CHIP_RATE_HZ: f64 = 1_023_000.0;
pub const L1C_CODE_LEN: usize = 10_230;
pub const L1C_WEIL_LEN: usize = 10_223;
pub const L1C_EXPANSION: [u8; 7] = [0, 1, 1, 0, 1, 0, 0];
pub const L1C_OVERLAY_LEN: usize = 1800;
pub const L5_CHIP_RATE_HZ: f64 = 10_230_000.0;
pub const L5_CODE_LEN: usize = 10_230;
pub const L2C_CHIP_RATE_HZ: f64 = 511_500.0;
pub const L2_CM_LEN: usize = 10_230;
pub const L2_CL_LEN: usize = 767_250;
pub const NH10: [u8; 10] = [0, 0, 0, 0, 1, 1, 0, 1, 0, 1];
pub const NH20: [u8; 20] = [0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0];
pub const L2C_POLY_OCTAL: u32 = 0o1112225171;
pub fn l1ca(prn: u16) -> Result<SignalCode, SignalError> {
let code = u8::try_from(prn)
.ok()
.and_then(CaCode::new)
.ok_or(SignalError::UnknownPrn {
signal: "GPS L1 C/A",
prn,
})?;
Ok(SignalCode::new(
format!("GPS L1 C/A PRN {prn}"),
crate::sdr::CA_CHIP_RATE_HZ,
L1_HZ,
bipolar(&code.chips),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Lnav))
}
pub fn l1c_weil(w: u16) -> Vec<u8> {
let l = legendre(L1C_WEIL_LEN);
let w = w as usize % L1C_WEIL_LEN;
(0..L1C_WEIL_LEN)
.map(|t| l[t] ^ l[(t + w) % L1C_WEIL_LEN])
.collect()
}
pub fn l1c_ranging_bits(w: u16, p: u16) -> Vec<u8> {
let p = p as usize;
assert!(
(1..=L1C_WEIL_LEN).contains(&p),
"L1C insertion index {p} outside 1..=10223"
);
let weil = l1c_weil(w);
let mut code = Vec::with_capacity(L1C_CODE_LEN);
code.extend_from_slice(&weil[..p - 1]);
code.extend_from_slice(&L1C_EXPANSION);
code.extend_from_slice(&weil[p - 1..]);
code
}
fn l1c_row(
table: &'static [L1cRow; 63],
signal: &'static str,
prn: u16,
) -> Result<&'static L1cRow, SignalError> {
(prn as usize)
.checked_sub(1)
.and_then(|i| table.get(i))
.ok_or(SignalError::UnknownPrn { signal, prn })
}
pub fn l1c_p_bits(prn: u16) -> Result<Vec<u8>, SignalError> {
let r = l1c_row(&L1C_P, "GPS L1CP", prn)?;
Ok(l1c_ranging_bits(r.w, r.p))
}
pub fn l1c_d_bits(prn: u16) -> Result<Vec<u8>, SignalError> {
let r = l1c_row(&L1C_D, "GPS L1CD", prn)?;
Ok(l1c_ranging_bits(r.w, r.p))
}
pub fn l1co_s1_run(s1_poly: u16, initial: u16, len: usize) -> Vec<u8> {
let mut reg = [0u8; 11];
for (j, s) in reg.iter_mut().enumerate() {
*s = ((initial >> j) & 1) as u8;
}
let taps: Vec<usize> = (1..=11).filter(|&j| (s1_poly >> j) & 1 == 1).collect();
let mut out = Vec::with_capacity(len);
for _ in 0..len {
out.push(reg[10]);
let fb = taps.iter().fold(0u8, |acc, &j| acc ^ reg[j - 1]);
reg.copy_within(0..10, 1);
reg[0] = fb;
}
out
}
pub fn l1c_o_bits(prn: u16) -> Result<Vec<u8>, SignalError> {
let r: &L1coRow = (prn as usize)
.checked_sub(1)
.and_then(|i| L1C_O.get(i))
.ok_or(SignalError::UnknownPrn {
signal: "GPS L1CO",
prn,
})?;
Ok(l1co_s1_run(r.s1_poly, r.initial, L1C_OVERLAY_LEN))
}
pub fn l1c_d(prn: u16) -> Result<SignalCode, SignalError> {
Ok(SignalCode::new(
format!("GPS L1CD PRN {prn}"),
L1C_CHIP_RATE_HZ,
L1_HZ,
bipolar(&l1c_d_bits(prn)?),
vec![],
Modulation::SineBoc { n: 1 },
)
.with_data_modulation(DataModulation::Symbols { symbol_s: 0.01 }))
}
pub fn l1c_p(prn: u16) -> Result<SignalCode, SignalError> {
Ok(SignalCode::new(
format!("GPS L1CP PRN {prn}"),
L1C_CHIP_RATE_HZ,
L1_HZ,
bipolar(&l1c_p_bits(prn)?),
bipolar(&l1c_o_bits(prn)?),
Modulation::Tmboc,
)
.with_data_modulation(DataModulation::Pilot))
}
type Reg13 = [u8; 13];
fn reg13_from_word(word: u16) -> Reg13 {
let mut r = [0u8; 13];
for (k, s) in r.iter_mut().enumerate() {
*s = ((word >> (12 - k)) & 1) as u8;
}
r
}
fn reg13_to_word(r: &Reg13) -> u16 {
r.iter().fold(0u16, |acc, &b| (acc << 1) | u16::from(b))
}
fn shift13(r: &mut Reg13, taps: &[usize]) {
let fb = taps.iter().fold(0u8, |acc, &t| acc ^ r[t - 1]);
r.copy_within(0..12, 1);
r[0] = fb;
}
const XA_TAPS: [usize; 4] = [9, 10, 12, 13];
const XB_TAPS: [usize; 8] = [1, 3, 4, 6, 7, 8, 12, 13];
pub fn l5_xb_state_after(advance: u32) -> u16 {
let mut r = [1u8; 13];
for _ in 0..advance {
shift13(&mut r, &XB_TAPS);
}
reg13_to_word(&r)
}
fn l5_primary_bits(xb_word: u16) -> Vec<u8> {
let mut xa = [1u8; 13];
let mut xb = reg13_from_word(xb_word);
let mut out = Vec::with_capacity(L5_CODE_LEN);
for k in 0..L5_CODE_LEN {
out.push(xa[12] ^ xb[12]);
if k % 8190 == 8189 {
xa = [1u8; 13];
} else {
shift13(&mut xa, &XA_TAPS);
}
shift13(&mut xb, &XB_TAPS);
}
out
}
fn l5_row(signal: &'static str, prn: u16) -> Result<&'static super::tables::L5Phase, SignalError> {
(prn as usize)
.checked_sub(1)
.and_then(|i| L5_PHASES.get(i))
.ok_or(SignalError::UnknownPrn { signal, prn })
}
pub fn l5_i5(prn: u16) -> Result<SignalCode, SignalError> {
let row = l5_row("GPS L5 I5", prn)?;
Ok(SignalCode::new(
format!("GPS L5 I5 PRN {prn}"),
L5_CHIP_RATE_HZ,
L5_HZ,
bipolar(&l5_primary_bits(row.xb_i5)),
bipolar(&NH10),
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Symbols { symbol_s: 0.01 }))
}
pub fn l5_q5(prn: u16) -> Result<SignalCode, SignalError> {
let row = l5_row("GPS L5 Q5", prn)?;
Ok(SignalCode::new(
format!("GPS L5 Q5 PRN {prn}"),
L5_CHIP_RATE_HZ,
L5_HZ,
bipolar(&l5_primary_bits(row.xb_q5)),
bipolar(&NH20),
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Pilot))
}
pub fn l2c_run(state: u32, chips: usize) -> (Vec<u8>, u32) {
let fb = L2C_POLY_OCTAL >> 1;
let mut s = state;
let mut out = Vec::with_capacity(chips);
for _ in 0..chips {
let o = (s & 1) as u8;
out.push(o);
s >>= 1;
if o == 1 {
s ^= fb;
}
}
(out, s)
}
fn l2c_row(
signal: &'static str,
prn: u16,
) -> Result<&'static super::tables::L2cStates, SignalError> {
(prn as usize)
.checked_sub(1)
.and_then(|i| L2C_STATES.get(i))
.ok_or(SignalError::UnknownPrn { signal, prn })
}
pub fn l2c_cm(prn: u16) -> Result<SignalCode, SignalError> {
let row = l2c_row("GPS L2 CM", prn)?;
let (bits, _) = l2c_run(row.cm_initial, L2_CM_LEN);
Ok(SignalCode::new(
format!("GPS L2 CM PRN {prn}"),
L2C_CHIP_RATE_HZ,
L2_HZ,
bipolar(&bits),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Symbols { symbol_s: 0.02 }))
}
pub fn l2c_cl(prn: u16) -> Result<SignalCode, SignalError> {
let row = l2c_row("GPS L2 CL", prn)?;
let (bits, _) = l2c_run(row.cl_initial, L2_CL_LEN);
Ok(SignalCode::new(
format!("GPS L2 CL PRN {prn}"),
L2C_CHIP_RATE_HZ,
L2_HZ,
bipolar(&bits),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::Pilot))
}
pub fn l2c(prn: u16) -> Result<SignalCode, SignalError> {
let row = l2c_row("GPS L2C", prn)?;
let (cm, _) = l2c_run(row.cm_initial, L2_CM_LEN);
let (cl, _) = l2c_run(row.cl_initial, L2_CL_LEN);
let mut bits = Vec::with_capacity(2 * L2_CL_LEN);
for (k, &cl_bit) in cl.iter().enumerate() {
bits.push(cm[k % L2_CM_LEN]);
bits.push(cl_bit);
}
Ok(SignalCode::new(
format!("GPS L2C PRN {prn}"),
2.0 * L2C_CHIP_RATE_HZ,
L2_HZ,
bipolar(&bits),
vec![],
Modulation::Bpsk,
)
.with_data_modulation(DataModulation::NotModelled))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::iq::SpreadingCode;
#[test]
fn xb_state_word_round_trips() {
for w in [0u16, 1, 0x1FFF, 0b0101011100100] {
assert_eq!(reg13_to_word(®13_from_word(w)), w);
}
}
#[test]
fn out_of_range_prns_are_refused() {
assert!(l1ca(0).is_err());
assert!(l1ca(33).is_err());
assert!(l5_i5(0).is_err());
assert!(l5_q5(211).is_err());
assert!(l2c_cm(64).is_err());
}
#[test]
fn l1c_prn_range_is_1_to_63() {
assert!(l1c_p(0).is_err());
assert!(l1c_d(64).is_err());
assert!(l1c_o_bits(64).is_err());
assert!(l1c_p_bits(63).is_ok() && l1c_o_bits(1).is_ok());
}
#[test]
fn l1c_expansion_is_inserted_before_the_pth_weil_chip() {
let weil = l1c_weil(1234);
assert_eq!(weil.len(), L1C_WEIL_LEN);
for p in [1u16, 2, 412, 10_223] {
let c = l1c_ranging_bits(1234, p);
let p = p as usize;
assert_eq!(c.len(), L1C_CODE_LEN);
assert_eq!(&c[..p - 1], &weil[..p - 1]);
assert_eq!(&c[p - 1..p + 6], &L1C_EXPANSION);
assert_eq!(&c[p + 6..], &weil[p - 1..]);
}
}
#[test]
fn l1co_register_emits_its_initial_state_first_and_has_period_2047() {
let init = 0b101_1001_0111u16;
let poly = (1 << 11) | (1 << 9) | 1;
let out = l1co_s1_run(poly, init, 2047 * 2);
let first: u16 = out[..11].iter().fold(0, |a, &b| (a << 1) | u16::from(b));
assert_eq!(first, init);
assert_eq!(&out[..2047], &out[2047..]);
assert!((1..2047).all(|s| out[s..s + 11] != out[..11]));
assert_eq!(out[..2047].iter().filter(|&&b| b == 1).count(), 1024);
}
#[test]
fn l1c_components_lengths_rates_and_modulations() {
let d = l1c_d(5).unwrap();
let p = l1c_p(5).unwrap();
assert_eq!(d.len_chips(), L1C_CODE_LEN);
assert_eq!(p.len_chips(), L1C_CODE_LEN * L1C_OVERLAY_LEN);
assert!((d.period_s() - 0.010).abs() < 1e-12);
assert!((p.period_s() - 18.0).abs() < 1e-9);
assert_eq!(d.modulation(), Modulation::SineBoc { n: 1 });
assert_eq!(p.modulation(), Modulation::Tmboc);
assert_eq!(
d.data_modulation(),
DataModulation::Symbols { symbol_s: 0.01 }
);
assert_eq!(p.data_modulation(), DataModulation::Pilot);
assert_eq!(p.carrier_hz(), L1_HZ);
let o = l1c_o_bits(5).unwrap();
let m = o.iter().position(|&b| b != o[0]).unwrap() as i64;
let k = m * L1C_CODE_LEN as i64 + 17;
assert_eq!(p.chip(k), -p.chip(17));
}
#[test]
fn l1cp_tmboc_symbols_follow_the_33_slot_pattern() {
let p = l1c_p(9).unwrap();
let listed = [
0usize, 4, 6, 29, 33, 37, 39, 62, 10_197, 10_201, 10_203, 10_226,
];
let boc61: Vec<usize> = (0..L1C_CODE_LEN)
.filter(|&t| super::super::tmboc_is_boc61(t))
.collect();
assert_eq!(boc61.len(), 4 * 310);
for t in listed {
assert!(boc61.contains(&t), "t = {t}");
}
let sign_changes = |t: usize| {
let v: Vec<f64> = (0..24)
.map(|s| p.value_at(t as f64 + (s as f64 + 0.5) / 24.0))
.collect();
v.windows(2).filter(|w| w[0] != w[1]).count()
};
for period in [0usize, 1, 1799] {
let base = period * L1C_CODE_LEN;
assert_eq!(sign_changes(base), 11);
assert_eq!(sign_changes(base + 29), 11);
assert_eq!(sign_changes(base + 1), 1);
assert_eq!(sign_changes(base + 32), 1);
assert_eq!(sign_changes(base + 33), 11);
}
for t in [0usize, 1, 4, 5] {
assert_eq!(p.value_at(t as f64 + 0.01), p.chip(t as i64));
}
}
#[test]
fn l1cp_tmboc_power_is_4_33_boc61_and_29_33_boc11_from_the_waveform() {
const SPC: usize = 48;
let p = l1c_p(21).unwrap();
let (mut a6, mut a1, mut e) = (0.0, 0.0, 0.0);
for k in 0..L1C_CODE_LEN {
let c = p.chip(k as i64);
for s in 0..SPC {
let f = (s as f64 + 0.5) / SPC as f64;
let v = p.value_at(k as f64 + f);
a6 += v * c * crate::iq::signals::square_subcarrier(6, f);
a1 += v * c * crate::iq::signals::square_subcarrier(1, f);
e += v * v;
}
}
let n = (L1C_CODE_LEN * SPC) as f64;
assert!((a6 / n - 4.0 / 33.0).abs() < 1e-12, "{}", a6 / n);
assert!((a1 / n - 29.0 / 33.0).abs() < 1e-12, "{}", a1 / n);
assert!((e / n - 1.0).abs() < 1e-12);
}
#[test]
fn non_tmboc_modulations_ignore_the_chip_index() {
for m in [
Modulation::Bpsk,
Modulation::SineBoc { n: 1 },
Modulation::Cboc { plus: true },
] {
for chip in [0usize, 4, 29, 33] {
for f in [0.03, 0.3, 0.7] {
assert_eq!(m.subcarrier_at(chip, f), m.subcarrier(f));
}
}
}
}
#[test]
fn l1ca_wrapper_matches_sdr_code() {
let c = l1ca(7).unwrap();
let sdr = CaCode::new(7).unwrap();
assert_eq!(c.len_chips(), 1023);
for k in 0..1023 {
assert_eq!(c.value_at(k as f64 + 0.5), sdr.bipolar[k]);
}
}
}