Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate features #736

Merged
merged 45 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f6542d4
check vendor present in index
LDannijs Jan 11, 2024
38d5d23
validate single vendor
LDannijs Jan 11, 2024
170a1a3
add vendorprofileid check
LDannijs Jan 11, 2024
dfe546a
update PULL_REQUEST_TEMPLATE
LDannijs Jan 11, 2024
1514143
remove some vendorprofileID checks
LDannijs Jan 12, 2024
453edd8
clearer error and prettier
LDannijs Jan 12, 2024
1a4ac1a
update all profileVendorID to be unique per vendor
LDannijs Jan 12, 2024
e49eeb4
improve vendorProfileID explanation further
LDannijs Jan 12, 2024
192e31b
validate specific vendor README update
LDannijs Jan 12, 2024
ecd801e
Add pre-commit hook for validation
LDannijs Jan 29, 2024
192f421
run validate and fmt
LDannijs Jan 29, 2024
03356bd
Merge branch 'TheThingsNetwork:master' into validate-features
LDannijs Jan 29, 2024
0d73eef
update nexelec vendorprofileid
LDannijs Jan 29, 2024
5e7d343
install pre-commit hooks with make deps
LDannijs Feb 7, 2024
2ff120a
add feedback
LDannijs Feb 7, 2024
64a7da4
Revert update all profileVendorID to be unique per vendor
LDannijs Feb 7, 2024
bfb1c0c
Revert update nexelec vendorprofileid
LDannijs Feb 7, 2024
3f865d8
update all vendorProfileID
LDannijs Feb 7, 2024
0662d56
Merge remote-tracking branch 'upstream/master' into validate-features
LDannijs Feb 7, 2024
695ca99
update vendor-id to VENDOR_ID
LDannijs Feb 12, 2024
a378aaf
fix vendor_id again
LDannijs Feb 14, 2024
0ae4f5a
remove duplicate index check
LDannijs Feb 28, 2024
78355b9
remove vendorProfileID and add profileIDs
LDannijs Feb 28, 2024
214f025
Merge branch 'master' into validate-features
LDannijs Feb 29, 2024
8a599b1
validate
LDannijs Feb 29, 2024
dd161cf
add all hardwareVersion & update schema
LDannijs Feb 29, 2024
5deead7
delete temp script
LDannijs Feb 29, 2024
c971618
Merge branch 'master' into validate-features
LDannijs Mar 1, 2024
153537c
fixes and update
LDannijs Mar 1, 2024
3eace04
Merge branch 'TheThingsNetwork:master' into validate-features
LDannijs Mar 1, 2024
f76ce76
update ks-technologies
LDannijs Mar 1, 2024
c585ab8
update README & example
LDannijs Mar 1, 2024
e5e9c20
Merge branch 'master' into validate-features
LDannijs Mar 6, 2024
6ca9f09
Add vendorProfileID deprecation message
LDannijs Mar 6, 2024
479c9fb
Merge branch 'master' into validate-features
LDannijs Mar 13, 2024
cd0e2e2
fix profileIDs
LDannijs Mar 13, 2024
929d25d
update profileID
LDannijs Mar 20, 2024
91c3187
Merge branch 'master' into validate-features
LDannijs Mar 20, 2024
7a83a9d
fix vendor in index
LDannijs Mar 25, 2024
58e2dc2
Merge branch 'master' into validate-features
LDannijs Mar 25, 2024
71e0caa
Merge branch 'master' into validate-features
johanstokking Mar 26, 2024
2c2d18b
Merge branch 'master' into validate-features
LDannijs Apr 3, 2024
e8f8de3
Merge branch 'master' into validate-features
Jaime-Trinidad Apr 5, 2024
e03a07f
Merge branch 'TheThingsNetwork:master' into validate-features
LDannijs Apr 9, 2024
dac4dec
Merge branch 'master' into validate-features
Jaime-Trinidad Apr 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 2 additions & 2 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ Closes #0000, References #0000, etc.
- ...

#### Checklist for Reviewers
<!-- Guidelines to follow when reviewing pull request -->
<!-- Guidelines to follow when reviewing pull request, please do not remove. -->

- [ ] Title and description should be descriptive (Not just a serial number for example).
- [ ] `vendorProfileID` should not be `vendorID`.
- [ ] `vendorProfileID` should not be `vendorID` and should be a unique value for every profile.
- [ ] All devices should be listed in the vendor's `index.yaml` file.
- [ ] At least 1 image per device and should be transparent.

Expand Down
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ GO = go
GOBIN = $(PWD)/bin
export GOBIN

vendor-id=
johanstokking marked this conversation as resolved.
Show resolved Hide resolved

.PHONY: default
default: validate

Expand All @@ -34,7 +36,11 @@ deps.update:

.PHONY: validate
validate:
$(NPM) run validate
@if [ -z "${vendor-id}" ]; then \
$(NPM) run validate; \
else \
$(NPM) run validate -- --vendor-id $(vendor-id); \
fi
LDannijs marked this conversation as resolved.
Show resolved Hide resolved

.PHONY: fmt
fmt:
Expand Down
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ To validate data:
$ make validate
```

The validation also supports validating a single vendor's files:

```bash
$ make validate vendor-id=<id-of-vendor>
```

[Visual Studio Code](https://code.visualstudio.com/) is a great editor for editing the Device Repository. You can validate your data automatically using the [YAML plugin](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml).

The YAML plugin supports you with filling out the document. When hitting Ctrl + Space, all fields are shown. The Debug Console of Visual Studio provides feedback by highlighting the incorrect fields.
Expand Down Expand Up @@ -218,10 +224,10 @@ There are a few guidelines to follow for images:
Each referenced end device profile needs to be defined in the **End device profile**, with the same filename as the profile ID: `vendor/<vendor-id>/<profile-id>.yaml`:

```yaml
# Vendor profile ID, can be freely issued by the vendor. NOTE: The vendor profile ID is different from the vendorID.
# The vendor Profile ID should be an incremental counter for every unique device listed in the vendor's folder.
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/wp-content/uploads/2020/11/TR005_LoRaWAN_Device_Identification_QR_Codes.pdf
# The vendorProfileID is a distinct (preferably incremental) value for every unique profile listed in the vendor's folder.
# This value can be freely issued by the vendor and is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/wp-content/uploads/2020/11/TR005_LoRaWAN_Device_Identification_QR_Codes.pdf#page=8
# NOTE: The vendorProfileID is different from the vendorID.
vendorProfileID: 0

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
Expand Down
19 changes: 19 additions & 0 deletions bin/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/bin/bash
LDannijs marked this conversation as resolved.
Show resolved Hide resolved

make validate fmt
make validate

validation_output=$(node ./validate.js 2>&1)

# Capture the exit status of the validation script
VALIDATION_STATUS=$?

# Check the exit status
if [ $VALIDATION_STATUS -ne 0 ]; then

# Display the last line of the validation log
last_line=$(echo "$validation_output" | tail -n 1)
echo "$last_line"

exit 1
fi
153 changes: 119 additions & 34 deletions bin/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ const imageType = require('image-type');

const ajv = new Ajv({ schemas: [require('../lib/payload.json'), require('../schema.json')] });

const options = yargs.usage('Usage: --vendor <file>').option('v', {
alias: 'vendor',
describe: 'Path to vendor index file',
type: 'string',
demandOption: true,
default: './vendor/index.yaml',
}).argv;
const options = yargs
.usage('Usage: --vendor <file> [--vendor-id <id>]')
.option('v', {
alias: 'vendor',
describe: 'Path to vendor index file',
type: 'string',
demandOption: true,
default: './vendor/index.yaml',
})
.option('vendor-id', {
describe: 'Specific vendor ID to validate',
type: 'string',
}).argv;

let validateVendorsIndex = ajv.compile({
$ref: 'https://schema.thethings.network/devicerepository/1/schema#/definitions/vendorsIndex',
Expand Down Expand Up @@ -202,6 +208,44 @@ function formatValidationErrors(errors) {
return errors.map((e) => `${e.dataPath} ${e.message}`);
}

const vendorsIndex = yaml.load(fs.readFileSync(options.vendor));

// Validate vendors index
if (!validateVendorsIndex(vendorsIndex)) {
console.error(`${options.vendor} index is invalid: ${formatValidationErrors(validateVendorsIndex.errors)}`);
process.exit(1);
}
console.log(`vendor index: valid`);

// Get the list of vendor IDs from the vendor/index.yaml
const vendorIdsInIndex = vendorsIndex.vendors.map((vendor) => vendor.id);

// Get the list of vendor folders in the vendor directory
const vendorFolders = fs
.readdirSync('./vendor', { withFileTypes: true })
.filter((dirent) => dirent.isDirectory())
.map((dirent) => dirent.name);

// Check for vendors in the vendor folder that are not in the vendor/index.yaml
const vendorsNotInIndex = vendorFolders.filter((vendorId) => {
// Check if any vendor ID in the index is present in the folder name, or vice versa
const normalizedVendorId = vendorId.toLowerCase(); // Normalize for case-insensitive comparison
LDannijs marked this conversation as resolved.
Show resolved Hide resolved
return (
!vendorIdsInIndex.some((indexId) => normalizedVendorId.includes(indexId.toLowerCase())) &&
!vendorIdsInIndex.some((indexId) => indexId.toLowerCase().includes(normalizedVendorId))
LDannijs marked this conversation as resolved.
Show resolved Hide resolved
);
});

if (vendorsNotInIndex.length > 0) {
console.error(
`Vendors found in the 'vendor' folder that are not listed in ${options.vendor} index:\n${vendorsNotInIndex.join(
', '
)}`
);
process.exit(1);
}
console.log(`All vendors in the 'vendor' folder are listed in ${options.vendor} index.`);

const vendors = yaml.load(fs.readFileSync(options.vendor));

if (!validateVendorsIndex(vendors)) {
Expand All @@ -212,7 +256,18 @@ console.log(`vendor index: valid`);

const vendorProfiles = {};

const vendorIDToValidate = options['vendor-id'];

if (vendorIDToValidate && !vendors.vendors.some((v) => v.id === vendorIDToValidate)) {
console.error(`Specified vendor ID '${vendorIDToValidate}' does not exist in the repository.`);
process.exit(1);
}

vendors.vendors.forEach((v) => {
if (vendorIDToValidate && v.id !== vendorIDToValidate) {
return;
}

const key = v.id;
const folder = `./vendor/${v.id}`;
if (v.logo) {
Expand Down Expand Up @@ -273,41 +328,71 @@ vendors.vendors.forEach((v) => {
const regionProfile = version.profiles[region];
const key = `${v.id}: ${d}: ${region}`;
const vendorID = regionProfile.vendorID ?? v.id;

if (!vendorProfiles[vendorID]) {
vendorProfiles[vendorID] = {};
}

if (!vendorProfiles[vendorID][regionProfile.id]) {
const profile = yaml.load(fs.readFileSync(`./vendor/${vendorID}/${regionProfile.id}.yaml`));
if (!validateEndDeviceProfile(profile)) {
console.error(
`${key}: profile ${vendorID}/${regionProfile.id} invalid: ${formatValidationErrors(
validateEndDeviceProfile.errors
)}`
);
process.exit(1);
}
}
vendorProfiles[vendorID][regionProfile.id] = true;
console.log(`${key}: profile ${vendorID}/${regionProfile.id} valid`);

if (regionProfile.codec && !codecs[regionProfile.codec]) {
const codec = yaml.load(fs.readFileSync(`${folder}/${regionProfile.codec}.yaml`));
if (!validateEndDevicePayloadCodec(codec)) {
console.error(
`${key}: codec ${regionProfile.codec} invalid: ${formatValidationErrors(
validateEndDevicePayloadCodec.errors
)}`

// Check if vendorProfileID is unique within the same company folder
if (profile.vendorProfileID !== undefined) {
LDannijs marked this conversation as resolved.
Show resolved Hide resolved
const duplicateProfiles = Object.entries(vendorProfiles[vendorID]).filter(
([profileId, existingProfile]) =>
existingProfile.vendorProfileID === profile.vendorProfileID && profileId !== regionProfile.id
);
process.exit(1);

if (duplicateProfiles.length > 0) {
const duplicateProfilePaths = duplicateProfiles.map(([duplicateProfileId, duplicateProfile]) => {
return `${duplicateProfileId} (vendorProfileID: ${duplicateProfile.vendorProfileID})`;
});

console.error(
`${key}: vendorProfileID ${
profile.vendorProfileID
} is not unique within ${vendorID} folder. Duplicate profiles found in: ${duplicateProfilePaths.join(
', '
)}`
);
process.exit(1);
}

vendorProfiles[vendorID][regionProfile.id] = { vendorProfileID: profile.vendorProfileID };

if (!validateEndDeviceProfile(profile)) {
console.error(
`${key}: profile ${vendorID}/${regionProfile.id} invalid: ${formatValidationErrors(
validateEndDeviceProfile.errors
)}`
);
process.exit(1);
}

console.log(`${key}: profile ${vendorID}/${regionProfile.id} valid`);
} else {
console.warn(`${key}: vendorProfileID is missing in ${vendorID}/${regionProfile.id}.yaml`);
LDannijs marked this conversation as resolved.
Show resolved Hide resolved
}
codecs[regionProfile.codec] = true;
await validatePayloadCodecs(folder, codec)
.then(() => console.log(`${key}: payload codec ${regionProfile.codec} valid`))
.catch((err) => {
console.error(`${key}: payload codec ${regionProfile.codec} invalid`);
console.error(err);

if (regionProfile.codec && !codecs[regionProfile.codec]) {
const codec = yaml.load(fs.readFileSync(`${folder}/${regionProfile.codec}.yaml`));
if (!validateEndDevicePayloadCodec(codec)) {
console.error(
`${key}: codec ${regionProfile.codec} invalid: ${formatValidationErrors(
validateEndDevicePayloadCodec.errors
)}`
);
process.exit(1);
});
}
codecs[regionProfile.codec] = true;
await validatePayloadCodecs(folder, codec)
.then(() => console.log(`${key}: payload codec ${regionProfile.codec} valid`))
.catch((err) => {
console.error(`${key}: payload codec ${regionProfile.codec} invalid`);
console.error(err);
process.exit(1);
});
}
}
});
});
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"validate": "bin/validate.js"
},
"scripts": {
"prestart": "cp bin/pre-commit .git/hooks/ && chmod +x .git/hooks/pre-commit && echo 'hook copied'",
LDannijs marked this conversation as resolved.
Show resolved Hide resolved
"validate": "node bin/validate.js",
"format": "prettier --write schema.json \"vendor/**/*.yaml\" \"bin/**/*.js\""
},
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-compact-tracker-pro-eu868.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 1
LDannijs marked this conversation as resolved.
Show resolved Hide resolved

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-compact-tracker-pro-us915.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 2

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-geolocation-module-profile.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 3

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-industrial-tracker-pro-as923.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 4

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-industrial-tracker-pro-eu868.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 5

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-industrial-tracker-pro-us915.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 6

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-micro-tracker-profile-as923.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 7

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-micro-tracker-profile-eu868.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 8

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-micro-tracker-profile-us915.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 9

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-smart-badge-profile-as923.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 10

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-smart-badge-profile-eu868.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 11

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/abeeway/abeeway-smart-badge-profile-us915.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Vendor profile ID, can be freely issued by the vendor
# This vendor profile ID is also used on the QR code for LoRaWAN devices, see
# https://lora-alliance.org/sites/default/files/2020-10/LoRa_Alliance_Vendor_ID_for_QR_Code.pdf
vendorProfileID: 0
vendorProfileID: 12

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: 1.0.2
Expand Down
2 changes: 1 addition & 1 deletion vendor/adeunis/adeunis-default-profile-c.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
vendorProfileID: 16
vendorProfileID: 1

# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1
macVersion: '1.0.2'
Expand Down
Loading
Loading