specpress
Version:
Export PDF and/or DOCX files from a subset of Markdown, ASN.1 and JSON files
276 lines • 11.2 kB
JavaScript
// BWC_ID.ts — Bandwidth Combination ID parsing
import { MAX_CARRIER_BW_MHZ } from "./ChannelBandwidthPerBand.js";
//////////////////////////////
// Exceptions
/** Thrown when a BWC-ID string cannot be parsed or is invalid. */
export class InvalidBwcIdException extends Error {
constructor(message) { super(message); this.name = "InvalidBwcIdException"; }
}
//////////////////////////////
// BWC value table
/**
* Describes the properties of a single BWC letter (e.g. 'A', 'C', 'D').
*
* Each BWC letter maps to a number of contiguous carriers and an aggregated
* bandwidth range, as defined in 3GPP TS 38.101.
*/
export class BWCValue {
/** Number of contiguous component carriers. */
nrofCarriers;
/** Minimum aggregated bandwidth in MHz (exclusive lower bound). */
minBW;
/** Maximum aggregated bandwidth in MHz (inclusive upper bound). */
maxBW;
/** Fallback group indices for this BWC letter. */
fallbackGroup;
constructor(aNrofCarriers, aMinBW, aMaxBw, aFallbackGroupList = []) {
this.nrofCarriers = aNrofCarriers;
this.minBW = aMinBW;
this.maxBW = aMaxBw;
this.fallbackGroup = aFallbackGroupList;
}
}
/** BWC letter definitions for FR1, as per TS 38.101-1. */
export const bwcValuesFR1 = {
"A": new BWCValue(1, 0, MAX_CARRIER_BW_MHZ, [1, 2, 3]),
"B": new BWCValue(2, 20, 100, [2, 3]),
"C": new BWCValue(2, 100, 2 * MAX_CARRIER_BW_MHZ, [1, 3]),
"D": new BWCValue(3, 200, 3 * MAX_CARRIER_BW_MHZ, [1, 3]),
"E": new BWCValue(4, 300, 4 * MAX_CARRIER_BW_MHZ, [1, 3]),
"G": new BWCValue(3, 100, 150, [2]),
"H": new BWCValue(4, 150, 200, [2]),
"I": new BWCValue(5, 200, 250, [2]),
"J": new BWCValue(6, 250, 300, [2]),
"K": new BWCValue(7, 300, 350, [2]),
"L": new BWCValue(8, 350, 400, [2]),
"M": new BWCValue(3, 50, 200, [3]),
"N": new BWCValue(4, 80, 300, [3]),
"O": new BWCValue(5, 100, 400, [3]),
};
/** BWC letter definitions for FR2, as per TS 38.101-2. */
export const bwcValuesFR2 = {
"A": new BWCValue(1, 0, 400, [1, 2, 3, 4, 5]),
"B": new BWCValue(2, 400, 800, [1]),
"C": new BWCValue(3, 800, 1200, [1]),
"V": new BWCValue(4, 1200, 1600, [1]),
"W": new BWCValue(5, 1600, 2000, [1]),
"D": new BWCValue(2, 200, 400, [2]),
"E": new BWCValue(3, 400, 600, [2]),
"F": new BWCValue(4, 600, 800, [2]),
"R": new BWCValue(5, 800, 1000, [2]),
"S": new BWCValue(6, 1000, 1200, [2]),
"T": new BWCValue(7, 1200, 1400, [2]),
"U": new BWCValue(8, 1400, 1600, [2]),
"G": new BWCValue(2, 100, 200, [3]),
"H": new BWCValue(3, 200, 300, [3]),
"I": new BWCValue(4, 300, 400, [3]),
"J": new BWCValue(5, 400, 500, [3]),
"K": new BWCValue(6, 500, 600, [3]),
"L": new BWCValue(7, 600, 700, [3]),
"M": new BWCValue(8, 700, 800, [3]),
"O": new BWCValue(2, 100, 200, [4]),
"P": new BWCValue(3, 150, 300, [4]),
"Q": new BWCValue(4, 200, 400, [4]),
"R2": new BWCValue(2, 200, 400, [5]),
"R3": new BWCValue(3, 300, 600, [5]),
"R4": new BWCValue(4, 400, 800, [5]),
"R5": new BWCValue(5, 500, 1000, [5]),
"R6": new BWCValue(6, 600, 1200, [5]),
"R7": new BWCValue(7, 700, 1400, [5]),
"R8": new BWCValue(8, 800, 1600, [5]),
"R9": new BWCValue(9, 900, 1800, [5]),
"R10": new BWCValue(10, 1000, 2000, [5]),
"R11": new BWCValue(11, 1100, 2200, [5]),
"R12": new BWCValue(12, 1200, 2400, [5]),
};
/**
* Checks whether a BWC letter is valid for the given frequency range.
*
* @param aBwc — the BWC letter to check (e.g. `"A"`, `"C"`, `"R3"`).
* @param aFrequencyRange — 0 = either FR, 1 = FR1 only, 2 = FR2 only.
* @returns true if the letter is defined in the corresponding BWC table.
*/
export function IsValidBwcCharacter(aBwc, aFrequencyRange = 0) {
if (aFrequencyRange === 0) {
if (aBwc in bwcValuesFR1 || aBwc in bwcValuesFR2)
return true;
}
if (aFrequencyRange === 1) {
if (aBwc in bwcValuesFR1)
return true;
}
if (aFrequencyRange === 2) {
if (aBwc in bwcValuesFR2)
return true;
}
return false;
}
/**
* Compares two BWC value strings with natural ordering:
* alphabetical by letter prefix, then numeric by trailing digits.
* e.g. "R" < "R2" < "R9" < "R10" < "R12" < "S"
*/
export function compareBwcValues(a, b) {
const re = /^([A-Z]+)(\d*)$/;
const ma = a.match(re), mb = b.match(re);
if (ma[1] !== mb[1])
return ma[1] < mb[1] ? -1 : 1;
const na = ma[2] ? parseInt(ma[2], 10) : -1;
const nb = mb[2] ? parseInt(mb[2], 10) : -1;
return na - nb;
}
//////////////////////////////
// BWC_ID class
/**
* Represents a Bandwidth Combination Identifier for a single band within a BC-ID.
*
* A BWC-ID is either a single letter (e.g. `"A"`, `"C"`) for a simple carrier
* configuration, or a parenthesized multi-component expression (e.g. `"(2A-C)"`)
* describing contiguous carrier groups with optional multipliers.
*
* Examples:
* - `"A"` — single non-contiguous carrier
* - `"C"` — two contiguous carriers (FR1: 100–200 MHz aggregate)
* - `"(2A)"` — two non-contiguous single carriers
* - `"(2A-C)"` — two non-contiguous single carriers plus one contiguous pair
*/
export class BWC_ID {
str;
/** Whether this BWC-ID was successfully parsed. */
isValid;
/** The individual contiguous carrier group letters after expansion. */
contGroups;
/**
* Parses a BWC-ID string.
*
* @param aString — the BWC-ID string to parse (e.g. `"A"`, `"(2A-C)"`).
* @throws InvalidBwcIdException if the string cannot be parsed.
*/
constructor(aString) {
this.str = aString;
if (IsValidBwcCharacter(aString)) {
this.isValid = true;
this.contGroups = [aString];
return;
}
const tempGroups = [];
if (aString.startsWith("(") && aString.endsWith(")") && aString.length >= 4) {
let previousBWC = "";
const inner = aString.slice(1, -1);
for (const oneComponent of inner.split("-")) {
let oneMultiplier = "";
let curNrofContBands = 1;
let oneBWC = "";
for (let i = 0; i < oneComponent.length; i++) {
const c = oneComponent[i];
if (c >= "0" && c <= "9") {
oneMultiplier += c;
}
else {
if (oneMultiplier.length > 0) {
curNrofContBands = parseInt(oneMultiplier, 10);
if (curNrofContBands < 1 || curNrofContBands > 32) {
throw new InvalidBwcIdException(`The given string '${aString}' contains an invalid BWC multiplier '${oneMultiplier}'.`);
}
}
oneBWC = oneComponent.slice(i);
if (!IsValidBwcCharacter(oneBWC)) {
throw new InvalidBwcIdException(`The given string '${aString}' contains an invalid BWC character '${oneBWC}'.`);
}
if (previousBWC !== "" && compareBwcValues(previousBWC, oneBWC) >= 0) {
throw new InvalidBwcIdException(`The given string '${aString}' contains BWC components in the wrong order: '${previousBWC}' >= '${oneBWC}'.`);
}
for (let j = 0; j < curNrofContBands; j++) {
tempGroups.push(oneBWC);
}
previousBWC = oneBWC;
break;
}
}
}
if (tempGroups.length > 0) {
this.isValid = true;
this.contGroups = tempGroups;
}
else {
throw new InvalidBwcIdException(`Could not find a valid BWC component in the given string '${aString}'.`);
}
}
else {
throw new InvalidBwcIdException(`The given string '${aString}' is neither a valid single- nor multi-component BWC.`);
}
}
/** Returns the expanded list of contiguous carrier group letters. */
getContGroups() { return this.contGroups; }
/** Returns the number of non-contiguous carrier groups (i.e. the length of contGroups). */
getNrofNonContiguousCarriers() { return this.contGroups.length; }
/**
* Returns the total number of component carriers across all contiguous groups.
*
* Sums the nrofCarriers from the BWCValue for each contiguous group letter.
* E.g., "(2A-C)" with FR1 returns 4 (1+1+2), "C" with FR1 returns 2.
*
* @param aFrequencyRange — 0 = try both FR1 and FR2, 1 = FR1 only, 2 = FR2 only
* @returns total number of physical carriers
*/
getNrofCarriers(aFrequencyRange = 0) {
let total = 0;
for (const groupLetter of this.contGroups) {
let bwcValue = null;
if (aFrequencyRange === 0 || aFrequencyRange === 1) {
bwcValue = bwcValuesFR1[groupLetter];
}
if (!bwcValue && (aFrequencyRange === 0 || aFrequencyRange === 2)) {
bwcValue = bwcValuesFR2[groupLetter];
}
if (bwcValue) {
total += bwcValue.nrofCarriers;
}
else {
// Unknown BWC letter, assume 1 carrier as fallback
total += 1;
}
}
return total;
}
contStr() {
return [...this.contGroups].sort(compareBwcValues).join("");
}
/** Returns the original BWC-ID string. */
valueOf() { return this.str; }
/** Returns the original BWC-ID string. */
toString() { return this.str; }
/** Returns true if this BWC-ID has the same string representation as `other`. */
equals(other) {
const otherStr = other instanceof BWC_ID ? other.str : other;
return this.str === otherStr;
}
/**
* Ordering comparison: fewer non-contiguous carriers first,
* then alphabetical by sorted contiguous group letters.
*/
lessThan(value) {
const other = value instanceof BWC_ID ? value : new BWC_ID(value);
if (this.getNrofNonContiguousCarriers() < other.getNrofNonContiguousCarriers())
return true;
if (this.getNrofNonContiguousCarriers() === other.getNrofNonContiguousCarriers()) {
const cmp = compareBwcValues(this.contStr(), other.contStr());
if (cmp < 0)
return true;
}
return false;
}
/** Returns true if this BWC-ID is strictly greater than `value`. */
greaterThan(value) {
return !this.equals(value) && !this.lessThan(value);
}
/** Returns true if this BWC-ID is less than or equal to `value`. */
lessOrEqual(value) {
return this.equals(value) || this.lessThan(value);
}
/** Returns true if this BWC-ID is greater than or equal to `value`. */
greaterOrEqual(value) {
return !this.lessThan(value);
}
}
//# sourceMappingURL=BWC_ID.js.map