Repository navigation
test: regression test for #223 (serial hook must not receive c_newline) - #4
Closed
zakoverflow wants to merge 42 commits into
Closed
zakoverflow wants to merge 42 commits into
zakoverflow wants to merge 42 commits into
Conversation
- Wait longer at the end of `after_upload`. I was seeing the dreaded "device reports readiness to read but returned no data" error again. This seems to have cleared it. - I was seeing intermittent failures due to the Notestation reservation never creating the ready file. I added code to the workflow to print the Notestation client log in the event of a ready file timeout.
…es#198) In this PR, we removed code that automatically called card.aux.serial with ms and max parameters to add flow control for AUX serial comms in note-arduino: blues/note-arduino#141 It turns out these tests were relying on that behavior in order to not have Notecard responses over AUX serial wind up corrupted. This corruption was manifesting as CRC errors on the responses. I saw with a logic analyzer that the Notecard was sending the correct response, but that note-c/the Swan was not receiving it correctly. So the fix is to restore the card.aux.serial request with the old ms and max parameters by adding it to the test.
* feat: NoteSetRequestTimeout * feat: Unit-tests for NoteSetRequestTimeout * Fix typo in doxygen comment Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* feat: heartbeat support
* chore: Unit-test {heartbeat}
* feat: heartbeat callback * chore: test heartbeat callback
* chore: gate heartbeat callback * fix: unit-tests
* chore: Doxygen comment public API * fix: deduplicate docs
Upgrade Notestation CLI to v2.2.0 and update the GitHub workflow accordingly.
* chore: bring notecard COBS optimizations into note-c These are huge optimizations that impact large transfers - arguably 10x-70x. * Update n_cobs.c Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Zachary J. Fields <zachary_fields@yahoo.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* chore: Test COBS functions * chore: Update LCOV parameters/performance * fix: Dockerfile USER creation * fix: Possible NULL dereference * chore: Suppress cppcheck false-positive
* fix: GitHub CI publish_ci_image * chore: Enable dispatch of Docker image publication
* ci: skip HIL tests on fork PRs The notecard-binary-test job requires secrets (Tailscale OAuth, Notehub credentials, ngrok auth) that are not available to pull requests from forks, causing guaranteed failures. Skip the job on fork PRs while still running it for same-repo PRs, scheduled runs, and manual workflow dispatches. * Improve condition for notecard-binary-test execution Updated condition to skip notecard-binary-test on fork PRs and nightly builds. --------- Co-authored-by: Zachary J. Fields <zachary_fields@yahoo.com>
… 2) (blues#228) * refactor: propagate const through internal transmit chain Make internal transmit functions accept const uint8_t * buffers, reflecting that the transmit path never modifies its input data. This resolves scattered const-discarding casts at call sites (e.g. (uint8_t *)c_newline, (uint8_t *)request) by giving them a const-correct destination type. The internal nTransmitFn typedef is updated to match. At the two public API boundaries (_noteSerialTransmit and _noteI2CTransmit), an intentional cast back to uint8_t * is added with an explanatory comment, consolidating the remaining const inconsistency to a single known location per interface. These casts will be removed in a follow-up breaking change when serialTransmitFn and i2cTransmitFn are updated to accept const uint8_t *. No functional or behavioral change — purely a const-correctness cleanup. The public hook typedefs (serialTransmitFn, i2cTransmitFn) are intentionally left unchanged for this commit. * fix: use stack buffers for flash-resident literals in transmit calls Replace direct casts of flash-resident string literals with small stack-allocated buffers at the three call sites where .rodata data was passed through the non-const transmit hook boundary: - n_serial.c: c_newline (\r\n) appended after JSON body - n_serial.c: "\n" sent during serial reset/resync - n_i2c.c: "\n" sent during I2C reset/resync Each buffer is 1-2 bytes. The stack cost is negligible, but the consequence is meaningful: the hook receives a pointer to mutable RAM rather than flash, eliminating the UB that would occur if any hook implementation writes to the buffer before transmitting. The intentional boundary casts in _noteSerialTransmit and _noteI2CTransmit remain in place, but with these stack buffers the pointed-to memory is now genuinely mutable, so no UB can arise even if a hook writes to it. TODO comments on each buffer mark them for removal in phase 2, when serialTransmitFn and i2cTransmitFn are updated to const uint8_t *. * test: capture _noteI2CTransmit arg before stack unwinds in _i2cNoteReset test After the stack-buffer fix (d59a62b), the buf pointer passed to _noteI2CTransmit points to a local uint8_t lf[] = {'\n'} inside _i2cNoteReset. Dereferencing arg1_history[0] after the call returns is UB (dangling stack pointer). Fix: add a custom fake _noteI2CTransmit_capture that copies buf[0] into a static variable during the call. The THEN block checks that static variable instead of the now-invalid history pointer.
* fix: correct memcmp size argument in _crcError CRC field detection
The third argument to memcmp was ((sizeof(CRC_FIELD_NAME_TEST) - 1)) != 0)
which evaluates != 0 as part of the size expression, yielding boolean 1
instead of the intended length 7. This caused CRC field detection to
compare only 1 byte (a quote character) instead of the full pattern.
This is triggered on Notecard firmware that does not support CRC. A
hub.status response like {"connected":false,"status":"connecting"}
has a quote at the CRC detection offset (position 19 = 41 - 22). The
1-byte compare falsely matches it as a CRC field, permanently setting
notecardFirmwareSupportsCrc=true and causing all subsequent responses
to be flagged as CRC errors.
When the Notecard does support CRC, the real CRC field sits at the
expected offset, so the 1-byte compare accidentally matches correctly.
Add regression tests with a realistic hub.status response that triggers
the bug to verify the fix.
* fix: Missing CRC bug
* fix: early return condition order
* fix: improve invalid JSON test
* fix: typos in comments
---------
Co-authored-by: Matthew McGowan <mat.mcgowan+git@gmail.com>
======================================================================
Background
----------
The Notecard firmware's cobsGuaranteedFit() tells the host library the
maximum number of unencoded binary bytes that will safely fit in the
Notecard's I/O buffer after COBS encoding. For years, the Notecard used
a simple, slightly conservative formula:
overhead = 1 + (buflen / 254) + 1
return buflen - overhead
This estimates the COBS overhead from the *buffer* size rather than from
the (smaller) payload size, which systematically overestimates the number
of code bytes by a small amount. For the Notecard's 256 KB I/O buffer
(262,144 bytes), this formula reports max = 261,110.
A recent firmware optimization replaced this with a formula intended to
compute the mathematically exact answer. For the same buffer it now
reports max = 261,114 -- four bytes more than before.
A customer immediately reported that filling the buffer to this new
maximum causes card.binary.get to fail. The old limit of 261,110 had
always worked; 261,114 does not.
The bug: ceil(u/254) vs floor(u/254) + 1
-----------------------------------------
COBS encoding partitions data into blocks of up to 254 bytes, prefixing
each block with a "code byte" that indicates its length (or the distance
to the next zero). The central question for buffer-size arithmetic is:
how many code bytes does worst-case encoding produce for u data bytes?
The note-c library computed this as:
ceil(u / 254)
The correct answer is:
floor(u / 254) + 1
For most values these are identical. They diverge at exact multiples
of 254:
ceil(254 / 254) = 1 WRONG
floor(254 / 254) + 1 = 2 CORRECT
Why the +1? Walk through what the encoder actually does with 254
non-zero bytes:
1. Reserve initial code byte (code = 1)
2. Copy 254 data bytes (code increments to 0xFF)
3. code == 0xFF: write it, reserve new (first code byte emitted)
4. No data remains; loop exits
5. Write final code byte = 0x01 (second code byte emitted)
Output: [0xFF] [254 data bytes] [0x01] = 256 bytes
The encoder ALWAYS writes a final code byte for the last block, even
when the data ends exactly on a 254-byte boundary. That final code byte
is the one that ceil() misses. ceil(254/254) = 1, but the encoder
emitted two code bytes.
This error appeared in two note-c functions:
_cobsEncodedMaxLength(u): Used ceil(u/254) for the code byte count,
underestimating by 1 at exact multiples of 254. For u = 254 it
returned 256 instead of the correct 257.
_cobsGuaranteedFit(bufLen): The algebraic inverse, which solved for
the largest payload that fits in a given buffer. It used the same
flawed ceil-based derivation. At buffer sizes of the form 255k + 1
(i.e. 256, 511, 766, ...) it overestimated the safe payload by 1,
promising a fit that would actually overflow after encoding.
Why it was hidden, and why it surfaced
--------------------------------------
The Notecard's old conservative cobsGuaranteedFit reported max = 261,110
for a 262,144-byte buffer. The exact answer is 261,114. That four-byte
gap meant the host never attempted to store a payload at a size where
the ceil-vs-floor discrepancy in _cobsEncodedMaxLength could matter.
The conservative formula had been quietly absorbing the note-c library's
arithmetic error for years.
When the Notecard's formula was updated to report the exact maximum, the
safety margin vanished. The host was now operating at the true edge of
the buffer, where every byte of COBS overhead must be accounted for
precisely. Any arithmetic error -- in the host library's buffer
validation, in the encoding expansion calculation, or anywhere else in
the pipeline -- would now manifest as a real failure instead of being
silently absorbed by the four spare bytes.
The Notecard firmware has been reverted to the old conservative formula.
It has a long track record, is obviously correct (it can only
underestimate capacity, never overestimate), and its cost is negligible:
roughly bufLen/64770 bytes of unused capacity, which is about 4 bytes
per 256 KB.
The fix (note-c)
----------------
_cobsEncodedMaxLength: Replace the ceil-based code byte computation
with floor + 1:
Old: codeBytes = (length / 254) + ((length % 254) > 0) // ceil
New: codeBytes = (length / 254) + 1 // floor + 1
_cobsGuaranteedFit: Replace the flawed inverse derivation. Given
the constraint u + floor(u/254) + 2 <= bufLen:
Old: e = bufLen - 1; return e - floor((e + 254) / 255)
New: t = bufLen - 2; return t - floor((t + 1) / 255)
Both new formulas have been verified against a reference COBS encoder
for all tested values from 0 to 262,144.
Tests added
-----------
_cobsEncodedMaxLength: Tests at exact multiples of 254 (254, 508) that
fail with ceil() but pass with floor()+1. Also tests at 253 and 255 to
confirm non-boundary values are unaffected.
_cobsGuaranteedFit: Tests at 255k+1 buffer sizes (256, 511, 766) that
fail with the old formula. Also tests at 257 (non-boundary) and 262,144
(the customer's actual buffer size) to anchor the expected behavior.
Round-trip consistency: For a range of buffer sizes -- including every
255k and 255k+1 value from 0 to 2,550, plus large values up to 262,144
-- the tests verify that _cobsEncodedMaxLength(_cobsGuaranteedFit(B))
never exceeds B. This ensures the two functions agree with each other
and that the guaranteed-fit promise is upheld.
…es#235) - Fix md5srv.py to handle requests using Transfer-Encoding: chunked instead of Content-Length. Notehub sends binary web.post payloads chunked, causing post_data to read 0 bytes and write_file to return 400 "Request body is empty." - Fix write_file to fall back to len(self.post_data) when the Content-Length header is absent, rather than calling int(None) and crashing with a 500. - Add error logging to md5srv.py: on any failed request, log the Content-Length, Transfer-Encoding, and Content-Type headers along with the error code and message to aid future debugging. - Capture Notecard USB trace output (req, comm, sync, mem) to trace.log during the test run and upload it as a CI artifact. - Upload md5srv.log as a CI artifact so server-side failures are visible without needing to reproduce locally. Commit message written by Claude.
n_atof.c used K&R (pre-ANSI C) style for the JAtoN function definition, separating parameter types from the parameter list. This is an obsolete style that GCC 15 rejects with -Wold-style-definition, and causes build failures in downstream projects that compile note-c sources with -Werror (e.g. on Debian Forky / Ubuntu 25.04 and later toolchains). Convert to a standard ANSI C prototype-style definition, preserving all the original inline parameter documentation.
* fix: very bad memory leaks in low-memory conditions
* chore: Use note-c allocation scheme in cJSON
Don't grow object exponentially; i'm trying to operate with a 2kb-4kb heap and the doubling strategy causes almost immediate failures.
---------
Co-authored-by: Zachary J. Fields <zachary_fields@yahoo.com>
Low memory cJSON improvements
* docs: add living architecture guidance
* Add architecture map artifacts
* Load HTML architecture map from JSON
* Check architecture HTML data drift in CI
* Publish architecture map in docs site
…es#236) * build: Add macOS build support for unit tests Add conditional CMake configuration for macOS (APPLE): - Skip -m32 and Linux-specific linker flags - Define HAVE_STRLCPY/HAVE_STRLCAT (macOS provides these natively) - Exclude n_str.c (strlcpy/strlcat bundled implementations) - Suppress AppleClang-specific warnings in upstream code - Use -flat_namespace for dylib and test executables to enable FFF test fake symbol interposition All changes are guarded by if(APPLE)/if(NOT APPLE) so Linux CI behavior is completely unchanged. * build: Add macOS build support for unit tests Use check_symbol_exists() to detect system-provided strlcpy/strlcat (macOS, *BSD) and conditionally exclude n_str.c and define HAVE_STRLCPY/ HAVE_STRLCAT guards. This avoids collisions with the platform's fortified string macros that prevented compilation on macOS. Guard the 32-bit x86 compile/link flags behind a Linux x86 check instead of if(NOT APPLE), making the build portable to any non-x86 platform. Suppress two AppleClang warnings-as-errors in upstream code (-Wstrict-prototypes, -Wunused-but-set-variable). Add -Wl,-flat_namespace on macOS for both the library and test executables so FFF test fakes can interpose dylib symbols. * fix: Resolve AppleClang warnings-as-errors - n_ua.c: add void to NoteUserAgent() prototype to fix -Wstrict-prototypes (empty parens means unspecified args in C) - n_helpers.c: remove unused variables j and lastLengthCount that triggered -Wunused-but-set-variable With these fixes the -Wno-* suppressions added in the previous commit are no longer needed and are removed from CMakeLists.txt. * ci: Add macOS build verification job to CI pipeline Adds a `run_macos_unit_tests` job that configures and builds note-c with tests on macos-latest. Tests are not run via ctest because FFF symbol interposition does not fully work on macOS — even with -flat_namespace, intra-library calls are resolved at link time and cannot be faked. * fix: resolve CI failures for strlcpy/strlcat on newer glibc - Add `strlcpy` and `strlcat` to the libc dependency whitelist so the `check_libc_dependencies` job passes when `n_str.c` is excluded on systems that provide these functions natively (glibc 2.38+). - Only add `n_str.c` to the lcov coverage exclude list when the file is actually compiled, avoiding an lcov error on unused patterns. * build: Add note_c_lib static library target for downstream consumers Add a clean note_c_lib static library target that downstream projects (e.g. note-posix) can consume via add_subdirectory + target_link_libraries without pulling in the unit test harness, 32-bit cross-compile flags, or FFF interposition settings. - Bump cmake_minimum_required to 3.21 for `PROJECT_IS_TOP_LEVEL` - Default `NOTE_C_BUILD_TESTS` to ON only when note-c is the top-level project; OFF when included as a subdirectory - Move the existing note_c shared library (with -Werror, 32-bit flags, NOTE_C_TEST, etc.) inside the NOTE_C_BUILD_TESTS guard - Add test/integration/ with a standalone CMake project that verifies `note_c_lib` links correctly via `add_subdirectory` * build: Address review feedback - Remove the dead `NOTE_C_NO_LIBC` block from the static note_c_lib target. - Add a `FATAL_ERROR` guard so enabling `NOTE_C_NO_LIBC` without `NOTE_C_BUILD_TESTS` fails loudly instead of appearing to audit when the audit target isn't built, and clarify the option's help text. - Wire test/integration into CI (run_integration_tests job) so the add_subdirectory + target_link_libraries(... note_c_lib) downstream contract is exercised on every PR. * build: De-duplicate shared config between note_c and note_c_lib.
…g sans delays/retry (blues#239) * feat: NotePing() allows for testing to see if a notecard is responding sans delays/retry The new NotePing method issues an echo transaction *without delays or retries* so that one can quickly determine whether a notecard is connected and communicating successfully. This is used by the note-arduino notecard.ping() to do automatic baud rate recovery during init. * test: isolate NotePing behavior (blues#241) * test: avoid unbounded NotePing request reads (blues#244) * refactor: polish NotePing response handling (blues#245) * test: avoid unbounded NotePing request reads * refactor: polish NotePing response handling * fix: lock I2C mutex while draining NotePing input (blues#246) --------- Co-authored-by: Zachary J. Fields (OpenClaw) <zachary_fields@outlook.com>
augmented dependency graph to facilitate AI comprehension
…lues#251) Explicitly cast JINTEGER_MAX/MIN and UINTMAX_MAX to JNUMBER in the saturation comparisons in n_cjson.c and n_ftoa.c. INT64_MAX and UINTMAX_MAX are not exactly representable as double; newer AppleClang (Xcode 26.5 on macos-latest) rejects the implicit conversion when the note_c test library is built with -Werror. Keeps run_macos_unit_tests on macos-latest with a short comment explaining the source-side fix.
* chore: Update to reasonable default for time sync * ci: pin macOS unit-test job to macos-15 macos-latest now resolves to macOS 26 / Xcode 26.5, where AppleClang fails the note_c -Werror build on pre-existing int-to-double limit comparisons. Pin to macos-15 so this PR is not blocked by the toolchain image change; a follow-up can restore macos-latest with a source fix.
The nightly HIL job kept failing, and recently hung for hours. Fixes: - Use the current notestation-client (v3.5.1) via the install_notestation_client action, replacing the old v2.2.0 client. The old one briefly flickered the Swan's USB test port while the board rebooted after flashing, so the tests couldn't open it and none ran. - Wait for the board's prompt and send the "start" command a few times. The board can miss a single "start" sent while its USB is still settling (~2s), which left the run hung with no output. - Add a concurrency group so a new push cancels the previous run instead of two runs fighting over the single test station. - Cap the job at 30 minutes so a stall fails fast. - Drop the "Install notecard CLI" and socat steps; the current client no longer needs them (a plain cat streams the Notecard trace). Verified on barcelona-notestation-1. Authored by Claude Code (AI agent). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit was made by an AI agent. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Fix card.binary HIL test build on STM32 Arduino core 3.x.
- PlatformIO's ststm32 20.0.0 platform brings in STM32 Arduino core 3.0.
The test's platformio.ini does not pin the platform, so CI picked it up.
- Core 3.x declares Serial2 and Serial3 itself. The test's own Serial2 and
Serial3 objects then fail with "conflicting declaration".
- Core 3.x also makes HardwareSerial an abstract class. Uart is now the
concrete UART class.
- Remove Serial2. No code uses it.
- Rename Serial3 to notecardSerial. It keeps the same pins (A5 RX, A4 TX).
- Pick Uart on core 3.x and HardwareSerial on core 2.x, so the test builds
on both cores.
- Checked with local builds on core 3.0 (ststm32 20.0.0) and core 2.12
(ststm32 19.7.1).
This commit message was written by Claude, an AI agent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Mask Notehub and MD5 server tokens in card.binary HIL workflow logs.
- The workflow writes NOTEHUB_ACCESS_TOKEN and MD5SRV_TOKEN to GITHUB_ENV.
GitHub does not mask values that a step writes there. It printed both
tokens in plain text in the env block of every later step.
- The "Notehub HTTP route request" log line also printed MD5SRV_TOKEN.
- Send ::add-mask:: for each token before the step writes it to
GITHUB_ENV. GitHub then shows *** in place of the token in all later
output.
This commit message was written by Claude, an AI agent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Fail the card.binary HIL upload when GDB can't flash the Swan.
- The upload command passed each GDB command with -ex and ended with
"quit". GDB runs later -ex commands after one fails, and "quit" exits 0.
A failed connect or load looked like a successful upload.
- The job then waited for the Swan's USB port and failed with a
misleading "Timed out waiting for test port" error.
- Move "monitor reset halt", "load", and "monitor reset" into upload.gdb
and run GDB with -batch. GDB stops the file at the first failed command
and exits non-zero, so PlatformIO fails the upload step.
- Checked locally: with an unreachable GDB server, PlatformIO fails with
"[upload] Error 1" and GDB's error message.
This commit message was written by Claude, an AI agent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Show in the HIL log what the card.binary upload flashed.
- GDB prints load progress only for interactive commands, so the CI log
never showed whether the firmware load happened.
- Add echo markers and compare-sections to upload.gdb. compare-sections
reads the flash back and prints matched or MIS-MATCHED for each section.
- compare-sections reports a mismatch as a warning, so it does not fail
the upload. A failed connect, reset, or load still does.
This commit message was written by Claude, an AI agent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Show GDB upload output in the card.binary HIL log.
- "pio test" shows the upload command's stderr but hides its stdout. GDB
writes echo and compare-sections output to stdout, so the markers from
the last commit did not show in the CI log.
- Send GDB's stdout to stderr with 1>&2. PlatformIO runs the upload
command through a shell, and GDB's exit status still reaches it.
- Checked locally with a stand-in for GDB_CMD: its stdout now shows, and a
non-zero exit still fails the upload with "[upload] Error".
This commit message was written by Claude, an AI agent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Set the Notecard product before the card.binary connect wait.
- barcelona-notestation-1 is shared with note-zephyr HIL tests. They leave
the Notecard with product com.blues.note-zephyr.hil, which has no
Notehub project, so the Notecard delays its connect attempts
("connect delayed", product-noexist) for several minutes.
- The waitForNotecardConnected test ran before any test sent hub.set, so
it waited 5 minutes on the old product and failed. The next test's
hub.set cleared the delay, and the Notecard connected 13 s later.
- Send the suite's hub.set (product and continuous mode) at the start of
waitForNotecardConnected. A product change also takes the Notecard out
of the penalty box.
This commit message was written by Claude, an AI agent.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The code fix landed in blues#228 (stack copy in _serialNoteTransaction). This adds the missing regression test. It fails if the (uint8_t *)c_newline cast is reintroduced and passes with the stack buffer. Hook stays non-const.
Coverage Report for CI Build 38457863397Warning No base build found for commit Coverage: 93.408%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats💛 - Coveralls |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Regression test for blues#223. The code fix (stack copy of \r\n before the non-const
_SerialTransmithook) is already on master via blues#228. This adds a test that fails if(uint8_t *)c_newlineis passed to the hook again.Blast radius: test-only (test/src/_serialNoteTransaction_test.cpp, +9 lines). No library code or hook signature change.
Verified: the new test fails with the old cast restored (165/166). ./scripts/run_unit_tests.sh on this branch: 166/166 pass.