Compare commits

...

10 commits
main ... rudi_s

Author SHA1 Message Date
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
49 changed files with 1326 additions and 73 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.
@ -267,108 +268,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 Refusal"];
}
.... ....
==== 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

@ -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