Compare commits

..

14 commits

Author SHA1 Message Date
Thomas Kolb 4dc2c60c8b rx: reduce squelch threshold from 10 to 6 dB
All checks were successful
/ build-hamnet70 (push) Successful in 27s
/ build-doc (push) Successful in 19s
/ deploy-doc (push) Has been skipped
During tests it was determined that this is much more reliable. The theory is
that the first packet of a burst was frequently not detected because the
squelch opened too late if the channel was very noisy.

Unfortunately, the squelch now opens very often even if no signal is present.
This will be improved in the future.
2024-09-12 23:52:33 +02:00
Thomas Kolb 706a1eb437 freq_est: make ramp-up delta phase check more strict
This should help with occasional jumps in the estimated carrier frequency on
receivers running frequency tracking.
2024-09-12 23:49:57 +02:00
Thomas Kolb eb802629a1 layer1/rx: calculate EVM during header and data reception 2024-09-07 00:48:11 +02:00
Thomas Kolb c61a7a7cf7 doc: start documenting the Layer 2 packets (WIP)
All checks were successful
/ build-hamnet70 (push) Successful in 26s
/ build-doc (push) Successful in 15s
/ deploy-doc (push) Has been skipped
2024-08-30 23:46:47 +02:00
Simon Ruderich 3f4a50bdce doc: don't use external resources
All checks were successful
/ build-hamnet70 (push) Successful in 26s
/ build-doc (push) Successful in 15s
/ deploy-doc (push) Has been skipped
Embedding (only) parts of MathJax is a bit ugly ...
2024-08-28 13:42:33 +02:00
Thomas Kolb b46b2536e1 Add a CI workflow compiling the hamnet70 program
All checks were successful
/ build-hamnet70 (push) Successful in 25s
/ build-doc (push) Successful in 14s
/ deploy-doc (push) Has been skipped
2024-08-27 22:01:51 +02:00
Thomas Kolb 1bcc8c2fea Add Dockerfile with hamnet70 build dependencies 2024-08-27 22:01:51 +02:00
Thomas Kolb c342cf656e Move documentation docker scripts to /ci/docker 2024-08-27 21:28:09 +02:00
Thomas Kolb 03829e058b Run deployment only on main branch 2024-08-27 21:23:40 +02:00
Thomas Kolb f686635837 doc: automatic builds and deployment using Forgejo Actions
All checks were successful
/ build-doc (push) Successful in 14s
/ deploy-doc (push) Successful in 9s
Actions run in a custom Docker image built by the scripts in `doc/docker`.
There are two jobs that run in sequence:

- `build-doc`: builds the documentation from the AsciiDoc sources.
- `deploy-doc`: Uploads the generated files to http://0fm.de
2024-08-27 21:10:30 +02:00
Thomas Kolb 10f634d144 doc: translate message sequence diagrams to mscgen
Mermaid is more beautiful, but the tool stack is really annoying. A Chrome
browser should not be necessary to generate some SVGs.
2024-08-27 18:07:12 +02:00
Thomas Kolb 79c340c20d connection: add state checks 2024-08-25 23:27:22 +02:00
Thomas Kolb fc9e5c5229 Combine layer2_rx and layer2_tx in a new connection module
The new module is not used yet, but this is a preparation for the future
multi-client networking support.
2024-08-25 22:26:56 +02:00
Thomas Kolb 899152a530 Remove config.h from Git; must be adapted by the user
A template for the config file is provided in src/config.h.template. It must be
copied to src/config.h and adapted for force users to set their own station
call sign.
2024-08-25 20:26:41 +02:00
51 changed files with 1489 additions and 78 deletions

View file

@ -0,0 +1,25 @@
on: [push]
jobs:
build-doc:
runs-on: docker
container:
image: git.tkolb.de/amateurfunk/hamnet70/asciidoctor:1.6
steps:
- uses: actions/checkout@v4
- run: cd doc && make
- uses: actions/upload-artifact@v3
with:
name: documentation
path: doc/out/
deploy-doc:
needs: build-doc
runs-on: docker
if: github.ref == 'refs/heads/main'
container:
image: git.tkolb.de/amateurfunk/hamnet70/asciidoctor:1.6
steps:
- run: mkdir ~/.ssh && echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_ed25519 && chmod 0600 ~/.ssh/id_ed25519 && echo "${{ secrets.SSH_KNOWN_HOST }}" > ~/.ssh/known_hosts
- uses: actions/download-artifact@v3
with:
name: documentation
- run: 'rsync -e "ssh -p 2342" -r . deployment@tkolb.de:'

View file

@ -0,0 +1,10 @@
on: [push]
jobs:
build-hamnet70:
runs-on: docker
container:
image: git.tkolb.de/amateurfunk/hamnet70/impl_buildenv:1.1
steps:
- uses: actions/checkout@v4
- run: cd impl && cp src/config.h.template src/config.h && sed -i 's/undefined/"TESTCALL"/' src/config.h
- run: cd impl && ./make.sh

16
ci/docker/doc/Dockerfile Normal file
View file

@ -0,0 +1,16 @@
FROM debian:stable
# for basic Forgejo + AsciiDoctor support
RUN apt update && apt install -y --no-install-recommends nodejs git ruby-rubygems make ca-certificates && apt clean
RUN gem install asciidoctor asciidoctor-diagram
# tools for diagram generation
RUN apt install -y --no-install-recommends mscgen && apt clean
# tools for automatic deployment
RUN apt install -y --no-install-recommends rsync openssh-client && apt clean
# run as unprivileged user in the container
RUN useradd -m ciuser
USER ciuser

8
ci/docker/doc/build.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
if [ -z "$1" ]; then
echo "usage: $0 <tag-version>"
exit 1
fi
docker build -t git.tkolb.de/amateurfunk/hamnet70/asciidoctor:$1 .

8
ci/docker/doc/test.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
if [ -z "$1" ]; then
echo "usage: $0 <tag-version>"
exit 1
fi
docker run -v $(realpath ../../../doc):/doc -it git.tkolb.de/amateurfunk/hamnet70/asciidoctor:$1

10
ci/docker/doc/upload.sh Executable file
View file

@ -0,0 +1,10 @@
#!/bin/sh
if [ -z "$1" ]; then
echo "usage: $0 <tag-version>"
exit 1
fi
docker login
docker push git.tkolb.de/amateurfunk/hamnet70/asciidoctor:$1
docker logout

11
ci/docker/impl/Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM debian:stable
# for Forgejo Actions
RUN apt update && apt install -y --no-install-recommends nodejs git ca-certificates && apt clean
# Hamnet70 build dependencies
RUN apt install -y --no-install-recommends cmake make gcc libc-dev libliquid-dev libhackrf-dev libfec-dev libfftw3-dev && apt clean
# run as unprivileged user in the container
RUN useradd -m ciuser
USER ciuser

8
ci/docker/impl/build.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
if [ -z "$1" ]; then
echo "usage: $0 <tag-version>"
exit 1
fi
docker build -t git.tkolb.de/amateurfunk/hamnet70/impl_buildenv:$1 .

8
ci/docker/impl/test.sh Executable file
View file

@ -0,0 +1,8 @@
#!/bin/sh
if [ -z "$1" ]; then
echo "usage: $0 <tag-version>"
exit 1
fi
docker run -v $(realpath ../../../impl):/impl -it git.tkolb.de/amateurfunk/hamnet70/impl_buildenv:$1

10
ci/docker/impl/upload.sh Executable file
View file

@ -0,0 +1,10 @@
#!/bin/sh
if [ -z "$1" ]; then
echo "usage: $0 <tag-version>"
exit 1
fi
docker login
docker push git.tkolb.de/amateurfunk/hamnet70/impl_buildenv:$1
docker logout

View file

@ -3,6 +3,9 @@ OUT_DIR=out
$(OUT_DIR)/hamnet70.html: hamnet70.adoc $(OUT_DIR)/hamnet70.html: hamnet70.adoc
mkdir -p $(OUT_DIR) mkdir -p $(OUT_DIR)
asciidoctor -D $(OUT_DIR) -r asciidoctor-diagram $< asciidoctor -D $(OUT_DIR) -r asciidoctor-diagram $<
@# Use local files to prevent privacy issues
cp -a ext/* $(OUT_DIR)
sed -i 's!src="[^"]*mathjax/[^/]*/!src="mathjax-2.7.9/!' $@
.PHONY: clean .PHONY: clean
clean: clean:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/Arrows.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{infix:{"\u219A":c.RELACCENT,"\u219B":c.RELACCENT,"\u219C":c.WIDEREL,"\u219D":c.WIDEREL,"\u219E":c.WIDEREL,"\u219F":c.WIDEREL,"\u21A0":c.WIDEREL,"\u21A1":c.RELSTRETCH,"\u21A2":c.WIDEREL,"\u21A3":c.WIDEREL,"\u21A4":c.WIDEREL,"\u21A5":c.RELSTRETCH,"\u21A7":c.RELSTRETCH,"\u21A8":c.RELSTRETCH,"\u21AB":c.WIDEREL,"\u21AC":c.WIDEREL,"\u21AD":c.WIDEREL,"\u21AE":c.RELACCENT,"\u21AF":c.RELSTRETCH,"\u21B0":c.RELSTRETCH,"\u21B1":c.RELSTRETCH,"\u21B2":c.RELSTRETCH,"\u21B3":c.RELSTRETCH,"\u21B4":c.RELSTRETCH,"\u21B5":c.RELSTRETCH,"\u21B6":c.RELACCENT,"\u21B7":c.RELACCENT,"\u21B8":c.REL,"\u21B9":c.WIDEREL,"\u21BA":c.REL,"\u21BB":c.REL,"\u21BE":c.RELSTRETCH,"\u21BF":c.RELSTRETCH,"\u21C2":c.RELSTRETCH,"\u21C3":c.RELSTRETCH,"\u21C4":c.WIDEREL,"\u21C5":c.RELSTRETCH,"\u21C6":c.WIDEREL,"\u21C7":c.WIDEREL,"\u21C8":c.RELSTRETCH,"\u21C9":c.WIDEREL,"\u21CA":c.RELSTRETCH,"\u21CB":c.WIDEREL,"\u21CD":c.RELACCENT,"\u21CE":c.RELACCENT,"\u21CF":c.RELACCENT,"\u21D6":c.RELSTRETCH,"\u21D7":c.RELSTRETCH,"\u21D8":c.RELSTRETCH,"\u21D9":c.RELSTRETCH,"\u21DA":c.WIDEREL,"\u21DB":c.WIDEREL,"\u21DC":c.WIDEREL,"\u21DD":c.WIDEREL,"\u21DE":c.REL,"\u21DF":c.REL,"\u21E0":c.WIDEREL,"\u21E1":c.RELSTRETCH,"\u21E2":c.WIDEREL,"\u21E3":c.RELSTRETCH,"\u21E4":c.WIDEREL,"\u21E5":c.WIDEREL,"\u21E6":c.WIDEREL,"\u21E7":c.RELSTRETCH,"\u21E8":c.WIDEREL,"\u21E9":c.RELSTRETCH,"\u21EA":c.RELSTRETCH,"\u21EB":c.RELSTRETCH,"\u21EC":c.RELSTRETCH,"\u21ED":c.RELSTRETCH,"\u21EE":c.RELSTRETCH,"\u21EF":c.RELSTRETCH,"\u21F0":c.WIDEREL,"\u21F1":c.REL,"\u21F2":c.REL,"\u21F3":c.RELSTRETCH,"\u21F4":c.RELACCENT,"\u21F5":c.RELSTRETCH,"\u21F6":c.WIDEREL,"\u21F7":c.RELACCENT,"\u21F8":c.RELACCENT,"\u21F9":c.RELACCENT,"\u21FA":c.RELACCENT,"\u21FB":c.RELACCENT,"\u21FC":c.RELACCENT,"\u21FD":c.WIDEREL,"\u21FE":c.WIDEREL,"\u21FF":c.WIDEREL}}});MathJax.Ajax.loadComplete(a.optableDir+"/Arrows.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/BasicLatin.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"||":[0,0,b.BIN,{fence:true,stretchy:true,symmetric:true}],"|||":[0,0,b.ORD,{fence:true,stretchy:true,symmetric:true}]},postfix:{"!!":[1,0,b.BIN],"'":c.ACCENT,"++":[0,0,b.BIN],"--":[0,0,b.BIN],"..":[0,0,b.BIN],"...":c.ORD,"||":[0,0,b.BIN,{fence:true,stretchy:true,symmetric:true}],"|||":[0,0,b.ORD,{fence:true,stretchy:true,symmetric:true}]},infix:{"!=":c.BIN4,"&&":c.BIN4,"**":[1,1,b.BIN],"*=":c.BIN4,"+=":c.BIN4,"-=":c.BIN4,"->":c.BIN5,"//":[1,1,b.BIN],"/=":c.BIN4,":=":c.BIN4,"<=":c.BIN5,"<>":[1,1,b.BIN],"==":c.BIN4,">=":c.BIN5,"@":c.ORD11,"||":[2,2,b.BIN,{fence:true,stretchy:true,symmetric:true}],"|||":[2,2,b.ORD,{fence:true,stretchy:true,symmetric:true}]}}});MathJax.Ajax.loadComplete(a.optableDir+"/BasicLatin.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/CombDiacritMarks.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{postfix:{"\u0311":c.ACCENT}}});MathJax.Ajax.loadComplete(a.optableDir+"/CombDiacritMarks.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/CombDiactForSymbols.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{postfix:{"\u20DB":c.ACCENT,"\u20DC":c.ACCENT}}});MathJax.Ajax.loadComplete(a.optableDir+"/CombDiactForSymbols.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/Dingbats.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"\u2772":c.OPEN},postfix:{"\u2773":c.CLOSE}}});MathJax.Ajax.loadComplete(a.optableDir+"/Dingbats.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/GeneralPunctuation.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"\u2016":[0,0,b.ORD,{fence:true,stretchy:true}],"\u2018":[0,0,b.OPEN,{fence:true}],"\u201C":[0,0,b.OPEN,{fence:true}]},postfix:{"\u2016":[0,0,b.ORD,{fence:true,stretchy:true}],"\u2019":[0,0,b.CLOSE,{fence:true}],"\u201D":[0,0,b.CLOSE,{fence:true}]}}});MathJax.Ajax.loadComplete(a.optableDir+"/GeneralPunctuation.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/GeometricShapes.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{infix:{"\u25A0":c.BIN3,"\u25A1":c.BIN3,"\u25AA":c.BIN3,"\u25AB":c.BIN3,"\u25AD":c.BIN3,"\u25AE":c.BIN3,"\u25AF":c.BIN3,"\u25B0":c.BIN3,"\u25B1":c.BIN3,"\u25B2":c.BIN4,"\u25B4":c.BIN4,"\u25B6":c.BIN4,"\u25B7":c.BIN4,"\u25B8":c.BIN4,"\u25BC":c.BIN4,"\u25BE":c.BIN4,"\u25C0":c.BIN4,"\u25C1":c.BIN4,"\u25C2":c.BIN4,"\u25C4":c.BIN4,"\u25C5":c.BIN4,"\u25C6":c.BIN4,"\u25C7":c.BIN4,"\u25C8":c.BIN4,"\u25C9":c.BIN4,"\u25CC":c.BIN4,"\u25CD":c.BIN4,"\u25CE":c.BIN4,"\u25CF":c.BIN4,"\u25D6":c.BIN4,"\u25D7":c.BIN4,"\u25E6":c.BIN4}}});MathJax.Ajax.loadComplete(a.optableDir+"/GeometricShapes.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/GreekAndCoptic.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{infix:{"\u03F6":c.REL}}});MathJax.Ajax.loadComplete(a.optableDir+"/GreekAndCoptic.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/Latin1Supplement.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{postfix:{"\u00B0":c.ORD,"\u00B4":c.ACCENT,"\u00B8":c.ACCENT}}});MathJax.Ajax.loadComplete(a.optableDir+"/Latin1Supplement.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/LetterlikeSymbols.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"\u2145":c.ORD21,"\u2146":[2,0,b.ORD]}}});MathJax.Ajax.loadComplete(a.optableDir+"/LetterlikeSymbols.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/MathOperators.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"\u2204":c.ORD21,"\u221B":c.ORD11,"\u221C":c.ORD11,"\u2221":c.ORD,"\u2222":c.ORD,"\u222C":c.INTEGRAL,"\u222D":c.INTEGRAL,"\u222F":c.INTEGRAL,"\u2230":c.INTEGRAL,"\u2231":c.INTEGRAL,"\u2232":c.INTEGRAL,"\u2233":c.INTEGRAL},infix:{"\u2201":[1,2,b.ORD],"\u2206":c.BIN3,"\u220A":c.REL,"\u220C":c.REL,"\u220D":c.REL,"\u220E":c.BIN3,"\u2214":c.BIN4,"\u221F":c.REL,"\u2224":c.REL,"\u2226":c.REL,"\u2234":c.REL,"\u2235":c.REL,"\u2236":c.REL,"\u2237":c.REL,"\u2238":c.BIN4,"\u2239":c.REL,"\u223A":c.BIN4,"\u223B":c.REL,"\u223D":c.REL,"\u223D\u0331":c.BIN3,"\u223E":c.REL,"\u223F":c.BIN3,"\u2241":c.REL,"\u2242":c.REL,"\u2242\u0338":c.REL,"\u2244":c.REL,"\u2246":c.REL,"\u2247":c.REL,"\u2249":c.REL,"\u224A":c.REL,"\u224B":c.REL,"\u224C":c.REL,"\u224E":c.REL,"\u224E\u0338":c.REL,"\u224F":c.REL,"\u224F\u0338":c.REL,"\u2251":c.REL,"\u2252":c.REL,"\u2253":c.REL,"\u2254":c.REL,"\u2255":c.REL,"\u2256":c.REL,"\u2257":c.REL,"\u2258":c.REL,"\u2259":c.REL,"\u225A":c.REL,"\u225C":c.REL,"\u225D":c.REL,"\u225E":c.REL,"\u225F":c.REL,"\u2262":c.REL,"\u2263":c.REL,"\u2266":c.REL,"\u2266\u0338":c.REL,"\u2267":c.REL,"\u2268":c.REL,"\u2269":c.REL,"\u226A\u0338":c.REL,"\u226B\u0338":c.REL,"\u226C":c.REL,"\u226D":c.REL,"\u226E":c.REL,"\u226F":c.REL,"\u2270":c.REL,"\u2271":c.REL,"\u2272":c.REL,"\u2273":c.REL,"\u2274":c.REL,"\u2275":c.REL,"\u2276":c.REL,"\u2277":c.REL,"\u2278":c.REL,"\u2279":c.REL,"\u227C":c.REL,"\u227D":c.REL,"\u227E":c.REL,"\u227F":c.REL,"\u227F\u0338":c.REL,"\u2280":c.REL,"\u2281":c.REL,"\u2282\u20D2":c.REL,"\u2283\u20D2":c.REL,"\u2284":c.REL,"\u2285":c.REL,"\u2288":c.REL,"\u2289":c.REL,"\u228A":c.REL,"\u228B":c.REL,"\u228C":c.BIN4,"\u228D":c.BIN4,"\u228F":c.REL,"\u228F\u0338":c.REL,"\u2290":c.REL,"\u2290\u0338":c.REL,"\u229A":c.BIN4,"\u229B":c.BIN4,"\u229C":c.BIN4,"\u229D":c.BIN4,"\u229E":c.BIN4,"\u229F":c.BIN4,"\u22A0":c.BIN4,"\u22A1":c.BIN4,"\u22A6":c.REL,"\u22A7":c.REL,"\u22A9":c.REL,"\u22AA":c.REL,"\u22AB":c.REL,"\u22AC":c.REL,"\u22AD":c.REL,"\u22AE":c.REL,"\u22AF":c.REL,"\u22B0":c.REL,"\u22B1":c.REL,"\u22B2":c.REL,"\u22B3":c.REL,"\u22B4":c.REL,"\u22B5":c.REL,"\u22B6":c.REL,"\u22B7":c.REL,"\u22B8":c.REL,"\u22B9":c.REL,"\u22BA":c.BIN4,"\u22BB":c.BIN4,"\u22BC":c.BIN4,"\u22BD":c.BIN4,"\u22BE":c.BIN3,"\u22BF":c.BIN3,"\u22C7":c.BIN4,"\u22C9":c.BIN4,"\u22CA":c.BIN4,"\u22CB":c.BIN4,"\u22CC":c.BIN4,"\u22CD":c.REL,"\u22CE":c.BIN4,"\u22CF":c.BIN4,"\u22D0":c.REL,"\u22D1":c.REL,"\u22D2":c.BIN4,"\u22D3":c.BIN4,"\u22D4":c.REL,"\u22D5":c.REL,"\u22D6":c.REL,"\u22D7":c.REL,"\u22D8":c.REL,"\u22D9":c.REL,"\u22DA":c.REL,"\u22DB":c.REL,"\u22DC":c.REL,"\u22DD":c.REL,"\u22DE":c.REL,"\u22DF":c.REL,"\u22E0":c.REL,"\u22E1":c.REL,"\u22E2":c.REL,"\u22E3":c.REL,"\u22E4":c.REL,"\u22E5":c.REL,"\u22E6":c.REL,"\u22E7":c.REL,"\u22E8":c.REL,"\u22E9":c.REL,"\u22EA":c.REL,"\u22EB":c.REL,"\u22EC":c.REL,"\u22ED":c.REL,"\u22F0":c.REL,"\u22F2":c.REL,"\u22F3":c.REL,"\u22F4":c.REL,"\u22F5":c.REL,"\u22F6":c.REL,"\u22F7":c.REL,"\u22F8":c.REL,"\u22F9":c.REL,"\u22FA":c.REL,"\u22FB":c.REL,"\u22FC":c.REL,"\u22FD":c.REL,"\u22FE":c.REL,"\u22FF":c.REL}}});MathJax.Ajax.loadComplete(a.optableDir+"/MathOperators.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/MiscMathSymbolsA.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"\u27E6":c.OPEN,"\u27EA":c.OPEN,"\u27EC":c.OPEN},postfix:{"\u27E7":c.CLOSE,"\u27EB":c.CLOSE,"\u27ED":c.CLOSE}}});MathJax.Ajax.loadComplete(a.optableDir+"/MiscMathSymbolsA.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/MiscMathSymbolsB.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"\u2980":[0,0,b.ORD,{fence:true,stretchy:true}],"\u2983":c.OPEN,"\u2985":c.OPEN,"\u2987":c.OPEN,"\u2989":c.OPEN,"\u298B":c.OPEN,"\u298D":c.OPEN,"\u298F":c.OPEN,"\u2991":c.OPEN,"\u2993":c.OPEN,"\u2995":c.OPEN,"\u2997":c.OPEN,"\u29FC":c.OPEN},postfix:{"\u2980":[0,0,b.ORD,{fence:true,stretchy:true}],"\u2984":c.CLOSE,"\u2986":c.CLOSE,"\u2988":c.CLOSE,"\u298A":c.CLOSE,"\u298C":c.CLOSE,"\u298E":c.CLOSE,"\u2990":c.CLOSE,"\u2992":c.CLOSE,"\u2994":c.CLOSE,"\u2996":c.CLOSE,"\u2998":c.CLOSE,"\u29FD":c.CLOSE},infix:{"\u2981":c.BIN3,"\u2982":c.BIN3,"\u2999":c.BIN3,"\u299A":c.BIN3,"\u299B":c.BIN3,"\u299C":c.BIN3,"\u299D":c.BIN3,"\u299E":c.BIN3,"\u299F":c.BIN3,"\u29A0":c.BIN3,"\u29A1":c.BIN3,"\u29A2":c.BIN3,"\u29A3":c.BIN3,"\u29A4":c.BIN3,"\u29A5":c.BIN3,"\u29A6":c.BIN3,"\u29A7":c.BIN3,"\u29A8":c.BIN3,"\u29A9":c.BIN3,"\u29AA":c.BIN3,"\u29AB":c.BIN3,"\u29AC":c.BIN3,"\u29AD":c.BIN3,"\u29AE":c.BIN3,"\u29AF":c.BIN3,"\u29B0":c.BIN3,"\u29B1":c.BIN3,"\u29B2":c.BIN3,"\u29B3":c.BIN3,"\u29B4":c.BIN3,"\u29B5":c.BIN3,"\u29B6":c.BIN4,"\u29B7":c.BIN4,"\u29B8":c.BIN4,"\u29B9":c.BIN4,"\u29BA":c.BIN4,"\u29BB":c.BIN4,"\u29BC":c.BIN4,"\u29BD":c.BIN4,"\u29BE":c.BIN4,"\u29BF":c.BIN4,"\u29C0":c.REL,"\u29C1":c.REL,"\u29C2":c.BIN3,"\u29C3":c.BIN3,"\u29C4":c.BIN4,"\u29C5":c.BIN4,"\u29C6":c.BIN4,"\u29C7":c.BIN4,"\u29C8":c.BIN4,"\u29C9":c.BIN3,"\u29CA":c.BIN3,"\u29CB":c.BIN3,"\u29CC":c.BIN3,"\u29CD":c.BIN3,"\u29CE":c.REL,"\u29CF":c.REL,"\u29CF\u0338":c.REL,"\u29D0":c.REL,"\u29D0\u0338":c.REL,"\u29D1":c.REL,"\u29D2":c.REL,"\u29D3":c.REL,"\u29D4":c.REL,"\u29D5":c.REL,"\u29D6":c.BIN4,"\u29D7":c.BIN4,"\u29D8":c.BIN3,"\u29D9":c.BIN3,"\u29DB":c.BIN3,"\u29DC":c.BIN3,"\u29DD":c.BIN3,"\u29DE":c.REL,"\u29DF":c.BIN3,"\u29E0":c.BIN3,"\u29E1":c.REL,"\u29E2":c.BIN4,"\u29E3":c.REL,"\u29E4":c.REL,"\u29E5":c.REL,"\u29E6":c.REL,"\u29E7":c.BIN3,"\u29E8":c.BIN3,"\u29E9":c.BIN3,"\u29EA":c.BIN3,"\u29EB":c.BIN3,"\u29EC":c.BIN3,"\u29ED":c.BIN3,"\u29EE":c.BIN3,"\u29EF":c.BIN3,"\u29F0":c.BIN3,"\u29F1":c.BIN3,"\u29F2":c.BIN3,"\u29F3":c.BIN3,"\u29F4":c.REL,"\u29F5":c.BIN4,"\u29F6":c.BIN4,"\u29F7":c.BIN4,"\u29F8":c.BIN3,"\u29F9":c.BIN3,"\u29FA":c.BIN3,"\u29FB":c.BIN3,"\u29FE":c.BIN4,"\u29FF":c.BIN4}}});MathJax.Ajax.loadComplete(a.optableDir+"/MiscMathSymbolsB.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/MiscSymbolsAndArrows.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{infix:{"\u2B45":c.RELSTRETCH,"\u2B46":c.RELSTRETCH}}});MathJax.Ajax.loadComplete(a.optableDir+"/MiscSymbolsAndArrows.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/MiscTechnical.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{postfix:{"\u23B4":c.WIDEACCENT,"\u23B5":c.WIDEACCENT,"\u23DC":c.WIDEACCENT,"\u23DD":c.WIDEACCENT,"\u23E0":c.WIDEACCENT,"\u23E1":c.WIDEACCENT}}});MathJax.Ajax.loadComplete(a.optableDir+"/MiscTechnical.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/SpacingModLetters.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{postfix:{"\u02CD":c.WIDEACCENT,"\u02DA":c.ACCENT,"\u02DD":c.ACCENT,"\u02F7":c.WIDEACCENT}}});MathJax.Ajax.loadComplete(a.optableDir+"/SpacingModLetters.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/SuppMathOperators.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{prefix:{"\u2A03":c.OP,"\u2A05":c.OP,"\u2A07":c.OP,"\u2A08":c.OP,"\u2A09":c.OP,"\u2A0A":c.OP,"\u2A0B":c.INTEGRAL2,"\u2A0C":c.INTEGRAL,"\u2A0D":c.INTEGRAL2,"\u2A0E":c.INTEGRAL2,"\u2A0F":c.INTEGRAL2,"\u2A10":c.OP,"\u2A11":c.OP,"\u2A12":c.OP,"\u2A13":c.OP,"\u2A14":c.OP,"\u2A15":c.INTEGRAL2,"\u2A16":c.INTEGRAL2,"\u2A17":c.INTEGRAL2,"\u2A18":c.INTEGRAL2,"\u2A19":c.INTEGRAL2,"\u2A1A":c.INTEGRAL2,"\u2A1B":c.INTEGRAL2,"\u2A1C":c.INTEGRAL2,"\u2AFC":c.OP,"\u2AFF":c.OP},infix:{"\u2A1D":c.BIN3,"\u2A1E":c.BIN3,"\u2A1F":c.BIN3,"\u2A20":c.BIN3,"\u2A21":c.BIN3,"\u2A22":c.BIN4,"\u2A23":c.BIN4,"\u2A24":c.BIN4,"\u2A25":c.BIN4,"\u2A26":c.BIN4,"\u2A27":c.BIN4,"\u2A28":c.BIN4,"\u2A29":c.BIN4,"\u2A2A":c.BIN4,"\u2A2B":c.BIN4,"\u2A2C":c.BIN4,"\u2A2D":c.BIN4,"\u2A2E":c.BIN4,"\u2A30":c.BIN4,"\u2A31":c.BIN4,"\u2A32":c.BIN4,"\u2A33":c.BIN4,"\u2A34":c.BIN4,"\u2A35":c.BIN4,"\u2A36":c.BIN4,"\u2A37":c.BIN4,"\u2A38":c.BIN4,"\u2A39":c.BIN4,"\u2A3A":c.BIN4,"\u2A3B":c.BIN4,"\u2A3C":c.BIN4,"\u2A3D":c.BIN4,"\u2A3E":c.BIN4,"\u2A40":c.BIN4,"\u2A41":c.BIN4,"\u2A42":c.BIN4,"\u2A43":c.BIN4,"\u2A44":c.BIN4,"\u2A45":c.BIN4,"\u2A46":c.BIN4,"\u2A47":c.BIN4,"\u2A48":c.BIN4,"\u2A49":c.BIN4,"\u2A4A":c.BIN4,"\u2A4B":c.BIN4,"\u2A4C":c.BIN4,"\u2A4D":c.BIN4,"\u2A4E":c.BIN4,"\u2A4F":c.BIN4,"\u2A50":c.BIN4,"\u2A51":c.BIN4,"\u2A52":c.BIN4,"\u2A53":c.BIN4,"\u2A54":c.BIN4,"\u2A55":c.BIN4,"\u2A56":c.BIN4,"\u2A57":c.BIN4,"\u2A58":c.BIN4,"\u2A59":c.REL,"\u2A5A":c.BIN4,"\u2A5B":c.BIN4,"\u2A5C":c.BIN4,"\u2A5D":c.BIN4,"\u2A5E":c.BIN4,"\u2A5F":c.BIN4,"\u2A60":c.BIN4,"\u2A61":c.BIN4,"\u2A62":c.BIN4,"\u2A63":c.BIN4,"\u2A64":c.BIN4,"\u2A65":c.BIN4,"\u2A66":c.REL,"\u2A67":c.REL,"\u2A68":c.REL,"\u2A69":c.REL,"\u2A6A":c.REL,"\u2A6B":c.REL,"\u2A6C":c.REL,"\u2A6D":c.REL,"\u2A6E":c.REL,"\u2A6F":c.REL,"\u2A70":c.REL,"\u2A71":c.BIN4,"\u2A72":c.BIN4,"\u2A73":c.REL,"\u2A74":c.REL,"\u2A75":c.REL,"\u2A76":c.REL,"\u2A77":c.REL,"\u2A78":c.REL,"\u2A79":c.REL,"\u2A7A":c.REL,"\u2A7B":c.REL,"\u2A7C":c.REL,"\u2A7D":c.REL,"\u2A7D\u0338":c.REL,"\u2A7E":c.REL,"\u2A7E\u0338":c.REL,"\u2A7F":c.REL,"\u2A80":c.REL,"\u2A81":c.REL,"\u2A82":c.REL,"\u2A83":c.REL,"\u2A84":c.REL,"\u2A85":c.REL,"\u2A86":c.REL,"\u2A87":c.REL,"\u2A88":c.REL,"\u2A89":c.REL,"\u2A8A":c.REL,"\u2A8B":c.REL,"\u2A8C":c.REL,"\u2A8D":c.REL,"\u2A8E":c.REL,"\u2A8F":c.REL,"\u2A90":c.REL,"\u2A91":c.REL,"\u2A92":c.REL,"\u2A93":c.REL,"\u2A94":c.REL,"\u2A95":c.REL,"\u2A96":c.REL,"\u2A97":c.REL,"\u2A98":c.REL,"\u2A99":c.REL,"\u2A9A":c.REL,"\u2A9B":c.REL,"\u2A9C":c.REL,"\u2A9D":c.REL,"\u2A9E":c.REL,"\u2A9F":c.REL,"\u2AA0":c.REL,"\u2AA1":c.REL,"\u2AA1\u0338":c.REL,"\u2AA2":c.REL,"\u2AA2\u0338":c.REL,"\u2AA3":c.REL,"\u2AA4":c.REL,"\u2AA5":c.REL,"\u2AA6":c.REL,"\u2AA7":c.REL,"\u2AA8":c.REL,"\u2AA9":c.REL,"\u2AAA":c.REL,"\u2AAB":c.REL,"\u2AAC":c.REL,"\u2AAD":c.REL,"\u2AAE":c.REL,"\u2AAF\u0338":c.REL,"\u2AB0\u0338":c.REL,"\u2AB1":c.REL,"\u2AB2":c.REL,"\u2AB3":c.REL,"\u2AB4":c.REL,"\u2AB5":c.REL,"\u2AB6":c.REL,"\u2AB7":c.REL,"\u2AB8":c.REL,"\u2AB9":c.REL,"\u2ABA":c.REL,"\u2ABB":c.REL,"\u2ABC":c.REL,"\u2ABD":c.REL,"\u2ABE":c.REL,"\u2ABF":c.REL,"\u2AC0":c.REL,"\u2AC1":c.REL,"\u2AC2":c.REL,"\u2AC3":c.REL,"\u2AC4":c.REL,"\u2AC5":c.REL,"\u2AC6":c.REL,"\u2AC7":c.REL,"\u2AC8":c.REL,"\u2AC9":c.REL,"\u2ACA":c.REL,"\u2ACB":c.REL,"\u2ACC":c.REL,"\u2ACD":c.REL,"\u2ACE":c.REL,"\u2ACF":c.REL,"\u2AD0":c.REL,"\u2AD1":c.REL,"\u2AD2":c.REL,"\u2AD3":c.REL,"\u2AD4":c.REL,"\u2AD5":c.REL,"\u2AD6":c.REL,"\u2AD7":c.REL,"\u2AD8":c.REL,"\u2AD9":c.REL,"\u2ADA":c.REL,"\u2ADB":c.REL,"\u2ADC":c.REL,"\u2ADD":c.REL,"\u2ADE":c.REL,"\u2ADF":c.REL,"\u2AE0":c.REL,"\u2AE1":c.REL,"\u2AE2":c.REL,"\u2AE3":c.REL,"\u2AE4":c.REL,"\u2AE5":c.REL,"\u2AE6":c.REL,"\u2AE7":c.REL,"\u2AE8":c.REL,"\u2AE9":c.REL,"\u2AEA":c.REL,"\u2AEB":c.REL,"\u2AEC":c.REL,"\u2AED":c.REL,"\u2AEE":c.REL,"\u2AEF":c.REL,"\u2AF0":c.REL,"\u2AF1":c.REL,"\u2AF2":c.REL,"\u2AF3":c.REL,"\u2AF4":c.BIN4,"\u2AF5":c.BIN4,"\u2AF6":c.BIN4,"\u2AF7":c.REL,"\u2AF8":c.REL,"\u2AF9":c.REL,"\u2AFA":c.REL,"\u2AFB":c.BIN4,"\u2AFD":c.BIN4,"\u2AFE":c.BIN3}}});MathJax.Ajax.loadComplete(a.optableDir+"/SuppMathOperators.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/SupplementalArrowsA.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{infix:{"\u27F0":c.RELSTRETCH,"\u27F1":c.RELSTRETCH,"\u27FB":c.WIDEREL,"\u27FD":c.WIDEREL,"\u27FE":c.WIDEREL,"\u27FF":c.WIDEREL}}});MathJax.Ajax.loadComplete(a.optableDir+"/SupplementalArrowsA.js")})(MathJax.ElementJax.mml);

View file

@ -0,0 +1,19 @@
/*
* /MathJax-v2/jax/element/mml/optable/SupplementalArrowsB.js
*
* Copyright (c) 2009-2018 The MathJax Consortium
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function(a){var c=a.mo.OPTYPES;var b=a.TEXCLASS;MathJax.Hub.Insert(a.mo.prototype,{OPTABLE:{infix:{"\u2900":c.RELACCENT,"\u2901":c.RELACCENT,"\u2902":c.RELACCENT,"\u2903":c.RELACCENT,"\u2904":c.RELACCENT,"\u2905":c.RELACCENT,"\u2906":c.RELACCENT,"\u2907":c.RELACCENT,"\u2908":c.REL,"\u2909":c.REL,"\u290A":c.RELSTRETCH,"\u290B":c.RELSTRETCH,"\u290C":c.WIDEREL,"\u290D":c.WIDEREL,"\u290E":c.WIDEREL,"\u290F":c.WIDEREL,"\u2910":c.WIDEREL,"\u2911":c.RELACCENT,"\u2912":c.RELSTRETCH,"\u2913":c.RELSTRETCH,"\u2914":c.RELACCENT,"\u2915":c.RELACCENT,"\u2916":c.RELACCENT,"\u2917":c.RELACCENT,"\u2918":c.RELACCENT,"\u2919":c.RELACCENT,"\u291A":c.RELACCENT,"\u291B":c.RELACCENT,"\u291C":c.RELACCENT,"\u291D":c.RELACCENT,"\u291E":c.RELACCENT,"\u291F":c.RELACCENT,"\u2920":c.RELACCENT,"\u2921":c.RELSTRETCH,"\u2922":c.RELSTRETCH,"\u2923":c.REL,"\u2924":c.REL,"\u2925":c.REL,"\u2926":c.REL,"\u2927":c.REL,"\u2928":c.REL,"\u2929":c.REL,"\u292A":c.REL,"\u292B":c.REL,"\u292C":c.REL,"\u292D":c.REL,"\u292E":c.REL,"\u292F":c.REL,"\u2930":c.REL,"\u2931":c.REL,"\u2932":c.REL,"\u2933":c.RELACCENT,"\u2934":c.REL,"\u2935":c.REL,"\u2936":c.REL,"\u2937":c.REL,"\u2938":c.REL,"\u2939":c.REL,"\u293A":c.RELACCENT,"\u293B":c.RELACCENT,"\u293C":c.RELACCENT,"\u293D":c.RELACCENT,"\u293E":c.REL,"\u293F":c.REL,"\u2940":c.REL,"\u2941":c.REL,"\u2942":c.RELACCENT,"\u2943":c.RELACCENT,"\u2944":c.RELACCENT,"\u2945":c.RELACCENT,"\u2946":c.RELACCENT,"\u2947":c.RELACCENT,"\u2948":c.RELACCENT,"\u2949":c.REL,"\u294A":c.RELACCENT,"\u294B":c.RELACCENT,"\u294C":c.REL,"\u294D":c.REL,"\u294E":c.WIDEREL,"\u294F":c.RELSTRETCH,"\u2950":c.WIDEREL,"\u2951":c.RELSTRETCH,"\u2952":c.WIDEREL,"\u2953":c.WIDEREL,"\u2954":c.RELSTRETCH,"\u2955":c.RELSTRETCH,"\u2956":c.RELSTRETCH,"\u2957":c.RELSTRETCH,"\u2958":c.RELSTRETCH,"\u2959":c.RELSTRETCH,"\u295A":c.WIDEREL,"\u295B":c.WIDEREL,"\u295C":c.RELSTRETCH,"\u295D":c.RELSTRETCH,"\u295E":c.WIDEREL,"\u295F":c.WIDEREL,"\u2960":c.RELSTRETCH,"\u2961":c.RELSTRETCH,"\u2962":c.RELACCENT,"\u2963":c.REL,"\u2964":c.RELACCENT,"\u2965":c.REL,"\u2966":c.RELACCENT,"\u2967":c.RELACCENT,"\u2968":c.RELACCENT,"\u2969":c.RELACCENT,"\u296A":c.RELACCENT,"\u296B":c.RELACCENT,"\u296C":c.RELACCENT,"\u296D":c.RELACCENT,"\u296E":c.RELSTRETCH,"\u296F":c.RELSTRETCH,"\u2970":c.RELACCENT,"\u2971":c.RELACCENT,"\u2972":c.RELACCENT,"\u2973":c.RELACCENT,"\u2974":c.RELACCENT,"\u2975":c.RELACCENT,"\u2976":c.RELACCENT,"\u2977":c.RELACCENT,"\u2978":c.RELACCENT,"\u2979":c.RELACCENT,"\u297A":c.RELACCENT,"\u297B":c.RELACCENT,"\u297C":c.RELACCENT,"\u297D":c.RELACCENT,"\u297E":c.REL,"\u297F":c.REL}}});MathJax.Ajax.loadComplete(a.optableDir+"/SupplementalArrowsB.js")})(MathJax.ElementJax.mml);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -3,6 +3,7 @@ Thomas Kolb DL5TKL
0.1, 2024-04-25 0.1, 2024-04-25
:toc: :toc:
:stem: :stem:
:webfonts!:
// SPDX-License-Identifier: CC-BY-SA-4.0 // SPDX-License-Identifier: CC-BY-SA-4.0
// List of contributors is at the end of the document. // List of contributors is at the end of the document.
@ -253,6 +254,157 @@ They therefore do not have a _TX sequence number_; this field is reserved and mu
The _RX sequence number_ and all other header fields are used as usual. The _RX sequence number_ and all other header fields are used as usual.
==== Connection Management
Connection Management Frames control the Layer 2 connection between a client and the digipeater.
They are especially important for connect and disconnect procedures.
Connection Management Frames have at least one byte in the data field: the type of the management packet.
The following types are defined:
[cols="1,2,5", options="header"]
.Connection Management Types
|===
|Type Indicator
|Sent by
|Description
|`0x00`
|Digipeater
|Beacon
|`0x01`
|Client
|Connection Request
|`0x02`
|Digipeater
|Connection Parameters
|`0x03`
|Digipeater
|Connection Reset
|`0x04`
|Digipeater
|Disconnect Request
|`0x05`
|Client
|Disconnect
|===
===== Beacons
Beacons are sent periodically by the digipeater to show its presence to potential new clients.
The layer 2 header is filled as follows:
- Message Type: `001` (Connection Management)
- TX Request: `1`
- Source Address: the digipeaters HAM-64 address
- Destination Address: the HAM-64 broadcast address
- TX sequence number: reserved, always 0
- RX sequence number: reserved, always 0
The message contains exactly 1 data byte: `0x00` to indicate that this is a Beacon packet.
A Beacon packet shall always be sent as the last or only packet in a burst.
After a Beacon is sent by the digipeater it listens for a short time for Connection Requests from new clients.
A new client that intends to connect shall send the Connection Request as soon as possible after the beacon transmission ends.
===== Connection Request
A Connection Request is sent by a client that intends to connect to a digipeater.
The layer 2 header is filled as follows:
- Message Type: `001` (Connection Management)
- TX Request: `1`
- Source Address: the new clients HAM-64 address
- Destination Address: the digipeaters HAM-64 address
- TX sequence number: reserved, always 0
- RX sequence number: reserved, always 0
The message contains exactly 1 data byte: `0x01` to indicate that this is a Connection Request packet.
A Connection Request is always a response to a Beacon packet.
It shall be the only packet in a burst.
When the digipeater receives a Connection Request it has two options for a response:
. Accept the connection by sending a Connection Parameters packet to the client in the next burst.
. Reject the connection by sending a Connection Reset packet to the client in the next burst.
===== Connection Parameters
The Connection Parameters packet has two functions: it indicates to a client that its Connection Request was accepted and tells it how to configure its network stack.
This is the first packet in the regular Go-back-N data flow.
The layer 2 header is filled as follows:
- Message Type: `001` (Connection Management)
- TX Request: `1`
- Source Address: the digipeaters HAM-64 address
- Destination Address: the new clients HAM-64 address
- TX sequence number: 0 (the first packet)
- RX sequence number: 0 (no packets received yet)
The type indicator is `0x02`.
After the type indicator follow one or more configuration blocks.
Each block consists of three data fields:
. Configuration Type: 1 byte
. Configuration Data Length: 1 byte
. Configuration data: variable number of bytes as indicated by Configuration Data Length
The following Configuration Types are defined:
[cols="1,1,5", options="header"]
.Configuration Types
|===
|Type Indicator
|Length
|Description
|`0x00`
|16 Byte
|IPv6 address
|`0x01`
|16 Byte
|IPv6 gateway
|`0x02`
|16 Byte
|IPv6 DNS server (may appear multiple times)
|`0x03` - `0x07`
|-
|_reserved_
|`0x08`
|4 Byte
|IPv4 address
|`0x09`
|4 Byte
|IPv4 gateway
|`0x0A`
|4 Byte
|IPv4 DNS server (may appear multiple times)
|`0x0B` - `0xFF`
|-
|_reserved_
|===
If the client receives an unknown Configuration Type the corresponding block shall be skipped.
The remaining blocks shall be parsed as usual.
=== Ideas === Ideas
To be defined: To be defined:
@ -267,108 +419,109 @@ To be defined:
==== Connection Establishment ==== Connection Establishment
[mermaid,format=svg,svg-type=interactive] [msc,format=svg,svg-type=interactive, scale=1.4]
.Connection establishment .Connection establishment
.... ....
sequenceDiagram msc {
participant digi as Digipeater digi [label="Digipeater"],
participant client as Client client [label="Client"];
digi -->> client: Beacon (Broadcast) digi -> client [label="Beacon (Broadcast)"];
digi -->> client: Beacon (Broadcast) digi -> client [label="Beacon (Broadcast)"];
digi -->> client: Beacon (Broadcast)
Note over client: Decides to connect client box client [label="Decides to connect"];
...;
client ->> digi: Connection Request digi -> client [label="Beacon (Broadcast)"];
digi <- client [label="Connection Request"];
alt Connection accepted --- [label="Alternative 1: Connection is accepted"];
digi ->> client: Connection Parameters digi -> client [label="Connection Parameters"];
client ->> digi: Connection Acknowledgement digi <- client [label="Connection Acknowledgement"];
Note over digi,client: Connection established digi box client [label="Connection established"];
else Connection rejected
digi ->> client: Connection Refusal --- [label="Alternative 2: Connection is rejected"];
end digi -> client [label="Connection Reset"];
}
.... ....
==== Communication during a Connection ==== Communication during a Connection
[mermaid,format=svg,svg-type=interactive] [msc,format=svg,svg-type=interactive, scale=1.4]
.In-connection communication .In-connection communication
.... ....
sequenceDiagram msc {
participant digi as Digipeater digi [label="Digipeater"],
participant client as Client client [label="Client"];
Note over digi,client: Connection established --- [label="Connection established"];
digi => client [label="Up to 14 packets without Transmission Request"];
digi -> client [label="Packet with Transmission Request"];
loop For up to 14 packets digi box digi [label="Start timeout timer"];
digi ->> client: Packet without Transmission Request
end
digi ->> client: Packet with Transmission Request --- [label="Alternative 1: Normal reply"];
Note over digi: Start timeout timer digi <= client [label="Up to 14 packets without Transmission Request"];
digi <- client [label="Packet with Transmission Request"];
alt Normal reply --- [label="Alternative 2: Partial reply"];
loop For up to 14 packets
client ->> digi: Packet without Transmission Request
end
client ->> digi: Packet with Transmission Request digi <= client [label="Up to 14 packets without Transmission Request"];
else Partial reply digi x- client [label="Packet with Transmission Request"];
loop For up to 14 packets ...;
client ->> digi: Packet without Transmission Request digi rbox digi [label="Timeout expired"];
end
client --x digi: Packet with Transmission Request (lost) --- [label="Alternative 3: No reply"];
Note over digi: Timeout expired digi x- client [label="Packets from client"];
else No reply ...;
Note over digi: Timeout expired digi rbox digi [label="Timeout expired"];
end
Note over digi: Query next client --- [label="Finally"];
digi box digi [label="Query next client"];
}
.... ....
==== Connection Shutdown ==== Connection Shutdown
===== Client-initiated ===== Client-initiated
[mermaid,format=svg,svg-type=interactive] [msc,format=svg,svg-type=interactive, scale=1.4]
.Client-initiated shutdown .Client-initiated shutdown
.... ....
sequenceDiagram msc {
participant digi as Digipeater digi [label="Digipeater"],
participant client as Client client [label="Client"];
Note over digi,client: Connection established --- [label="Connection established"];
client box client [label="Decides to disconnect"];
...;
digi -> client [label="Transmission request"];
digi <- client [label="Disconnect"];
Note over client: Decides to disconnect --- [label="Connection closed"];
}
digi ->> client: Transmission Request
client ->> digi: Disconnect
Note over digi,client: Connection closed
.... ....
===== Digipeater-initiated ===== Digipeater-initiated
[mermaid,format=svg,svg-type=interactive] [msc,format=svg,svg-type=interactive, scale=1.4]
.Digipeater-initiated shutdown .Digipeater-initiated shutdown
.... ....
sequenceDiagram msc {
participant digi as Digipeater digi [label="Digipeater"],
participant client as Client client [label="Client"];
Note over digi,client: Connection established --- [label="Connection established"];
digi box digi [label="Decides to disconnect Client"];
Note over digi: Decides to disconnect client digi -> client [label="Disconnect request"];
digi <- client [label="Disconnect"];
digi ->> client: Disconnect Request --- [label="Connection closed"];
client ->> digi: Disconnect }
Note over digi,client: Connection closed
.... ....
== Higher Layer Protocols == Higher Layer Protocols

View file

@ -53,6 +53,8 @@ set(sources
src/layer2/layer2_rx.h src/layer2/layer2_rx.h
src/layer2/ham64.c src/layer2/ham64.c
src/layer2/ham64.h src/layer2/ham64.h
src/layer2/connection.c
src/layer2/connection.h
src/sdr/sdr.c src/sdr/sdr.c
src/sdr/sdr.h src/sdr/sdr.h
) )

22
impl/README.md Normal file
View file

@ -0,0 +1,22 @@
# Hamnet70 Implementation
This directory contains an implementation of the Hamnet70 protocol.
Before you can compile and use this code, some additional steps are necessary:
1. Copy `src/config.h.template` to `src/config.h` and set the following variables:
- `MY_CALL`: the station call sign (i.e. your amateur radio call sign).
This will be encoded into the address fields of outgoing packets.
2. Install dependencies:
- _libliquid_ compiled with _libfec_ support
- _libfec_
- _fftw3_
- _libhackrf_
After everything is prepared, compile the code using `./make.sh`.
Parameters to this script are forwarded to `make` so you can speed things up a little with `./make.sh -j4` (on a CPU with 4 threads).
When compiled, you have two options for running Hamnet70:
1. In digipeater (base station) mode: `build/hamnet70 -c`. This will broadcast beacons and wait for clients to connect.
2. In client mode: `build/hamnet70`. This will wait for a beacon to arrive and connect to it.

View file

@ -9,6 +9,10 @@
#include <liquid/liquid.h> #include <liquid/liquid.h>
/*** LAYER 2 CONFIG ***/
#define MY_CALL undefined // define MY_CALL to your call sign as a C string, e.g. "DL5TKL"
/*** TIMING CONFIG ***/ /*** TIMING CONFIG ***/
#define TX_SWITCH_BACKOFF_PREAMBLE_MS 42 // only relevant if packet cannot be decoded (maximum packet duration) #define TX_SWITCH_BACKOFF_PREAMBLE_MS 42 // only relevant if packet cannot be decoded (maximum packet duration)

View file

@ -143,7 +143,7 @@ float freq_est_in_rampup(const float complex *recv, size_t n, float *final_phase
// rotate every second symbol by 180°. As a plausibility check that we are // rotate every second symbol by 180°. As a plausibility check that we are
// in fact inside the ramp-up, we verify that there is no phase rotation by // in fact inside the ramp-up, we verify that there is no phase rotation by
// more than 90° between the symbols. // more than 60° between the symbols.
for(size_t i = 0; i < n; i++) { for(size_t i = 0; i < n; i++) {
if((i % 2) == 0) { if((i % 2) == 0) {
rotated[i] = recv[i]; rotated[i] = recv[i];
@ -154,7 +154,7 @@ float freq_est_in_rampup(const float complex *recv, size_t n, float *final_phase
float phase = cargf(rotated[i]); float phase = cargf(rotated[i]);
if(i > 0) { if(i > 0) {
float phase_delta = phase - prev_phase; float phase_delta = phase - prev_phase;
if(fabsf(phase_delta) > 3.14159f/2.0f) { if(fabsf(phase_delta) > 3.14159f/3.0f) {
// abort because we either have the wrong signal or too much noise. // abort because we either have the wrong signal or too much noise.
if(final_phase) { if(final_phase) {
*final_phase = 0.0f; *final_phase = 0.0f;

View file

@ -206,7 +206,7 @@ static enum squelch_state_t update_and_check_squelch(layer1_rx_t *rx, unsigned i
// Adjustment value is in dB. // Adjustment value is in dB.
rx->noise_floor_level += 1e-4f; rx->noise_floor_level += 1e-4f;
} }
agc_crcf_squelch_set_threshold(rx->agc, rx->noise_floor_level + 10.0f); // in dB agc_crcf_squelch_set_threshold(rx->agc, rx->noise_floor_level + 6.0f); // in dB
} }
switch(agc_crcf_squelch_get_status(rx->agc)) { switch(agc_crcf_squelch_get_status(rx->agc)) {
@ -265,6 +265,8 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
static float complex samples2dump_s[8192]; static float complex samples2dump_s[8192];
static size_t nsamples2dump_s = 0; static size_t nsamples2dump_s = 0;
static float evm;
// cache configuration flags // cache configuration flags
bool is_central_node = options_is_flag_set(OPTIONS_FLAG_IS_CENTRAL_NODE); bool is_central_node = options_is_flag_set(OPTIONS_FLAG_IS_CENTRAL_NODE);
@ -344,6 +346,7 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
// go on with decoding the header // go on with decoding the header
rx->state = RX_STATE_HEADER; rx->state = RX_STATE_HEADER;
symbol_counter = 0; symbol_counter = 0;
evm = 0.0f;
} }
break; break;
@ -361,6 +364,7 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
LOG(LVL_DUMP, "@%zu: Sym: %d; Phase error: %f %s", rx->sample_index, sym_demod, phase_error, LOG(LVL_DUMP, "@%zu: Sym: %d; Phase error: %f %s", rx->sample_index, sym_demod, phase_error,
(fabs(phase_error) > 0.3) ? "!!!" : ""); (fabs(phase_error) > 0.3) ? "!!!" : "");
evm += modem_get_demodulator_evm(rx->hdr_demod);
update_nco_pll(rx->carrier_fine_nco, phase_error, PLL_BW_HEADER); update_nco_pll(rx->carrier_fine_nco, phase_error, PLL_BW_HEADER);
@ -378,7 +382,7 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
// store debug info about the header // store debug info about the header
rx->packet_debug_info.noise_floor_level = rx->noise_floor_level; rx->packet_debug_info.noise_floor_level = rx->noise_floor_level;
rx->packet_debug_info.header_rssi = agc_crcf_get_rssi(rx->agc); rx->packet_debug_info.header_rssi = agc_crcf_get_rssi(rx->agc);
rx->packet_debug_info.header_evm = -1e38f; // FIXME rx->packet_debug_info.header_evm = evm / symbol_counter;
ERR_CHECK_LIQUID(liquid_repack_bytes( ERR_CHECK_LIQUID(liquid_repack_bytes(
symbols_int, modem_get_bps(rx->hdr_demod), rx->hdr_len_symbols, symbols_int, modem_get_bps(rx->hdr_demod), rx->hdr_len_symbols,
@ -433,6 +437,7 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
rx->state = RX_STATE_DATA; rx->state = RX_STATE_DATA;
symbol_counter = 0; symbol_counter = 0;
evm = 0.0f;
} }
break; break;
@ -453,6 +458,8 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
float phase_error = modem_get_demodulator_phase_error(rx->payload_demod); float phase_error = modem_get_demodulator_phase_error(rx->payload_demod);
//LOG(LVL_DEBUG, "@%zu: Sym: %d; Phase error: %f", rx->sample_index, sym_demod, phase_error); //LOG(LVL_DEBUG, "@%zu: Sym: %d; Phase error: %f", rx->sample_index, sym_demod, phase_error);
evm += modem_get_demodulator_evm(rx->payload_demod);
update_nco_pll(rx->carrier_fine_nco, phase_error, PLL_BW_DATA); update_nco_pll(rx->carrier_fine_nco, phase_error, PLL_BW_DATA);
symbols_int[symbol_counter] = sym_demod; symbols_int[symbol_counter] = sym_demod;
@ -471,7 +478,7 @@ result_t layer1_rx_process(layer1_rx_t *rx, const float complex *samples, size_t
// store debug info about the data // store debug info about the data
rx->packet_debug_info.data_rssi = agc_crcf_get_rssi(rx->agc); rx->packet_debug_info.data_rssi = agc_crcf_get_rssi(rx->agc);
rx->packet_debug_info.data_evm = -1e38f; // FIXME rx->packet_debug_info.data_evm = evm / symbol_counter;
// deinterleave the message symbols // deinterleave the message symbols
uint8_t symbols_int_deinterleaved[rx->payload_len_symbols]; uint8_t symbols_int_deinterleaved[rx->payload_len_symbols];

View file

@ -0,0 +1,375 @@
#include <string.h>
#include <assert.h>
#include "connection.h"
#include "config.h"
#include "layer2/ham64.h"
#include "results.h"
#define SEQ_NR_MASK 0xF
result_t connection_init(connection_ctx_t *ctx, const ham64_t *my_addr, const ham64_t *peer_addr)
{
ctx->last_acked_seq = 0;
ctx->next_expected_seq = 0;
packet_queue_init(&ctx->packet_queue);
ctx->next_packet_index = 0;
ctx->next_seq_nr = 0;
ctx->my_addr = *my_addr;
ctx->peer_addr = *peer_addr;
ctx->conn_state = CONN_STATE_INITIALIZED;
return OK;
}
void connection_destroy(connection_ctx_t *ctx)
{
if(ctx->conn_state == CONN_STATE_UNINITIALIZED) {
return;
}
ctx->conn_state = CONN_STATE_UNINITIALIZED;
packet_queue_destroy(&ctx->packet_queue);
}
result_t connection_handle_packet(connection_ctx_t *ctx, const uint8_t *buf, size_t buf_len)
{
// check the connection state
switch(ctx->conn_state) {
case CONN_STATE_UNINITIALIZED:
case CONN_STATE_INITIALIZED:
case CONN_STATE_CLOSED:
LOG(LVL_ERR, "Trying to pass packet to connection in state %u", ctx->conn_state);
return ERR_INVALID_STATE;
case CONN_STATE_CONNECTING:
case CONN_STATE_ESTABLISHED:
// in these states, packets can be handled
break;
}
// check the CRC
size_t packet_size = buf_len - crc_sizeof_key(PAYLOAD_CRC_SCHEME);
if(!crc_check_key(PAYLOAD_CRC_SCHEME, (unsigned char*)buf, packet_size)) {
LOG(LVL_ERR, "payload CRC check failed!");
return ERR_INTEGRITY;
}
// decode the header
layer2_packet_header_t header;
if(!layer2_decode_packet_header(buf, buf_len, &header)) {
LOG(LVL_ERR, "Header could not be decoded!");
return ERR_INTEGRITY;
}
// check if the packet really should be handled by us
if(!ham64_is_equal(&header.src_addr, &ctx->peer_addr)) {
char fmt_src_addr[HAM64_FMT_MAX_LEN];
char fmt_peer_addr[HAM64_FMT_MAX_LEN];
ham64_format(&header.src_addr, fmt_src_addr);
ham64_format(&ctx->peer_addr, fmt_peer_addr);
LOG(LVL_ERR, "Packet has the wrong source address: got %s, expected %s",
fmt_src_addr, fmt_peer_addr);
return ERR_INVALID_ADDRESS;
}
if(!ham64_is_equal(&header.dst_addr, &ctx->my_addr)) {
char fmt_dst_addr[HAM64_FMT_MAX_LEN];
char fmt_my_addr[HAM64_FMT_MAX_LEN];
ham64_format(&header.dst_addr, fmt_dst_addr);
ham64_format(&ctx->my_addr, fmt_my_addr);
LOG(LVL_ERR, "Packet has the wrong destination address: got %s, expected %s",
fmt_dst_addr, fmt_my_addr);
return ERR_INVALID_ADDRESS;
}
LOG(LVL_DEBUG, "Handling %s packet with rx_seq_nr %u, tx_seq_nr %u.",
layer2_msg_type_to_string(header.msg_type), header.rx_seq_nr, header.tx_seq_nr);
ctx->last_acked_seq = header.rx_seq_nr;
switch(header.msg_type) {
case L2_MSG_TYPE_EMPTY:
LOG(LVL_DEBUG, "Empty packet: accepted ACK for %u.", ctx->last_acked_seq);
return OK; // do not ACK and call back
case L2_MSG_TYPE_CONN_MGMT:
case L2_MSG_TYPE_CONNECTIONLESS:
LOG(LVL_WARN, "Message type %s is not implemented yet.", layer2_msg_type_to_string(header.msg_type));
return OK;
case L2_MSG_TYPE_DATA:
break;
default:
LOG(LVL_ERR, "Invalid message type %d.", header.msg_type);
return ERR_INVALID_STATE;
}
if(ctx->next_expected_seq != header.tx_seq_nr) {
LOG(LVL_ERR, "Expected sequence number %u, received %u.", ctx->next_expected_seq, header.tx_seq_nr);
return ERR_SEQUENCE;
}
ctx->next_expected_seq++;
ctx->next_expected_seq &= 0xF;
LOG(LVL_INFO, "Received ACK for seq_nr %u in packet seq_nr %u.", header.rx_seq_nr, header.tx_seq_nr);
// handle the acknowledgement internally
connection_handle_ack(ctx, header.rx_seq_nr);
size_t header_size = layer2_get_encoded_header_size(&header);
// extract the payload and forward it to the tun device
const uint8_t *payload = buf + header_size;
size_t payload_len = packet_size - header_size;
ctx->data_cb(ctx, payload, payload_len);
return OK;
}
uint8_t connection_get_next_expected_seq(const connection_ctx_t *ctx)
{
return ctx->next_expected_seq;
}
uint8_t connection_get_last_acked_seq(const connection_ctx_t *ctx)
{
return ctx->last_acked_seq;
}
result_t connection_enqueue_packet(connection_ctx_t *ctx, uint8_t *buf, size_t buf_len)
{
// check the connection state
switch(ctx->conn_state) {
case CONN_STATE_UNINITIALIZED:
case CONN_STATE_INITIALIZED:
case CONN_STATE_CLOSED:
case CONN_STATE_CONNECTING:
LOG(LVL_ERR, "Trying to enqueue packet in inactive state %u", ctx->conn_state);
return ERR_INVALID_STATE;
case CONN_STATE_ESTABLISHED:
// in these states, packets can be handled
break;
}
layer2_packet_header_t header;
if(packet_queue_get_free_space(&ctx->packet_queue) == 0) {
return ERR_NO_MEM;
}
header.dst_addr = ctx->peer_addr;
header.src_addr = ctx->my_addr;
header.msg_type = L2_MSG_TYPE_DATA;
header.rx_seq_nr = 0; // will be filled in layer2_tx_encode_next_packet()
header.tx_request = 0;
header.tx_seq_nr = ctx->next_seq_nr;
// create a persistent copy of the packet data.
// TODO: possibly this copy operation can be removed by passing a malloc'd buffer in.
uint8_t *packetbuf = malloc(buf_len);
if(!packetbuf) {
LOG(LVL_ERR, "malloc failed.");
return ERR_NO_MEM;
}
memcpy(packetbuf, buf, buf_len);
packet_queue_add(&ctx->packet_queue, &header, packetbuf, buf_len);
LOG(LVL_INFO, "Added packet tx_seq %u to queue -> %zu entries",
header.tx_seq_nr, packet_queue_get_used_space(&ctx->packet_queue));
ctx->next_seq_nr++;
ctx->next_seq_nr &= SEQ_NR_MASK;
return OK;
}
result_t connection_add_empty_packet(connection_ctx_t *ctx, bool tx_request)
{
// check the connection state
switch(ctx->conn_state) {
case CONN_STATE_UNINITIALIZED:
case CONN_STATE_INITIALIZED:
case CONN_STATE_CLOSED:
case CONN_STATE_CONNECTING:
LOG(LVL_ERR, "Trying to add empty packet in inactive state %u", ctx->conn_state);
return ERR_INVALID_STATE;
case CONN_STATE_ESTABLISHED:
// in these states, packets can be handled
break;
}
layer2_packet_header_t header;
header.dst_addr.addr[0] = 0xFFFF;
header.dst_addr.length = 1;
header.src_addr.addr[0] = 0x0001;
header.src_addr.length = 1;
header.msg_type = L2_MSG_TYPE_EMPTY;
header.rx_seq_nr = 0; // will be filled in layer2_tx_encode_next_packet()
header.tx_seq_nr = 0; // not used in empty packets
header.tx_request = tx_request;
if (!packet_queue_add(&ctx->packet_queue, &header, NULL, 0)) {
return ERR_NO_MEM;
}
return OK;
}
size_t connection_encode_next_packet(connection_ctx_t *ctx, uint8_t ack_seq_nr, uint8_t *buf, size_t buf_len)
{
// check the connection state
switch(ctx->conn_state) {
case CONN_STATE_UNINITIALIZED:
case CONN_STATE_INITIALIZED:
case CONN_STATE_CLOSED:
LOG(LVL_ERR, "Trying to encode packet in inactive state %u", ctx->conn_state);
return ERR_INVALID_STATE;
case CONN_STATE_CONNECTING:
case CONN_STATE_ESTABLISHED:
// in these states, packets may be present for transmission
break;
}
const packet_queue_entry_t *entry = packet_queue_get(&ctx->packet_queue, ctx->next_packet_index);
if(!entry) {
// no more entries
return 0;
}
unsigned int crc_size = crc_sizeof_key(PAYLOAD_CRC_SCHEME);
assert(buf_len >= LAYER2_PACKET_HEADER_ENCODED_SIZE_MAX + crc_size + entry->data_len);
layer2_packet_header_t header = entry->header;
header.rx_seq_nr = ack_seq_nr;
// encode the header
LOG(LVL_DEBUG, "Encoding packet with rx_seq_nr %u, tx_seq_nr %u.", header.rx_seq_nr, header.tx_seq_nr);
size_t packet_size = layer2_encode_packet_header(&header, buf);
// add the payload data
if(entry->data) {
memcpy(buf + packet_size, entry->data, entry->data_len);
}
packet_size += entry->data_len;
// calculate CRC of everything and append it to the packet
crc_append_key(PAYLOAD_CRC_SCHEME, buf, packet_size);
packet_size += crc_size;
ctx->next_packet_index++;
return packet_size;
}
void connection_restart_tx(connection_ctx_t *ctx)
{
ctx->next_packet_index = 0;
}
void connection_tx_clean_empty_packet(connection_ctx_t *ctx)
{
assert(ctx->conn_state != CONN_STATE_UNINITIALIZED);
const packet_queue_entry_t *entry = packet_queue_get(&ctx->packet_queue, 0);
if(entry && entry->header.msg_type == L2_MSG_TYPE_EMPTY) {
packet_queue_delete(&ctx->packet_queue, 1);
if(ctx->next_packet_index > 0) {
ctx->next_packet_index--;
}
}
}
void connection_handle_ack(connection_ctx_t *ctx, uint8_t acked_seq)
{
// check the connection state
switch(ctx->conn_state) {
case CONN_STATE_UNINITIALIZED:
case CONN_STATE_INITIALIZED:
case CONN_STATE_CLOSED:
case CONN_STATE_CONNECTING:
LOG(LVL_ERR, "Trying to call connection_handle_ack() in inactive state %u", ctx->conn_state);
return;
case CONN_STATE_ESTABLISHED:
// in these states, packets may be present for transmission
break;
}
ctx->next_packet_index = 0;
size_t packets_to_remove = 0;
size_t packets_available = packet_queue_get_used_space(&ctx->packet_queue);
for(size_t i = 0; i < packets_available; i++) {
const packet_queue_entry_t *entry = packet_queue_get(&ctx->packet_queue, i);
if(entry->header.tx_seq_nr == acked_seq) {
break;
}
packets_to_remove++;
}
packet_queue_delete(&ctx->packet_queue, packets_to_remove);
packets_available = packet_queue_get_used_space(&ctx->packet_queue);
LOG(LVL_DEBUG, "handling ack for seq_nr %u, removing %zu packets, %zu packets remaining.", acked_seq, packets_to_remove, packets_available);
if(packets_available == 0) {
// no packets left in queue, but an acknowledgement must be
// transmitted. Add an empty packet to do that.
result_t res = connection_add_empty_packet(ctx, false);
if (res != OK) {
LOG(LVL_WARN, "Failed to add empty packet: %d.", res);
}
}
}
bool connection_can_transmit(const connection_ctx_t *ctx)
{
assert(ctx->conn_state != CONN_STATE_UNINITIALIZED);
return (packet_queue_get_used_space(&ctx->packet_queue) != 0)
&& (packet_queue_get(&ctx->packet_queue, ctx->next_packet_index) != NULL);
}

View file

@ -0,0 +1,126 @@
/*
* This file contains functions to handle a single layer 2 connection.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Copyright (C) 2024 Thomas Kolb
*/
#ifndef CONNECTION_H
#define CONNECTION_H
#include <results.h>
#include <stdbool.h>
#include "packet_queue.h"
struct connection_ctx_s;
typedef enum {
CONN_STATE_UNINITIALIZED, //!< Uninitialized. Cannot be used in any way
CONN_STATE_INITIALIZED, //!< Initialized, no packets processed yet
CONN_STATE_CONNECTING, //!< Connection request sent, no two-way communication yet
CONN_STATE_ESTABLISHED, //!< Connection is established
CONN_STATE_CLOSED //!< Connection has been closed (gracefully or by timeout)
} connection_state_t;
/*!\brief Type for a callback function that is called when a data packet was received. */
typedef void (*connection_data_callback_t)(struct connection_ctx_s *conn, const uint8_t *data, size_t len);
typedef struct connection_ctx_s {
connection_state_t conn_state; //!< State of the connection.
connection_data_callback_t data_cb; //!< Callback function for received data packets.
ham64_t my_addr; //!< The local link layer address.
ham64_t peer_addr; //!< The link layer address of the peer.
uint8_t last_acked_seq; //!< Next sequence number expected by the peer (from last Ack).
uint8_t next_expected_seq; //!< Next sequence number expected by us.
packet_queue_t packet_queue; //!< Transmission packet queue.
size_t next_packet_index; //!< Index in the packet queue of the next packet to transmit.
uint8_t next_seq_nr; //!< Sequence number to tag the next transmitted packet with.
} connection_ctx_t;
/*!\brief Initialize the layer 2 connection context.
*
* \param ctx The connection context to initialize.
* \param my_addr The local link layer address.
* \param peer_addr The remote link layer address.
* \returns OK if everything worked or a fitting error code.
*/
result_t connection_init(connection_ctx_t *ctx, const ham64_t *my_addr, const ham64_t *peer_addr);
/*!\brief Destroy the given layer 2 connection context.
*/
void connection_destroy(connection_ctx_t *ctx);
/*!\brief Handle a received packet.
*
* \param ctx The receiver context.
* \param buf Where to write the encoded packet data.
* \param buf_len Space available in the buffer.
* \returns A result code from the packet handling procedure.
*/
result_t connection_handle_packet(connection_ctx_t *ctx, const uint8_t *buf, size_t buf_len);
/*!\brief Return the sequence number expected next by our side.
*/
uint8_t connection_get_next_expected_seq(const connection_ctx_t *ctx);
/*!\brief Return the sequence number expected next by the other side.
*/
uint8_t connection_get_last_acked_seq(const connection_ctx_t *ctx);
/*!\brief Enqueue a packet for transmission.
* \param ctx The connection context.
*/
result_t connection_enqueue_packet(connection_ctx_t *ctx, uint8_t *buf, size_t buf_len);
/*!\brief Add an empty packet to ensure an acknowledgement is sent.
* \param ctx The connection context.
* \param tx_request Value of the TX Request field in the packet.
*/
result_t connection_add_empty_packet(connection_ctx_t *ctx, bool tx_request);
/*!\brief Encode the next packet for transmission.
*
* \note
* If no more packets are available, this function returns zero. In that case,
* either \ref connection_restart() or \ref connection_handle_ack() must be
* called to handle retransmits correctly.
*
* \param ctx The connection context.
* \param ack_seq_nr The received sequence number to send as an acknowledgement.
* \param buf Where to write the encoded packet data.
* \param buf_len Space available in the buffer.
* \returns The number of bytes written to buf or zero if no packet was available.
*/
size_t connection_encode_next_packet(connection_ctx_t *ctx, uint8_t ack_seq_nr, uint8_t *buf, size_t buf_len);
/*!\brief Restart the transmission from the beginning of the packet queue.
*/
void connection_restart_tx(connection_ctx_t *ctx);
/*!\brief Remove the first packet from the queue if it is an empty packet.
*/
void connection_tx_clean_empty_packet(connection_ctx_t *ctx);
/*!\brief Handle acknowledgements.
* \details
* Removes all packets before the given sequence number from the queue.
*
* \param ctx The connection context.
* \param acked_seq The acknowledged (= next expected) sequence number.
* \param do_ack Whether an empty packet shall be generated if the queue is empty.
*/
void connection_handle_ack(connection_ctx_t *ctx, uint8_t acked_seq);
/*!\brief Check if there are packets queued for transmission.
*/
bool connection_can_transmit(const connection_ctx_t *ctx);
#endif // CONNECTION_H

View file

@ -187,3 +187,19 @@ void ham64_format(const ham64_t *ham64, char *out)
out[5*ham64->length - 1] = '\0'; out[5*ham64->length - 1] = '\0';
} }
bool ham64_is_equal(const ham64_t *a, const ham64_t *b)
{
if(a->length != b->length) {
return false;
}
for(uint8_t i = 0; i < a->length; i++) {
if(a->addr[i] != b->addr[i]) {
return false;
}
}
return true;
}

View file

@ -9,6 +9,7 @@
#include <stddef.h> #include <stddef.h>
#include <stdint.h> #include <stdint.h>
#include <stdbool.h>
// buffer size required for the string representation of a maximum-length HAM64 // buffer size required for the string representation of a maximum-length HAM64
// address, including terminating zero. // address, including terminating zero.
@ -62,4 +63,11 @@ const char *ham64_addr_type_to_string(ham64_addr_type_t addr_type);
*/ */
void ham64_format(const ham64_t *ham64, char *out); void ham64_format(const ham64_t *ham64, char *out);
/*!\brief Check if two ham64 addresses are equal.
* \param a Pointer to the first address.
* \param b Pointer to the second address.
* \returns True if a and b are equal, false otherwise.
*/
bool ham64_is_equal(const ham64_t *a, const ham64_t *b);
#endif // HAM64_H #endif // HAM64_H

View file

@ -6,7 +6,6 @@
*/ */
#include <string.h> #include <string.h>
#include <assert.h>
#include <unistd.h> #include <unistd.h>
#include <errno.h> #include <errno.h>

View file

@ -11,16 +11,17 @@
typedef enum { typedef enum {
OK, OK,
ERR_INVALID_STATE, ERR_INVALID_STATE, // module or context is in an invalid state
ERR_INVALID_PARAM, // invalid / nonsense parameters given ERR_INVALID_PARAM, // invalid / nonsense parameters given
ERR_NO_MEM, // not enough memory or allocation error ERR_INVALID_ADDRESS, // invalid address received or given
ERR_SIZE, // a given size is invalid ERR_NO_MEM, // not enough memory or allocation error
ERR_LIQUID, // an error occurred in the LiquidDSP library. ERR_SIZE, // a given size is invalid
ERR_SYSCALL, // a syscall failed. Use errno to determine the cause. ERR_LIQUID, // an error occurred in the LiquidDSP library.
ERR_SOAPY, // an error occurred in the SoapySDR library. ERR_SYSCALL, // a syscall failed. Use errno to determine the cause.
ERR_SDR, // an error occurred in the SDR interface. ERR_SOAPY, // an error occurred in the SoapySDR library.
ERR_INTEGRITY, // an integrity check failed (e.g. CRC of received packet is wrong) ERR_SDR, // an error occurred in the SDR interface.
ERR_SEQUENCE, // an unexpected packet was received ERR_INTEGRITY, // an integrity check failed (e.g. CRC of received packet is wrong)
ERR_SEQUENCE, // an unexpected packet was received
} result_t; } result_t;
#ifdef DEBUG_LIQUID #ifdef DEBUG_LIQUID