From b515b6b56c509fd4c9ddf5bc8f78baca536b9afb Mon Sep 17 00:00:00 2001 From: DiosDelRayo Date: Fri, 27 Sep 2024 02:20:56 -0600 Subject: [PATCH] Adding all necessary for UR code offline signing in both directions and needs to get enabled with `WITH_OTS_UR` to compile with UR. This update depends on [monero PR](https://github.com/monero-project/monero/pull/9492). It cannot work without modifications on monero core! @tobtoht also mentions that build with static Qt5 will not work. I compiled and tested exclusively with ubuntu 24.04 in Qt Creator with Qt 5.15.2 --- CMakeLists.txt | 10 +- components/QRCodeScanner.qml | 16 +- components/TxConfirmationDialog.qml | 4 +- components/UrCode.qml | 165 +++ components/UrCodeScanner.qml | 358 +++++ images/restore-wallet-from-qr.png | Bin 0 -> 1366 bytes images/restore-wallet-from-qr@2x.png | Bin 0 -> 5569 bytes main.qml | 87 +- monero | 2 +- pages/Transfer.qml | 142 +- pages/settings/SettingsLayout.qml | 9 + qml.qrc | 4 + src/CMakeLists.txt | 6 + src/libwalletqt/PendingTransaction.cpp | 5 + src/libwalletqt/PendingTransaction.h | 1 + src/libwalletqt/UnsignedTransaction.cpp | 5 + src/libwalletqt/UnsignedTransaction.h | 1 + src/libwalletqt/Wallet.cpp | 40 + src/libwalletqt/Wallet.h | 10 + src/main/main.cpp | 20 +- src/otsur/CMakeLists.txt | 16 + src/otsur/bcur/CMakeLists.txt | 34 + src/otsur/bcur/README.md | 2 + src/otsur/bcur/bc-ur.hpp | 28 + src/otsur/bcur/bytewords.cpp | 170 +++ src/otsur/bcur/bytewords.hpp | 30 + src/otsur/bcur/cbor-lite.hpp | 558 ++++++++ src/otsur/bcur/crc32.c | 43 + src/otsur/bcur/crc32.h | 28 + src/otsur/bcur/fountain-decoder.cpp | 254 ++++ src/otsur/bcur/fountain-decoder.hpp | 103 ++ src/otsur/bcur/fountain-encoder.cpp | 126 ++ src/otsur/bcur/fountain-encoder.hpp | 81 ++ src/otsur/bcur/fountain-utils.cpp | 43 + src/otsur/bcur/fountain-utils.hpp | 61 + src/otsur/bcur/memzero.c | 84 ++ src/otsur/bcur/memzero.h | 8 + src/otsur/bcur/random-sampler.cpp | 84 ++ src/otsur/bcur/random-sampler.hpp | 38 + src/otsur/bcur/sha2.c | 889 ++++++++++++ src/otsur/bcur/sha2.h | 100 ++ src/otsur/bcur/ur-decoder.cpp | 119 ++ src/otsur/bcur/ur-decoder.hpp | 66 + src/otsur/bcur/ur-encoder.cpp | 50 + src/otsur/bcur/ur-encoder.hpp | 52 + src/otsur/bcur/ur.cpp | 28 + src/otsur/bcur/ur.hpp | 34 + src/otsur/bcur/utils.cpp | 172 +++ src/otsur/bcur/utils.hpp | 88 ++ src/otsur/bcur/xoshiro256.cpp | 177 +++ src/otsur/bcur/xoshiro256.hpp | 45 + src/otsur/data/CMakeLists.txt | 23 + src/otsur/data/MoneroData.cpp | 110 ++ src/otsur/data/MoneroData.h | 41 + src/otsur/data/MoneroTxData.h | 49 + src/otsur/data/MoneroWalletData.h | 61 + src/otsur/data/UrTypes.h | 9 + src/otsur/qrcode/CMakeLists.txt | 25 + src/otsur/qrcode/QrCode.cpp | 183 +++ src/otsur/qrcode/QrCode.h | 85 ++ src/otsur/qrcode/QrCode_p.h | 33 + src/otsur/qrencode/CMakeLists.txt | 29 + src/otsur/qrencode/bitstream.c | 231 ++++ src/otsur/qrencode/bitstream.h | 43 + src/otsur/qrencode/config.h | 43 + src/otsur/qrencode/mask.c | 357 +++++ src/otsur/qrencode/mask.h | 38 + src/otsur/qrencode/mmask.c | 177 +++ src/otsur/qrencode/mmask.h | 34 + src/otsur/qrencode/mqrspec.c | 232 ++++ src/otsur/qrencode/mqrspec.h | 150 +++ src/otsur/qrencode/qrenc.c | 1453 ++++++++++++++++++++ src/otsur/qrencode/qrencode.c | 938 +++++++++++++ src/otsur/qrencode/qrencode.h | 568 ++++++++ src/otsur/qrencode/qrencode_inner.h | 88 ++ src/otsur/qrencode/qrinput.c | 1639 +++++++++++++++++++++++ src/otsur/qrencode/qrinput.h | 124 ++ src/otsur/qrencode/qrspec.c | 514 +++++++ src/otsur/qrencode/qrspec.h | 174 +++ src/otsur/qrencode/rsecc.c | 149 +++ src/otsur/qrencode/rsecc.h | 31 + src/otsur/qrencode/split.c | 323 +++++ src/otsur/qrencode/split.h | 47 + src/otsur/qtquick/CMakeLists.txt | 37 + src/otsur/qtquick/ScanResult.h | 24 + src/otsur/qtquick/ScanThread.cpp | 118 ++ src/otsur/qtquick/ScanThread.h | 43 + src/otsur/qtquick/UrCodeScanner.cpp | 177 +++ src/otsur/qtquick/UrCodeScanner.h | 99 ++ src/otsur/qtquick/UrImageProvider.cpp | 29 + src/otsur/qtquick/UrImageProvider.h | 23 + src/otsur/qtquick/UrRegister.cpp | 59 + src/otsur/qtquick/UrRegister.h | 13 + src/otsur/qtquick/UrSender.cpp | 164 +++ src/otsur/qtquick/UrSender.h | 75 ++ wizard/WizardHome.qml | 39 + wizard/WizardRestoreWallet1.qml | 53 +- 97 files changed, 13408 insertions(+), 42 deletions(-) create mode 100644 components/UrCode.qml create mode 100644 components/UrCodeScanner.qml create mode 100644 images/restore-wallet-from-qr.png create mode 100644 images/restore-wallet-from-qr@2x.png create mode 100644 src/otsur/CMakeLists.txt create mode 100644 src/otsur/bcur/CMakeLists.txt create mode 100644 src/otsur/bcur/README.md create mode 100644 src/otsur/bcur/bc-ur.hpp create mode 100644 src/otsur/bcur/bytewords.cpp create mode 100644 src/otsur/bcur/bytewords.hpp create mode 100644 src/otsur/bcur/cbor-lite.hpp create mode 100644 src/otsur/bcur/crc32.c create mode 100644 src/otsur/bcur/crc32.h create mode 100644 src/otsur/bcur/fountain-decoder.cpp create mode 100644 src/otsur/bcur/fountain-decoder.hpp create mode 100644 src/otsur/bcur/fountain-encoder.cpp create mode 100644 src/otsur/bcur/fountain-encoder.hpp create mode 100644 src/otsur/bcur/fountain-utils.cpp create mode 100644 src/otsur/bcur/fountain-utils.hpp create mode 100644 src/otsur/bcur/memzero.c create mode 100644 src/otsur/bcur/memzero.h create mode 100644 src/otsur/bcur/random-sampler.cpp create mode 100644 src/otsur/bcur/random-sampler.hpp create mode 100644 src/otsur/bcur/sha2.c create mode 100644 src/otsur/bcur/sha2.h create mode 100644 src/otsur/bcur/ur-decoder.cpp create mode 100644 src/otsur/bcur/ur-decoder.hpp create mode 100644 src/otsur/bcur/ur-encoder.cpp create mode 100644 src/otsur/bcur/ur-encoder.hpp create mode 100644 src/otsur/bcur/ur.cpp create mode 100644 src/otsur/bcur/ur.hpp create mode 100644 src/otsur/bcur/utils.cpp create mode 100644 src/otsur/bcur/utils.hpp create mode 100644 src/otsur/bcur/xoshiro256.cpp create mode 100644 src/otsur/bcur/xoshiro256.hpp create mode 100644 src/otsur/data/CMakeLists.txt create mode 100644 src/otsur/data/MoneroData.cpp create mode 100644 src/otsur/data/MoneroData.h create mode 100644 src/otsur/data/MoneroTxData.h create mode 100644 src/otsur/data/MoneroWalletData.h create mode 100644 src/otsur/data/UrTypes.h create mode 100644 src/otsur/qrcode/CMakeLists.txt create mode 100644 src/otsur/qrcode/QrCode.cpp create mode 100644 src/otsur/qrcode/QrCode.h create mode 100644 src/otsur/qrcode/QrCode_p.h create mode 100644 src/otsur/qrencode/CMakeLists.txt create mode 100644 src/otsur/qrencode/bitstream.c create mode 100644 src/otsur/qrencode/bitstream.h create mode 100644 src/otsur/qrencode/config.h create mode 100644 src/otsur/qrencode/mask.c create mode 100644 src/otsur/qrencode/mask.h create mode 100644 src/otsur/qrencode/mmask.c create mode 100644 src/otsur/qrencode/mmask.h create mode 100644 src/otsur/qrencode/mqrspec.c create mode 100644 src/otsur/qrencode/mqrspec.h create mode 100644 src/otsur/qrencode/qrenc.c create mode 100644 src/otsur/qrencode/qrencode.c create mode 100644 src/otsur/qrencode/qrencode.h create mode 100644 src/otsur/qrencode/qrencode_inner.h create mode 100644 src/otsur/qrencode/qrinput.c create mode 100644 src/otsur/qrencode/qrinput.h create mode 100644 src/otsur/qrencode/qrspec.c create mode 100644 src/otsur/qrencode/qrspec.h create mode 100644 src/otsur/qrencode/rsecc.c create mode 100644 src/otsur/qrencode/rsecc.h create mode 100644 src/otsur/qrencode/split.c create mode 100644 src/otsur/qrencode/split.h create mode 100644 src/otsur/qtquick/CMakeLists.txt create mode 100644 src/otsur/qtquick/ScanResult.h create mode 100644 src/otsur/qtquick/ScanThread.cpp create mode 100644 src/otsur/qtquick/ScanThread.h create mode 100644 src/otsur/qtquick/UrCodeScanner.cpp create mode 100644 src/otsur/qtquick/UrCodeScanner.h create mode 100644 src/otsur/qtquick/UrImageProvider.cpp create mode 100644 src/otsur/qtquick/UrImageProvider.h create mode 100644 src/otsur/qtquick/UrRegister.cpp create mode 100644 src/otsur/qtquick/UrRegister.h create mode 100644 src/otsur/qtquick/UrSender.cpp create mode 100644 src/otsur/qtquick/UrSender.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 55f426cc3f..67923505ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ option(STATIC "Link libraries statically, requires static Qt") option(USE_DEVICE_TREZOR "Trezor support compilation" ON) option(WITH_SCANNER "Enable webcam QR scanner" OFF) +option(WITH_OTS_UR "Enable offline transaction signing via UR" OFF) option(WITH_DESKTOP_ENTRY "Ask to install desktop entry on first startup" ON) option(WITH_UPDATER "Regularly check for new updates" ON) option(DEV_MODE "Checkout latest monero master on build" OFF) @@ -80,9 +81,12 @@ include(VersionGui) message(STATUS "${CMAKE_MODULE_PATH}") -if(WITH_SCANNER) +if(WITH_SCANNER AND NOT WITH_OTS_UR) add_definitions(-DWITH_SCANNER) endif() +if(WITH_OTS_UR) + add_definitions(-DWITH_OTS_UR) +endif() if(WITH_DESKTOP_ENTRY) add_definitions(-DWITH_DESKTOP_ENTRY) @@ -122,7 +126,7 @@ set(QT5_LIBRARIES Qt5Xml ) -if(WITH_SCANNER) +if(WITH_SCANNER OR OTS_UR_WITH_QTQUICK) list(APPEND QT5_LIBRARIES Qt5Multimedia) endif() @@ -241,7 +245,7 @@ if(STATIC) modelsplugin ) - if(WITH_SCANNER) + if(WITH_SCANNER OR OTS_UR_WITH_QTQUICK) list(APPEND QT5_EXTRA_LIBRARIES_LIST declarative_multimedia Qt5MultimediaQuick diff --git a/components/QRCodeScanner.qml b/components/QRCodeScanner.qml index 7479c75f6a..48bb5e39f4 100644 --- a/components/QRCodeScanner.qml +++ b/components/QRCodeScanner.qml @@ -29,6 +29,7 @@ import QtQuick 2.9 import QtMultimedia 5.4 import QtQuick.Dialogs 1.2 +import "../components" as MoneroComponents import moneroComponents.QRCodeScanner 1.0 Rectangle { @@ -51,7 +52,7 @@ Rectangle { name: "Capture" StateChangeScript { script: { - root.visible = true + root.visible = true camera.captureMode = Camera.CaptureStillImage camera.cameraState = Camera.ActiveState camera.start() @@ -64,7 +65,7 @@ Rectangle { StateChangeScript { script: { camera.stop() - root.visible = false + root.visible = false finder.enabled = false camera.cameraState = Camera.UnloadedState } @@ -135,6 +136,17 @@ Rectangle { } } + MoneroComponents.StandardButton { + id: btnClose + text: qsTr("Cancel") + z: viewfinder.z + 1 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + anchors.topMargin: 20 + onClicked: root.state = "Stopped" + } + MessageDialog { id: messageDialog title: qsTr("QrCode Scanned") + translationManager.emptyString diff --git a/components/TxConfirmationDialog.qml b/components/TxConfirmationDialog.qml index dc55a6f520..e4036d925e 100644 --- a/components/TxConfirmationDialog.qml +++ b/components/TxConfirmationDialog.qml @@ -163,10 +163,12 @@ Rectangle { fontFamily: "Arial" horizontalAlignment: Text.AlignHCenter text: { - if (appWindow.viewOnly) { + if (appWindow.viewOnly && !persistentSettings.useURCode) { return qsTr("Create transaction file") + translationManager.emptyString; } else if (root.sweepUnmixable) { return qsTr("Sweep unmixable outputs") + translationManager.emptyString; + } else if (appWindow.viewOnly && persistentSettings.useURCode) { // intentionally behind sweepUnmixable + return qsTr("Create transaction") + translationManager.emptyString; } else { return qsTr("Confirm send") + translationManager.emptyString; } diff --git a/components/UrCode.qml b/components/UrCode.qml new file mode 100644 index 0000000000..adb964582a --- /dev/null +++ b/components/UrCode.qml @@ -0,0 +1,165 @@ +import QtQuick 2.9 +import QtMultimedia 5.4 +import OtsUr 0.1 +import "." as MoneroComponents + +Rectangle { + id : root + + x: 0 + y: 0 + z: parent.z+1 + width: parent.width + height: parent.height + + property bool active: false + property bool ur: true + + visible: root.active + focus: root.active + color: "black" + + Image { + id: qrCodeImage + cache: false + width: qrCodeImage.height + height: Math.max(300, Math.min(parent.height - frameInfo.height - displayType.height - 240, parent.width - 40)) + anchors.centerIn: parent + function reload() { + var tmp = qrCodeImage.source + qrCodeImage.source = "" + qrCodeImage.source = tmp + } + } + + Rectangle { + id: frameInfo + visible: textFrameInfo.visible + height: textFrameInfo.height + 5 + width: textFrameInfo.width + 20 + z: parent.z + 1 + radius: 16 + color: "#FA6800" + anchors.centerIn: textFrameInfo + opacity: 0.4 + } + + Text { + id: textFrameInfo + z: frameInfo.z + 1 + visible: textFrameInfo.text !== "" + text: urSender.currentFrameInfo + anchors.top: parent.top + anchors.horizontalCenter: qrCodeImage.horizontalCenter + anchors.margins: 30 + font.pixelSize: 22 + color: "white" + opacity: 0.7 + } + + Rectangle { + id: displayType + visible: textDisplayType.text !== "" + height: textDisplayType.height + 5 + width: textDisplayType.width + 20 + z: parent.z + 1 + radius: 16 + color: "#FA6800" + anchors.centerIn: textDisplayType + opacity: 0.4 + } + + Text { + id: textDisplayType + visible: displayType.visible + z: displayType.z + 1 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: btnClose.top + anchors.margins: 30 + text: "" + font.pixelSize: 22 + color: "white" + opacity: 0.7 + } + + MoneroComponents.StandardButton { + id: btnClose + text: qsTr("Close") + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + anchors.topMargin: 20 + focus: true + onClicked: root.close() + } + + Connections { + target: urSender + function onUpdateQrCode() { + qrCodeImage.reload() + } + } + + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + onDoubleClicked: { + root.close() + } + } + + function showQr(text) { + urSender.sendQrCode(text) + root.ur = false + root.active = true + } + + function showWalletData(address, spendKey, viewKey, mnemonic, height) { + textDisplayType.text = qsTr("Wallet") + urSender.sendWallet(address, spendKey, viewKey, mnemonic, height) + root.ur = false + root.active = true + } + + function showTxData(address, amount, paymentId, recipient, description) { + textDisplayType.text = qsTr("TX Data") + urSender.sendTx(address, amount, paymentId, recipient, description) + root.ur = false + root.active = true + } + + function showOutputs(outputs) { + textDisplayType.text = qsTr("Outputs") + urSender.sendOutputs(outputs) + root.active = true + } + + function showKeyImages(keyImages) { + textDisplayType.text = qsTr("Key Images") + urSender.sendKeyImages(keyImages) + root.active = true + } + + function showUnsignedTx(tx) { + textDisplayType.text = qsTr("Unsigned TX") + urSender.sendTxUnsigned(tx) + root.active = true + } + + function showSignedTx(tx) { + textDisplayType.text = qsTr("Signed TX") + urSender.sendTxSigned(tx) + root.active = true + } + + function close() { + textDisplayType.text = "" + urSender.sendClear() + root.ur = true + root.active = false + } + + Component.onCompleted: { + qrCodeImage.source = "image://urcode/qr" + } +} diff --git a/components/UrCodeScanner.qml b/components/UrCodeScanner.qml new file mode 100644 index 0000000000..d128cfdbe9 --- /dev/null +++ b/components/UrCodeScanner.qml @@ -0,0 +1,358 @@ +import QtQuick 2.9 +import QtQml.Models 2.2 +import QtMultimedia 5.4 +import QtQuick.Dialogs 1.2 +import OtsUr 0.1 +import "." as MoneroComponents + +Rectangle { + id : root + + x: 0 + y: 0 + z: parent.z+1 + width: parent.width + height: parent.height + + visible: false + color: "black" + state: "Stopped" + + property bool active: false + property bool ur: true + property string errorMessage: "" + + signal canceled() + signal qrCode(string data) + signal wallet(MoneroWalletData walletData) + signal txData(MoneroTxData data) + signal unsignedTx(var tx) + signal signedTx(var tx) + signal keyImages(var keyImages) + signal outputs(var outputs) + + states: [ + State { + name: "Capture" + when: root.active + StateChangeScript { + script: { + root.visible = true + for(var i = 0; i < QtMultimedia.availableCameras.length; i++) + if(QtMultimedia.availableCameras[i].deviceId === persistentSettings.lastUsedCamera) { + urCamera.deviceId = persistentSettings.lastUsedCamera + break + } + urCamera.captureMode = Camera.CaptureStillImage + urCamera.cameraState = Camera.ActiveState + urCamera.start() + } + } + }, + State { + name: "Stopped" + when: !root.active + StateChangeScript { + script: { + urCamera.stop() + urScanner.stop() + root.ur = true + scanProgress.reset() + root.visible = false + urCamera.cameraState = Camera.UnloadedState + } + } + } + ] + + ListModel { + id: availableCameras + Component.onCompleted: { + availableCameras.clear() + for(var i = 0; i < QtMultimedia.availableCameras.length; i++) { + var cam = QtMultimedia.availableCameras[i] + availableCameras.append({ + column1: cam.displayName, + column2: cam.deviceId, + priority: i + }) + } + } + } + + UrCodeScannerImpl { + id: urScanner + objectName: "urScanner" + onQrDataReceived: function(data) { + root.active = false + } + + onUrDataReceived: function(type, data) { + root.active = false + } + + onUrDataFailed: function(error) { + root.cancel() + } + } + + MoneroComponents.StandardButton { + id: btnSwitchCamera + visible: QtMultimedia.availableCameras.length === 2 // if the system has exact to cams, show a switch button + text: qsTr("Switch Camera") + z: viewfinder.z + 1 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 20 + anchors.bottomMargin: 20 + onClicked: { + btnSwitchCamera.visible = false + urCamera.deviceId = urCamera.deviceId === QtMultimedia.availableCameras[0].deviceId ? QtMultimedia.availableCameras[1].deviceId : QtMultimedia.availableCameras[0].deviceId + persistentSettings.lastUsedCamera = urCamera.deviceId + btnSwitchCamera.visible = true + } + } + + StandardDropdown { + id: cameraChooser + visible: QtMultimedia.availableCameras.length > 2 // if the system has more then 2 cams, show a list + z: viewfinder.z + 1 + width: 300 + height: 30 + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.topMargin: 20 + anchors.bottomMargin: 20 + dataModel: availableCameras + onChanged: { + urCamera.deviceId = QtMultimedia.availableCameras[cameraChooser.currentIndex].deviceId + persistentSettings.lastUsedCamera = urCamera.deviceId + } + } + + Camera { + id: urCamera + objectName: "urCamera" + captureMode: Camera.CaptureStillImage + cameraState: Camera.UnloadedState + + focus { + focusMode: Camera.FocusContinuous + } + } + + VideoOutput { + id: viewfinder + visible: root.active == true + + x: 0 + y: btnSwitchCamera.height + 40 // 2 x 20 (margin) + z: parent.z+1 + width: parent.width + height: parent.height - btnClose.height - btnSwitchCamera.height - 80 // 4 x 20 (margin) + + source: urCamera + autoOrientation: true + focus: visible + + MouseArea { + anchors.fill: parent + onPressAndHold: { + if (camera.lockStatus === Camera.locked)camera.unlock() + camera.searchAndLock() + } + onDoubleClicked: root.cancel() + } + + Rectangle { + id: scanTypeFrame + height: scanType.height + 20 + width: scanType.width + 30 + z: parent.z + 1 + radius: 16 + color: "#FA6800" + opacity: 0.4 + anchors.centerIn: scanType + } + + Text { + z: scanTypeFrame.z + 1 + anchors.centerIn: parent + id: scanType + text: "" + font.pixelSize: 22 + color: "white" + opacity: 0.7 + } + + Rectangle { + id: unexpectedTypeFrame + visible: root.errorMessage !== "" + height: Math.max(unexpectedType.height + 20, scanTypeFrame.height) + width: Math.max(unexpectedType.width + 30, scanTypeFrame.width) + z: parent.z + 100 + radius: 3 + color: "black" + anchors.centerIn: unexpectedType + } + + Text { + id: unexpectedType + visible: unexpectedTypeFrame.visible + text: root.errorMessage + z: unexpectedTypeFrame.z + 1 + anchors.centerIn: parent + font.pixelSize: 22 + color: "#FA6800" + } + + Rectangle { + id: scanProgress + property int scannedFrames: 0 + property int totalFrames: 0 + property int progress: 0 + visible: root.ur + height: textScanProgress.height + 10 + width: viewfinder.contentRect.width - 40 + z: viewfinder.z + 1 + radius: 20 + color: "#FA6800" + opacity: 0.4 + anchors.horizontalCenter: viewfinder.horizontalCenter + anchors.bottom: viewfinder.bottom + anchors.bottomMargin: 20 + + function onScannedFrames(count, total) { + scanProgress.scannedFrames = count + scanProgress.totalFrames = total + } + + function onProgress(complete) { + scanProgress.progress = Math.floor(complete * 100) + } + + function reset() { + scanProgress.scannedFrames = 0 + scanProgress.totalFrames = 0 + scanProgress.progress = 0 + } + } + + Rectangle { + id: scanProgressBar + visible: root.ur && scanProgressBar.width > 36 + height: scanProgress.height - 8 + width: Math.floor((scanProgress.width - 8) * scanProgress.progress / 100) + x: scanProgress.x + 4 + y: scanProgress.y + 4 + z: scanProgress.z + 1 + color: "#FA6800" + opacity: 0.8 + radius: 16 + } + + Text { + id: textScanProgress + visible: root.ur + z: scanProgress.z + 2 + anchors.centerIn: scanProgress + text: (scanProgress.progress > 0 || scanProgress.totalFrames > 0) ? (scanProgress.progress + "% (" + scanProgress.scannedFrames + "/" + scanProgress.totalFrames + ")") : "" + font.pixelSize: 22 + color: "white" + opacity: 0.7 + } + } + + MouseArea { + anchors.fill: parent + enabled: true + } + + MoneroComponents.StandardButton { + id: btnClose + text: qsTr("Cancel") + z: viewfinder.z + 1 + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + anchors.bottomMargin: 20 + anchors.topMargin: 20 + onClicked: root.cancel() + } + + function cancel() { + root.active = false + root.canceled() + } + + function scanQrCode() { + root.ur = false + root.active = true + scanType.text = qsTr("Scan QR Code") + urScanner.qr() + } + + function scanWallet() { + root.ur = false + root.active = true + scanType.text = qsTr("Scan Wallet QR Code") + urScanner.scanWallet() + } + + function scanTxData() { + root.ur = false + root.active = true + scanType.text = qsTr("Scan Tx Data QR Code") + urScanner.scanTxData() + } + + function scanOutputs() { + root.active = true + scanType.text = qsTr("Scan Outputs UR Code") + urScanner.scanOutputs() + } + + function scanKeyImages() { + root.active = true + scanType.text = qsTr("Scan Key Images UR Code") + urScanner.scanKeyImages() + } + + function scanUnsignedTx() { + root.active = true + scanType.text = qsTr("Scan Unsigned Transaction UR Code") + urScanner.scanUnsignedTx() + } + + function scanSignedTx() { + root.active = true + scanType.text = qsTr("Scan Signed Transaction UR Code") + urScanner.scanSignedTx() + } + + function onUnexpectedFrame(urType){ + root.errorMessage = qsTr("Unexpected UR type: ") + urType + } + + function onDecodedFrame(unused) { + root.errorMessage = "" + } + + Component.onCompleted: { + if( QtMultimedia.availableCameras.length === 0) { + console.warn("No camera available. Disable qrScannerEnabled") + appWindow.qrScannerEnabled = false + return + } + urScanner.outputs.connect(root.outputs) + urScanner.keyImages.connect(root.keyImages) + urScanner.unsignedTx.connect(root.unsignedTx) + urScanner.signedTx.connect(root.signedTx) + urScanner.txData.connect(root.txData) + urScanner.wallet.connect(root.wallet) + urScanner.qrCaptureStarted.connect(scanProgress.reset) + urScanner.scannedFrames.connect(scanProgress.onScannedFrames) + urScanner.estimatedCompletedPercentage.connect(scanProgress.onProgress) + urScanner.unexpectedUrType.connect(root.onUnexpectedFrame) + urScanner.decodedFrame.connect(root.onDecodedFrame) + } +} diff --git a/images/restore-wallet-from-qr.png b/images/restore-wallet-from-qr.png new file mode 100644 index 0000000000000000000000000000000000000000..856329748d775b0d130eccd2259a5dcb4b45a623 GIT binary patch literal 1366 zcmV-c1*!UpP)EX>4Tx04R}tkv&MmKpe$iTg4Ar1nnRqlA$`;#fmuADionYs1;guFuC*#nlvOS zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0scmXsb*IkP&La) zClf+8w<`3!q7NYqVhk~fnfjb4rrU6f~epZjx!l)T9RpFkXEx?vG-5YKE{ zI_G`jFe^z4@j3CNK^G)`i6e@tQNBOx zvch?bvs$UK);;+PgL!Rbnd>x1k;EdFAVPqQ8p^1^LX=jG6ccIMPk8u89KT2|nOtQs zax9<<6_Voz|AXJ%nuXYun-oj{-7mKNF%Imu4RCM> zOcW`5-Q(T8oxS~grq$mM%i40vtLi-6000AxNkl7(_G@V(uB0F}j9t`KH#SAwh)^qN#YHRPri(^UDlRN&BBfo3xM;VP1~D32 zNg)V9YeUdVJMAQS_Z}C|3F>@wUdYq;pu7Vz3oVERXm`pRD34_}!8T1;v>dXsW3siT7%RieV;JC&C<7_VZm7chz|8tL6r~M#OQADW1+6T`5gf!U z|1>taAhF>81BuL$=9Mc1eUH?!3Ko0e9PaE`G}`;dF;6}NfvF9hyNZ41(QF8=D;6Ly z1Or33TroNv7^*9_dJUVE46pW+#b40*IWUHxe-Jtff1e1e|L>M@*Ji+4q_+?0>Q2WC zA6xMJ9QO2n?3ttJ?Z$RjZ{Nb6If^~KKN_Gc`rPPcHc%LV`?^4ycpQ-^rg~0R1qc0u z$jcvt2vXeCSZ{F?xx*(x71`LCLBY;O&8VY*bQEfK5^DbE@<`X#+G}J*u)T0D>cCRF zZDKD@z{>UL@Ztn!qy#4Tr+dQ9PG?83J3F>I_0+I(9W-iSdiez5Hw~7u;mr&-t$W^m zOZ~x+PRRGb@`45nuZBe$v5cm0q==Z0PJ+EO#MhTDiXT(}I zu!{g@q_kSb(z6ZeeR$P_$mol>p(h9icO%8k87k?j*t_M(a8#$IT)wtJ{dIckeh{+jPLR?r(i!j*ZnyohWc&Q3|Y zxvLE=r<FAP~a)(D1$H{ zgQ$oIb~=byMi5bkDu@LXL~%gS2)>(OeQ(}+%eCJ8^R9Js@7?Fy-`V@y`<#`t+sng6 zTXTUX3K=Q$FT!B|Zbrma)j+mW&7`cd=4irxmB7R&>0*`KRln_^ zAI;YG@K)1Jb6hQ^WlYb$vMVg$*8SMK&D`MZCF@YmZzaHy?aPNw+c~qg$?3H2&iu=B zQZ=q?r$<{pIq~@I_N^)SyB__^s43BdhL5!KVGhb#(M{b9DTe4wOVj^gfzv?MmaijqB=kgH)cw z5&N^LZFPy{mgV}{4*3Q_TQQeLw9*O7EKteuq1DwXbte+`loaX|#>2;_^$zyr_9ILt z(H3WS4fkAJ{9=eUbbs2U^Y-KH0Anw!o33Vh4#W$~N6K<^m&^*8h24GlZH9SX52a%0 z!NY+KX&hOWT>Mj7*>zafN@LDtV2)sRV^|QiFS-|F>83eM8P2sgS{C!@^5Cn5eL1x; zne&Obr?Otm8Lz?MO9_G3y>=hly)^NJcZJZDs@nb!_CpO0shQ=m>f=b6%?&s<0!yOnv?%J}J`t67uUq$KyXc0& zqLzO`Z71bI?X{NSPGt&sD1aph0Z~$(5b8S^%*IYC1el?q1Rer%xO^JoMQJ4h&SlXM z0agqQL+A)@;<`qPK;K9YKW1bolfpvS*=pKIsSp7VlmKukFN`myN@)lsFBN)Mn9&Hh z5+VtuA=Way;EnXMY&GFFA{LwKLwEi_0e#UBn&Ahk*XlL+l5wQnU{!7HkzUL8ow# zFIoB}1&jH~U$|8irc8&$M1x@<4}yxJS+QSD>Eg!l`sAUIfWzepm0pn8Um+!2_Ghua zicK-1Oy|o$AoowaU!gzdt^`9U27^i$Ft;kgbEDG`iutK50h7z3DqjH%g+e6ZSx6EA z#3Bh`h!v7d!jX_6Y%CcdkT5I^j{F6c8(%B|_)Jhi1(Bn;5D%UNSmE$23KEMaFp-21 zG8svsFj){8mKZ`|kuXdm>kA4`5f|!8AnePi6jUsTih^UZNdSh7WRi#q0xLEWzyo9? zNG3A@EG2|OWP?g77L)2M5b*%0oLnBj0ntJ}M>(JnoNDjoMnm9Gn9mlkFhIhF9B2r4 zE`O`^^MD_h2l`3?g_>9rj)=3uS&>K>64?qz`V3kFio{SSDmbwi6rQLYQIv%WB?Acy zDEbs4P}-qfsE#5KkO)M60znuJp$G}C@ccB)fR+;rkN|W*0z#x19D$0VP_cMFERKpN zQn4f?hDgPHffum2Y}x-pD;5vjW~$__Tro7iOlg{0QNG}osjsQeFs^bb!Qsk9K?Rso zDTsk^aH?1k*VGVm6Ts(y(C+b}T|e5nf6)p6fq=u}F#r-z0IZM%01G0?Y!V5HhkAs- z#F0Y?WTsMuPwZj=TM_|?Kzk0PBcv77o=UCY=E_1@e2tFS1S+Zk35LWd`t9EZLw`sZ zt=Ka@W^9B04^3>8fG;)~$ZyI9ZC=n`i2k%0e$Whx_wT$u)Z*`)0uKK^$v5fyU9RtP zeUk#;1peM#-{txy1-=RVy}SNza%p}(PJw*rUr+>eSlZ;g1_K?m)IwIf(3M9n*c->v zSm;ed=o%=7!L;-g_cT~~#yrTVE^%WxsrPGW&s{c$;b?ap22%}nqucvQ+n;yDZt@3> z>xak04dwEt^SAG`vVz-$*s-0>7Q1z}?$4R+O)+dXx2TCR&ySrq$623#T*b}8pn&e4 zI^Et`ZIw?fJ=ZDs5YkC&Cp*_&(ox=LGqHu(DTsJ6)Yf$=`p2T!hQ~J6QMT5NZ*0e8 zI=lY*+RHQ@+$ikTSYw)6aN*!)ExqP2(+3M;k}j3`mt@UI zXG^Qa7*!fD3}S>T8LE`_5aM zZm`bo6;YD;Odc3x(6Im3Ma%T6MKV(Bp1$GNg8Iz1Y-@6=kB(7Q(gPH)DzJ#(v$!I7 z(a@b8X*H@C-@INT|IX|q!`9?`!l*|EE8F0fBCt8W@#?D}{i02U?dxzF=ezIQ{-J7i z>;1ZJ^~QmS(gnNf^o7Kl(TaDoJ6a+K*4kqmitmxfJUr3eTBpW$j;*X8UH9goe~<0! z^(@C^epO3IN6?vLQRO{eKI%s-n*}8$GvxK8+mpQ02@BPd=ccRgnQo}vbiv=tc-Bo? z;;LqJ5uAJ>Uq639ZmlmgyoiF>qq8n0Cs-goV+%I~C3l=P$=WTbN7()1q&cJ4_H zKWD?LGSS1B=v_03R6BWXfmxY%8TYA)b9mT_LZ@lEO~;T?*?+PzW{d~*b`cBS_g5j? zC!5&s#K(O3DGk3=pX;BW5m>#yJE_`8XU8S+#L-*Ar+c<#ZFoCfKx%z!)pOP5KD#6E z-HUL#Y0t}W|H6A(JxdQe>CD$Gn%s5*PjII`X_dTxxXQlbawwN7H@(G82k1CC8^T52;D!?C4J( zm>^!eH@cy~D7vXBi&vS*t2|*%HmjW&)4X4ul29Yx$HD5ES@aZO)qK}X3+wkEHB^tv zzoR|=%4zVp;Y+>NXEHBu#@#=zAl`X6uaG_rE(!>69=PgOR=#k_np|sg$C8~R@EytB z)sn_-6F0k#I$uA<)FjuuIw>^dUrFmuDUZ>9-(z`u)QmkjKt51XH8z|+;P1p4$-6AI zE!Qih{`x#@bKj$`q{CPvP6Hw%oqDZO-F=D2;}se0+sR_1(Dbz77gnzYo-`JE_t@yR zk=I_MHXWx_J0@0fRr*@nQ>}D1c^)(mo-dDk?>)w@%&M6+ezEvwjNNv_qHT+ApyU(! zK~=*WjB!PbIXVl6&X1#XoJ8q&mkv+2EC6W-+p3n;-7uI{pY))7T>WgmHT}O_%W@`X YR1D_$)T=nQDK=_1Cl7j|LvZ}R0I{{2nE(I) literal 0 HcmV?d00001 diff --git a/main.qml b/main.qml index 98fb43c01d..1acaa5b3b7 100644 --- a/main.qml +++ b/main.qml @@ -82,12 +82,14 @@ ApplicationWindow { property string walletName property bool viewOnly: false property bool foundNewBlock: false - property bool qrScannerEnabled: (typeof builtWithScanner != "undefined") && builtWithScanner + property bool qrScannerEnabled: ((typeof builtWithScanner != "undefined") && builtWithScanner) || ((typeof builtWithOtsUr != "undefined") && builtWithOtsUr) property int blocksToSync: 1 property int firstBlockSeen property bool isMining: false property int walletMode: persistentSettings.walletMode property var cameraUi + property var urScannerUi + property var urDisplay property bool androidCloseTapped: false; property int userLastActive; // epoch // Default daemon addresses @@ -298,6 +300,7 @@ ApplicationWindow { currentWallet.deviceButtonPressed.disconnect(onDeviceButtonPressed); currentWallet.walletPassphraseNeeded.disconnect(onWalletPassphraseNeededWallet); currentWallet.transactionCommitted.disconnect(onTransactionCommitted); + currentWallet.transactionCommittedForExport.disconnect(onTransactionCommittedForExport); middlePanel.paymentClicked.disconnect(handlePayment); middlePanel.sweepUnmixableClicked.disconnect(handleSweepUnmixable); middlePanel.getProofClicked.disconnect(handleGetProof); @@ -345,6 +348,7 @@ ApplicationWindow { currentWallet.deviceButtonPressed.connect(onDeviceButtonPressed); currentWallet.walletPassphraseNeeded.connect(onWalletPassphraseNeededWallet); currentWallet.transactionCommitted.connect(onTransactionCommitted); + currentWallet.transactionCommittedForExport.connect(onTransactionCommittedForExport); currentWallet.proxyAddress = Qt.binding(persistentSettings.getWalletProxyAddress); middlePanel.paymentClicked.connect(handlePayment); middlePanel.sweepUnmixableClicked.connect(handleSweepUnmixable); @@ -850,7 +854,7 @@ ApplicationWindow { // here we update txConfirmationPopup txConfirmationPopup.transactionAmount = Utils.removeTrailingZeros(walletManager.displayAmount(transaction.amount)); txConfirmationPopup.transactionFee = Utils.removeTrailingZeros(walletManager.displayAmount(transaction.fee)); - txConfirmationPopup.confirmButton.text = viewOnly ? qsTr("Save as file") : qsTr("Confirm") + translationManager.emptyString; + txConfirmationPopup.confirmButton.text = viewOnly ? (persistentSettings.useURCode?qsTr("Show UR code"):qsTr("Save as file")) : qsTr("Confirm") + translationManager.emptyString; txConfirmationPopup.confirmButton.rightIcon = viewOnly ? "" : "qrc:///images/rightArrow.png" } } @@ -923,13 +927,13 @@ ApplicationWindow { txConfirmationPopup.sweepUnmixable = true; transaction = currentWallet.createSweepUnmixableTransaction(); - if (transaction.status !== PendingTransaction.Status_Ok) { + if (transaction.status !== PendingTransaction.Status_Ok) { // TODO: Dialog should only inform and offer only OK button IMO, informationPopup instead? console.error("Can't create transaction: ", transaction.errorString); txConfirmationPopup.errorText.text = qsTr("Can't create transaction: ") + transaction.errorString + translationManager.emptyString // deleting transaction object, we don't want memleaks currentWallet.disposeTransaction(transaction); - } else if (transaction.txCount == 0) { + } else if (transaction.txCount == 0) { // TODO: Dialog should only inform and offer only OK button IMO, informationPopup instead? console.error("No unmixable outputs to sweep"); txConfirmationPopup.errorText.text = qsTr("No unmixable outputs to sweep") + translationManager.emptyString // deleting transaction object, we don't want memleaks @@ -946,8 +950,13 @@ ApplicationWindow { // called after user confirms transaction function handleTransactionConfirmed(fileName) { - // View only wallet - we save the tx - if(viewOnly){ + if(viewOnly && persistentSettings.useURCode) { + appWindow.showProcessingSplash(qsTr("Preparing transaction ...")); + currentWallet.commitTransactionForExportAsync(transaction); + return + } + // View only wallet - we save the tx to file if UR is not enabled + if(viewOnly && !persistentSettings.useURCode){ // No file specified - abort if(!saveTxDialog.fileUrl) { currentWallet.disposeTransaction(transaction) @@ -992,6 +1001,35 @@ ApplicationWindow { }); } + function onTransactionCommittedForExport(txString, transaction, txid) { + hideProcessingSplash(); + if (txString.length === 0) { + console.log("Error committing transaction: " + transaction.errorString); + informationPopup.title = qsTr("Error") + translationManager.emptyString + informationPopup.text = qsTr("Couldn't send the money: ") + transaction.errorString + informationPopup.icon = StandardIcon.Critical + informationPopup.onCloseCallback = null; + informationPopup.open(); + } else { + if (txConfirmationPopup.transactionDescription.length > 0) { + for (var i = 0; i < txid.length; ++i) + currentWallet.setUserNote(txid[i], txConfirmationPopup.transactionDescription); + } + + // Clear tx fields + middlePanel.transferView.clearFields() + txConfirmationPopup.clearFields() + urDisplay.showUnsignedTx(txString) + } + currentWallet.refresh() + currentWallet.disposeTransaction(transaction) + currentWallet.storeAsync(function(success) { + if (!success) { + appWindow.showStatusMessage(qsTr("Failed to store the wallet"), 3); + } + }); + } + function doSearchInHistory(searchTerm) { middlePanel.searchInHistory(searchTerm); leftPanel.selectItem(middlePanel.state) @@ -1330,7 +1368,7 @@ ApplicationWindow { // Connect app exit to qml window exit handling mainApp.closing.connect(appWindow.close); - if( appWindow.qrScannerEnabled ){ + if(builtWithScanner && appWindow.qrScannerEnabled){ console.log("qrScannerEnabled : load component QRCodeScanner"); var component = Qt.createComponent("components/QRCodeScanner.qml"); if (component.status == Component.Ready) { @@ -1340,7 +1378,26 @@ ApplicationWindow { console.log("component not READY !!!"); appWindow.qrScannerEnabled = false; } - } else console.log("qrScannerEnabled disabled"); + } + + if(builtWithOtsUr && appWindow.qrScannerEnabled){ + var urScanComponent = Qt.createComponent("components/UrCodeScanner.qml"); + console.warn(urScanComponent.errorString()) + if (urScanComponent.status == Component.Ready) { + urScannerUi = urScanComponent.createObject(appWindow); + } else { + console.warn("UR Scanner component not READY !!!"); + } + } + + if(builtWithOtsUr) { + var urComponent = Qt.createComponent("components/UrCode.qml"); + if (urComponent.status == Component.Ready) { + urDisplay = urComponent.createObject(appWindow); + } else { + console.warn("UR Display component not READY !!!"); + } + } if(!walletsFound()) { wizard.wizardState = "wizardLanguage"; @@ -1439,6 +1496,8 @@ ApplicationWindow { property int lockOnUserInActivityInterval: 10 // minutes property bool blackTheme: MoneroComponents.Style.blackTheme property bool checkForUpdates: true + property bool useURCode: false + property string lastUsedCamera: "" property bool autosave: true property int autosaveMinutes: 10 property bool pruneBlockchain: false @@ -1584,7 +1643,7 @@ ApplicationWindow { onAccepted: { var handleAccepted = function() { // Save transaction to file if view only wallet - if (viewOnly) { + if (viewOnly && !persistentSettings.useURCode) { saveTxDialog.open(); } else { handleTransactionConfirmed() @@ -1605,7 +1664,7 @@ ApplicationWindow { passwordDialog.open( "", "", - (appWindow.viewOnly ? qsTr("Save transaction file") : qsTr("Send transaction")) + translationManager.emptyString, + (appWindow.viewOnly ? (persistentSettings.useURCode?qsTr("Show transaction UR code"):qsTr("Save transaction file")) : qsTr("Send transaction")) + translationManager.emptyString, appWindow.viewOnly ? "" : FontAwesome.arrowCircleRight); } } @@ -2131,9 +2190,13 @@ ApplicationWindow { onClosing: { close.accepted = false; console.log("blocking close event"); + if(builtWithOtsUr) { + urScannerUi.cancel() + urDisplay.close() + } if(isAndroid) { console.log("blocking android exit"); - if(qrScannerEnabled) + if(qrScannerEnabled && builtWithScanner) cameraUi.state = "Stopped" if(!androidCloseTapped) { @@ -2143,8 +2206,6 @@ ApplicationWindow { // first close return; } - - } // If daemon is running - prompt user before exiting diff --git a/monero b/monero index b089f9ee69..7e73ecd120 160000 --- a/monero +++ b/monero @@ -1 +1 @@ -Subproject commit b089f9ee69924882c5d14dd1a6991deb05d9d1cd +Subproject commit 7e73ecd120795fd1abe95810ad56053c948dcc8c diff --git a/pages/Transfer.qml b/pages/Transfer.qml index 66e0b66b49..bb898aa15b 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -300,8 +300,15 @@ Rectangle { visible: appWindow.qrScannerEnabled tooltip: qsTr("Scan QR code") + translationManager.emptyString onClicked: { - cameraUi.state = "Capture" - cameraUi.qrcode_decoded.connect(updateFromQrCode) + if(builtWithOtsUr) { + urScannerUi.txData.connect(root.txDataFromScanner) + urScannerUi.canceled.connect(root.scanCanceled) + urScannerUi.scanTxData() + + } else { + cameraUi.state = "Capture" + cameraUi.qrcode_decoded.connect(updateFromQrCode) + } } } @@ -881,13 +888,24 @@ Rectangle { button1.enabled: appWindow.viewOnly button1.onClicked: { console.log("Transfer: export outputs clicked") - exportOutputsDialog.open(); + if(persistentSettings.useURCode) { + var outputs = currentWallet.exportOutputsAsString(true); + urDisplay.showOutputs(outputs) + } else { + exportOutputsDialog.open(); + } } button2.text: qsTr("Import") + translationManager.emptyString button2.enabled: !appWindow.viewOnly button2.onClicked: { console.log("Transfer: import outputs clicked") - importOutputsDialog.open(); + if(persistentSettings.useURCode) { + urScannerUi.outputs.connect(root.importOutputs) + urScannerUi.canceled.connect(root.scanCanceled) + urScannerUi.scanOutputs() + } else { + importOutputsDialog.open(); + } } tooltip: { var header = qsTr("Required for cold wallets to sign their corresponding key images") + translationManager.emptyString; @@ -907,13 +925,24 @@ Rectangle { button1.enabled: !appWindow.viewOnly button1.onClicked: { console.log("Transfer: export key images clicked") - exportKeyImagesDialog.open(); + if(persistentSettings.useURCode) { + var keyImages = currentWallet.exportKeyImagesAsString(true); + urDisplay.showKeyImages(keyImages) + } else { + exportKeyImagesDialog.open(); + } } button2.text: qsTr("Import") + translationManager.emptyString button2.enabled: appWindow.viewOnly && appWindow.isTrustedDaemon() button2.onClicked: { console.log("Transfer: import key images clicked") - importKeyImagesDialog.open(); + if(persistentSettings.useURCode) { + urScannerUi.keyImages.connect(root.importKeyImages) + urScannerUi.canceled.connect(root.scanCanceled) + urScannerUi.scanKeyImages() + } else { + importKeyImagesDialog.open(); + } } tooltip: { var errorMessage = ""; @@ -946,13 +975,25 @@ Rectangle { button2.enabled: !appWindow.viewOnly button2.onClicked: { console.log("Transfer: sign tx clicked") - signTxDialog.open(); + if(persistentSettings.useURCode) { + urScannerUi.canceled.connect(root.scanCanceled) + urScannerUi.unsignedTx.connect(root.signTx) + urScannerUi.scanUnsignedTx() + } else { + signTxDialog.open(); + } } button3.text: qsTr("Submit") + translationManager.emptyString button3.enabled: appWindow.viewOnly button3.onClicked: { console.log("Transfer: submit tx clicked") - submitTxDialog.open(); + if(persistentSettings.useURCode) { + urScannerUi.canceled.connect(root.scanCanceled) + urScannerUi.signedTx.connect(root.submitTx) + urScannerUi.scanSignedTx() + } else { + submitTxDialog.open(); + } } tooltip: { var errorMessage = ""; @@ -1029,7 +1070,7 @@ Rectangle { } } - //SignTxDialog + //SubmitTxDialog FileDialog { id: submitTxDialog title: qsTr("Please choose a file") + translationManager.emptyString @@ -1189,4 +1230,87 @@ Rectangle { fillPaymentDetails(address, paymentId, amount, description); } + + function txDataFromScanner(txData) { + disconnectCameraUi() + middlePanel.state = 'Transfer'; + fillPaymentDetails(txData.address, txData.payment_id, txData.amount, txData.description); + } + + function signTx(tx) { + disconnectCameraUi() + transaction = currentWallet.loadTxString(tx) + if (transaction.status !== PendingTransaction.Status_Ok) { + console.error("Can't load unsigned transaction: ", transaction.errorString); + informationPopup.title = qsTr("Error") + translationManager.emptyString; + informationPopup.text = qsTr("Can't load unsigned transaction: ") + transaction.errorString + informationPopup.icon = StandardIcon.Critical + informationPopup.onCloseCallback = null + informationPopup.open(); + // deleting transaction object, we don't want memleaks + transaction.destroy(); + } else { + confirmationDialog.text = qsTr("\nConfirmation message:\n ") + transaction.confirmationMessage + console.log(transaction.confirmationMessage); + + // Show confirmation dialog + confirmationDialog.title = qsTr("Confirmation") + translationManager.emptyString + confirmationDialog.icon = StandardIcon.Question + confirmationDialog.onAcceptedCallback = function() { + var signed_tx = transaction.signAsString(); + transaction.destroy(); + urDisplay.showSignedTx(signed_tx) + }; + confirmationDialog.onRejectedCallback = transaction.destroy; + + confirmationDialog.open() + } + } + + function submitTx(tx) { + disconnectCameraUi() + if(!currentWallet.submitTxString(tx)){ + informationPopup.title = qsTr("Error") + translationManager.emptyString; + informationPopup.text = qsTr("Can't submit transaction: ") + currentWallet.errorString + informationPopup.icon = StandardIcon.Critical + informationPopup.onCloseCallback = null + informationPopup.open(); + } else { + informationPopup.title = qsTr("Information") + translationManager.emptyString + informationPopup.text = qsTr("Monero sent successfully") + translationManager.emptyString + informationPopup.icon = StandardIcon.Information + informationPopup.onCloseCallback = null + informationPopup.open(); + } + } + + function importOutputs(outputs) { + disconnectCameraUi() + if (currentWallet.importOutputsFromString(outputs)) { + appWindow.showStatusMessage(qsTr("Outputs successfully imported to wallet") + translationManager.emptyString, 3); + } else { + appWindow.showStatusMessage(currentWallet.errorString, 5); + } + } + + function importKeyImages(keyImages) { + disconnectCameraUi() + if (currentWallet.importKeyImagesFromString(keyImages)) { + appWindow.showStatusMessage(qsTr("Key images successfully imported to wallet") + translationManager.emptyString, 3); + } else { + appWindow.showStatusMessage(currentWallet.errorString, 5); + } + } + + function scanCanceled() { + disconnectCameraUi() + } + + function disconnectCameraUi() { + urScannerUi.outputs.disconnect(root.importOutputs) + urScannerUi.canceled.disconnect(root.scanCanceled) + urScannerUi.keyImages.disconnect(root.importKeyImages) + urScannerUi.unsignedTx.disconnect(root.signTx) + urScannerUi.signedTx.disconnect(root.submitTx) + } } diff --git a/pages/settings/SettingsLayout.qml b/pages/settings/SettingsLayout.qml index c2dfc7e662..9eb859521c 100644 --- a/pages/settings/SettingsLayout.qml +++ b/pages/settings/SettingsLayout.qml @@ -66,6 +66,15 @@ Rectangle { text: qsTr("Check for updates periodically") + translationManager.emptyString } + MoneroComponents.CheckBox { + id: useUrCheckBox + visible: builtWithOtsUr + enabled: builtWithOtsUr + checked: persistentSettings.useURCode && builtWithOtsUr + onClicked: persistentSettings.useURCode = !persistentSettings.useURCode + text: qsTr("Use UR Code instead of files for cold wallet") + translationManager.emptyString + } + MoneroComponents.CheckBox { checked: persistentSettings.displayWalletNameInTitleBar onClicked: persistentSettings.displayWalletNameInTitleBar = !persistentSettings.displayWalletNameInTitleBar diff --git a/qml.qrc b/qml.qrc index d4bd45a618..be8bc34d3e 100644 --- a/qml.qrc +++ b/qml.qrc @@ -229,6 +229,8 @@ images/world-flags-globe.png images/restore-wallet-from-hardware@2x.png images/restore-wallet-from-hardware.png + images/restore-wallet-from-qr@2x.png + images/restore-wallet-from-qr.png images/open-wallet-from-file@2x.png images/open-wallet-from-file.png images/open-wallet-from-file-mainnet@2x.png @@ -298,5 +300,7 @@ wizard/SeedListItem.qml wizard/SeedListGrid.qml wizard/template.pdf + components/UrCode.qml + components/UrCodeScanner.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b5718df3a2..218930b54e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -103,6 +103,7 @@ target_include_directories(monero-wallet-gui PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/libwalletqt ${CMAKE_CURRENT_SOURCE_DIR}/model ${CMAKE_CURRENT_SOURCE_DIR}/QR-Code-scanner + ${CMAKE_CURRENT_SOURCE_DIR}/ur/qtquick ${CMAKE_CURRENT_SOURCE_DIR}/zxcvbn-c ${X11_INCLUDE_DIR} ) @@ -154,6 +155,11 @@ if(WITH_SCANNER) endif() endif() +if(WITH_OTS_UR) + add_subdirectory(otsur) + target_link_libraries(monero-wallet-gui otsqtquick) +endif() + add_custom_command(TARGET monero-wallet-gui POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy $ $) include(Deploy) diff --git a/src/libwalletqt/PendingTransaction.cpp b/src/libwalletqt/PendingTransaction.cpp index 72c934ff68..548160ecb8 100644 --- a/src/libwalletqt/PendingTransaction.cpp +++ b/src/libwalletqt/PendingTransaction.cpp @@ -47,6 +47,11 @@ bool PendingTransaction::commit() return m_pimpl->commit(m_fileName.toStdString()); } +QByteArray PendingTransaction::commitAsString() const +{ + return QByteArray::fromStdString(m_pimpl->commit_string()); +} + quint64 PendingTransaction::amount() const { return m_pimpl->amount(); diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h index 08b6a55776..27148a3de3 100644 --- a/src/libwalletqt/PendingTransaction.h +++ b/src/libwalletqt/PendingTransaction.h @@ -70,6 +70,7 @@ class PendingTransaction : public QObject Status status() const; QString errorString() const; Q_INVOKABLE bool commit(); + Q_INVOKABLE QByteArray commitAsString() const; quint64 amount() const; quint64 dust() const; quint64 fee() const; diff --git a/src/libwalletqt/UnsignedTransaction.cpp b/src/libwalletqt/UnsignedTransaction.cpp index fcd425634e..0b8936bdbc 100644 --- a/src/libwalletqt/UnsignedTransaction.cpp +++ b/src/libwalletqt/UnsignedTransaction.cpp @@ -103,6 +103,11 @@ bool UnsignedTransaction::sign(const QString &fileName) const return m_walletImpl->exportKeyImages(fileName.toStdString() + "_keyImages"); } +QByteArray UnsignedTransaction::signAsString() const +{ + return QByteArray::fromStdString(m_pimpl->signAsString()); +} + void UnsignedTransaction::setFilename(const QString &fileName) { m_fileName = fileName; diff --git a/src/libwalletqt/UnsignedTransaction.h b/src/libwalletqt/UnsignedTransaction.h index 1ce7186beb..7257638558 100644 --- a/src/libwalletqt/UnsignedTransaction.h +++ b/src/libwalletqt/UnsignedTransaction.h @@ -65,6 +65,7 @@ class UnsignedTransaction : public QObject QString confirmationMessage() const; quint64 minMixinCount() const; Q_INVOKABLE bool sign(const QString &fileName) const; + Q_INVOKABLE QByteArray signAsString() const; Q_INVOKABLE void setFilename(const QString &fileName); private: diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index cec4ff63c8..1bfe637956 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -506,15 +506,33 @@ bool Wallet::exportKeyImages(const QString& path, bool all) return m_walletImpl->exportKeyImages(path.toStdString(), all); } +QByteArray Wallet::exportKeyImagesAsString(bool all) +{ + return QByteArray::fromStdString(m_walletImpl->exportKeyImagesAsString(all)); +} + bool Wallet::importKeyImages(const QString& path) { return m_walletImpl->importKeyImages(path.toStdString()); } +bool Wallet::importKeyImagesFromString(const QByteArray& data) +{ + return m_walletImpl->importKeyImagesFromString(data.toStdString()); +} + +QByteArray Wallet::exportOutputsAsString(bool all) const { + return QByteArray::fromStdString(m_walletImpl->exportOutputsAsString(all)); +} + bool Wallet::exportOutputs(const QString& path, bool all) { return m_walletImpl->exportOutputs(path.toStdString(), all); } +bool Wallet::importOutputsFromString(const QByteArray& data) { + return m_walletImpl->importOutputsFromString(data.toStdString()); +} + bool Wallet::importOutputs(const QString& path) { return m_walletImpl->importOutputs(path.toStdString()); } @@ -650,6 +668,14 @@ UnsignedTransaction * Wallet::loadTxFile(const QString &fileName) return result; } +UnsignedTransaction * Wallet::loadTxString(const QByteArray &data) +{ + qDebug() << "Trying to sign"; + Monero::UnsignedTransaction * ptImpl = m_walletImpl->loadUnsignedTxFromString(data.toStdString()); + UnsignedTransaction * result = new UnsignedTransaction(ptImpl, m_walletImpl, this); + return result; +} + bool Wallet::submitTxFile(const QString &fileName) const { qDebug() << "Trying to submit " << fileName; @@ -659,6 +685,12 @@ bool Wallet::submitTxFile(const QString &fileName) const return m_walletImpl->importKeyImages(fileName.toStdString() + "_keyImages"); } +bool Wallet::submitTxString(const QByteArray &data) const +{ + qDebug() << "Trying to submit"; + return m_walletImpl->submitTransactionFromString(data.toStdString()); // TODO: Why importing key images? This had should be done before... (remove this comment after clarification) +} + void Wallet::commitTransactionAsync(PendingTransaction *t) { m_scheduler.run([this, t] { @@ -667,6 +699,14 @@ void Wallet::commitTransactionAsync(PendingTransaction *t) }); } +void Wallet::commitTransactionForExportAsync(PendingTransaction *t) +{ + m_scheduler.run([this, t] { + auto txIdList = t->txid(); // retrieve before commit + emit transactionCommittedForExport(t->commitAsString(), t, txIdList); + }); +} + void Wallet::disposeTransaction(PendingTransaction *t) { m_walletImpl->disposeTransaction(t->m_pimpl); diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 398d19c622..f7947b8273 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -205,10 +205,14 @@ class Wallet : public QObject, public PassprasePrompter Q_INVOKABLE void refreshHeightAsync(); //! export/import key images + Q_INVOKABLE QByteArray exportKeyImagesAsString(bool all = false); + Q_INVOKABLE bool importKeyImagesFromString(const QByteArray &data); Q_INVOKABLE bool exportKeyImages(const QString& path, bool all = false); Q_INVOKABLE bool importKeyImages(const QString& path); //! export/import outputs + Q_INVOKABLE QByteArray exportOutputsAsString(bool all = false) const; + Q_INVOKABLE bool importOutputsFromString(const QByteArray& data); Q_INVOKABLE bool exportOutputs(const QString& path, bool all = false); Q_INVOKABLE bool importOutputs(const QString& path); @@ -245,14 +249,19 @@ class Wallet : public QObject, public PassprasePrompter Q_INVOKABLE void createSweepUnmixableTransactionAsync(); //! Sign a transfer from file + Q_INVOKABLE UnsignedTransaction * loadTxString(const QByteArray &data); Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName); //! Submit a transfer from file + Q_INVOKABLE bool submitTxString(const QByteArray &data) const; Q_INVOKABLE bool submitTxFile(const QString &fileName) const; //! asynchronous transaction commit Q_INVOKABLE void commitTransactionAsync(PendingTransaction * t); + //! asynchronous transaction commit for export + Q_INVOKABLE void commitTransactionForExportAsync(PendingTransaction * t); + //! deletes transaction and frees memory Q_INVOKABLE void disposeTransaction(PendingTransaction * t); @@ -376,6 +385,7 @@ class Wallet : public QObject, public PassprasePrompter void deviceButtonPressed(); void walletPassphraseNeeded(bool onDevice); void transactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); + void transactionCommittedForExport(const QByteArray &txAsString, PendingTransaction *t, const QStringList& txid); void heightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) const; void deviceShowAddressShowed(); diff --git a/src/main/main.cpp b/src/main/main.cpp index 38901d3ff1..adc9c624e0 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -87,7 +87,10 @@ #include "qt/macoshelper.h" #endif -#ifdef WITH_SCANNER +#ifdef WITH_OTS_UR +#include "otsur/qtquick/UrRegister.h" +// use scanner only if OTS UR is not used, how OTS UR can provide QR Scan, too. +#elif WITH_SCANNER #include "QR-Code-scanner/QrCodeScanner.h" #endif @@ -440,7 +443,9 @@ Verify update binary using 'shasum'-compatible (SHA256 algo) output signed by tw qRegisterMetaType(); qmlRegisterType("moneroComponents.NetworkType", 1, 0, "NetworkType"); -#ifdef WITH_SCANNER +#ifdef WITH_OTS_UR + OtsUr::registerTypes(); +#elif WITH_SCANNER qmlRegisterType("moneroComponents.QRCodeScanner", 1, 0, "QRCodeScanner"); #endif @@ -518,10 +523,15 @@ Verify update binary using 'shasum'-compatible (SHA256 algo) output signed by tw engine.rootContext()->setContextProperty("socksProxyFlagSet", parser.isSet(socksProxyOption)); bool builtWithScanner = false; -#ifdef WITH_SCANNER + bool builtWithOtsUr = false; +#ifdef WITH_OTS_UR + builtWithOtsUr = true; + OtsUr::setupContext(engine); +#elif WITH_SCANNER builtWithScanner = true; #endif engine.rootContext()->setContextProperty("builtWithScanner", builtWithScanner); + engine.rootContext()->setContextProperty("builtWithOtsUr", builtWithOtsUr); bool builtWithDesktopEntry = false; #ifdef WITH_DESKTOP_ENTRY @@ -549,7 +559,9 @@ Verify update binary using 'shasum'-compatible (SHA256 algo) output signed by tw if (parser.isSet(testQmlOption)) return 0; -#ifdef WITH_SCANNER +#if WITH_OTS_UR + OtsUr::setupCamera(engine); +#elif WITH_SCANNER QObject *qmlCamera = rootObject->findChild("qrCameraQML"); if (qmlCamera) { diff --git a/src/otsur/CMakeLists.txt b/src/otsur/CMakeLists.txt new file mode 100644 index 0000000000..012ec2d20e --- /dev/null +++ b/src/otsur/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.10) +project(libotsur VERSION 0.1 LANGUAGES C CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +option(USE_EXTERNAL_QRENCODE "Use an external qrencode library" OFF) + +add_subdirectory(bcur) +add_subdirectory(qrcode) +add_subdirectory(data) +add_subdirectory(qtquick) +if(NOT DEFINED USE_EXTERNAL_QRENCODE OR NOT USE_EXTERNAL_QRENCODE) + add_subdirectory(qrencode) +endif() diff --git a/src/otsur/bcur/CMakeLists.txt b/src/otsur/bcur/CMakeLists.txt new file mode 100644 index 0000000000..344af14cc2 --- /dev/null +++ b/src/otsur/bcur/CMakeLists.txt @@ -0,0 +1,34 @@ +set(BCUR_SOURCE + bytewords.cpp + crc32.c + fountain-decoder.cpp + fountain-encoder.cpp + fountain-utils.cpp + memzero.c + random-sampler.cpp + sha2.c + ur.cpp + ur-decoder.cpp + ur-encoder.cpp + utils.cpp + xoshiro256.cpp) +set(BCUR_HEADERS + bc-ur.hpp + bytewords.hpp + cbor-lite.hpp + crc32.h + fountain-decoder.hpp + fountain-encoder.hpp + fountain-utils.hpp + memzero.h + random-sampler.hpp + sha2.h + ur-decoder.hpp + ur-encoder.hpp + ur.hpp + utils.hpp + xoshiro256.hpp +) +add_library(bcur STATIC ${BCUR_SOURCE} ${BCUR_HEADERS}) +target_include_directories(bcur PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/src/otsur/bcur/README.md b/src/otsur/bcur/README.md new file mode 100644 index 0000000000..478fce3667 --- /dev/null +++ b/src/otsur/bcur/README.md @@ -0,0 +1,2 @@ +vendored from https://github.com/BlockchainCommons/bc-ur +2bfc3fd396498c2519273aeaa732abf7ea7d24b8 \ No newline at end of file diff --git a/src/otsur/bcur/bc-ur.hpp b/src/otsur/bcur/bc-ur.hpp new file mode 100644 index 0000000000..2f695ee77f --- /dev/null +++ b/src/otsur/bcur/bc-ur.hpp @@ -0,0 +1,28 @@ +// +// bc-ur.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_HPP +#define BC_UR_HPP + +#include "ur.hpp" +#include "ur-encoder.hpp" +#include "ur-decoder.hpp" +#include "fountain-encoder.hpp" +#include "fountain-decoder.hpp" +#include "fountain-utils.hpp" +#include "utils.hpp" +#include "bytewords.hpp" +#include "xoshiro256.hpp" +#include "random-sampler.hpp" + +namespace ur { + +#include "cbor-lite.hpp" + +} + +#endif // BC_UR_HPP diff --git a/src/otsur/bcur/bytewords.cpp b/src/otsur/bcur/bytewords.cpp new file mode 100644 index 0000000000..90ca65ad21 --- /dev/null +++ b/src/otsur/bcur/bytewords.cpp @@ -0,0 +1,170 @@ +// +// bytewords.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "bytewords.hpp" +#include "utils.hpp" +#include +#include +#include + +namespace ur { + +using namespace std; + +static const char* bytewords = "ableacidalsoapexaquaarchatomauntawayaxisbackbaldbarnbeltbetabiasbluebodybragbrewbulbbuzzcalmcashcatschefcityclawcodecolacookcostcruxcurlcuspcyandarkdatadaysdelidicedietdoordowndrawdropdrumdulldutyeacheasyechoedgeepicevenexamexiteyesfactfairfernfigsfilmfishfizzflapflewfluxfoxyfreefrogfuelfundgalagamegeargemsgiftgirlglowgoodgraygrimgurugushgyrohalfhanghardhawkheathelphighhillholyhopehornhutsicedideaidleinchinkyintoirisironitemjadejazzjoinjoltjowljudojugsjumpjunkjurykeepkenokeptkeyskickkilnkingkitekiwiknoblamblavalazyleaflegsliarlimplionlistlogoloudloveluaulucklungmainmanymathmazememomenumeowmildmintmissmonknailnavyneednewsnextnoonnotenumbobeyoboeomitonyxopenovalowlspaidpartpeckplaypluspoempoolposepuffpumapurrquadquizraceramprealredorichroadrockroofrubyruinrunsrustsafesagascarsetssilkskewslotsoapsolosongstubsurfswantacotasktaxitenttiedtimetinytoiltombtoystriptunatwinuglyundouniturgeuservastveryvetovialvibeviewvisavoidvowswallwandwarmwaspwavewaxywebswhatwhenwhizwolfworkyankyawnyellyogayurtzapszerozestzinczonezoom"; + +uint8_t decode_word(const string& word, size_t word_len) { + if(word.length() != word_len) { + throw runtime_error("Invalid Bytewords."); + } + + static int16_t* array = NULL; + const size_t dim = 26; + + // Since the first and last letters of each Byteword are unique, + // we can use them as indexes into a two-dimensional lookup table. + // This table is generated lazily. + if(array == NULL) { + const size_t array_len = dim * dim; + array = (int16_t*)malloc(array_len * sizeof(int16_t)); + for(size_t i = 0; i < array_len; i++) { + array[i] = -1; + } + for(size_t i = 0; i < 256; i++) { + const char* byteword = bytewords + i * 4; + size_t x = byteword[0] - 'a'; + size_t y = byteword[3] - 'a'; + size_t offset = y * dim + x; + array[offset] = i; + } + } + + // If the coordinates generated by the first and last letters are out of bounds, + // or the lookup table contains -1 at the coordinates, then the word is not valid. + int x = tolower(word[0]) - 'a'; + int y = tolower(word[word_len == 4 ? 3 : 1]) - 'a'; + if(!(0 <= x && x < dim && 0 <= y && y < dim)) { + throw runtime_error("Invalid Bytewords."); + } + size_t offset = y * dim + x; + int16_t value = array[offset]; + if(value == -1) { + throw runtime_error("Invalid Bytewords."); + } + + // If we're decoding a full four-letter word, verify that the two middle letters are correct. + if(word_len == 4) { + const char* byteword = bytewords + value * 4; + int c1 = tolower(word[1]); + int c2 = tolower(word[2]); + if(c1 != byteword[1] || c2 != byteword[2]) { + throw runtime_error("Invalid Bytewords."); + } + } + + // Successful decode. + return value; +} + +static const string get_word(uint8_t index) { + auto p = &bytewords[index * 4]; + return string(p, p + 4); +} + +static const string get_minimal_word(uint8_t index) { + string word; + word.reserve(2); + auto p = &bytewords[index * 4]; + word.push_back(*p); + word.push_back(*(p + 3)); + return word; +} + +static const string encode(const ByteVector& buf, const string& separator) { + auto len = buf.size(); + StringVector words; + words.reserve(len); + for(int i = 0; i < len; i++) { + auto byte = buf[i]; + words.push_back(get_word(byte)); + } + return join(words, separator); +} + +static const ByteVector add_crc(const ByteVector& buf) { + auto crc_buf = crc32_bytes(buf); + auto result = buf; + append(result, crc_buf); + return result; +} + +static const string encode_with_separator(const ByteVector& buf, const string& separator) { + auto crc_buf = add_crc(buf); + return encode(crc_buf, separator); +} + +static const string encode_minimal(const ByteVector& buf) { + string result; + auto crc_buf = add_crc(buf); + auto len = crc_buf.size(); + for(int i = 0; i < len; i++) { + auto byte = crc_buf[i]; + result.append(get_minimal_word(byte)); + } + return result; +} + +static const ByteVector _decode(const string& s, char separator, size_t word_len) { + StringVector words; + if(word_len == 4) { + words = split(s, separator); + } else { + words = partition(s, 2); + } + ByteVector buf; + transform(words.begin(), words.end(), back_inserter(buf), [&](auto word) { return decode_word(word, word_len); }); + if(buf.size() < 5) { + throw runtime_error("Invalid Bytewords."); + } + auto p = split(buf, buf.size() - 4); + auto body = p.first; + auto body_checksum = p.second; + auto checksum = crc32_bytes(body); + if(checksum != body_checksum) { + throw runtime_error("Invalid Bytewords."); + } + + return body; +} + +string Bytewords::encode(style style, const ByteVector& bytes) { + switch(style) { + case standard: + return encode_with_separator(bytes, " "); + case uri: + return encode_with_separator(bytes, "-"); + case minimal: + return encode_minimal(bytes); + default: + assert(false); + } +} + +ByteVector Bytewords::decode(style style, const string& string) { + switch(style) { + case standard: + return _decode(string, ' ', 4); + case uri: + return _decode(string, '-', 4); + case minimal: + return _decode(string, 0, 2); + default: + assert(false); + } +} + +} diff --git a/src/otsur/bcur/bytewords.hpp b/src/otsur/bcur/bytewords.hpp new file mode 100644 index 0000000000..427f2c32f0 --- /dev/null +++ b/src/otsur/bcur/bytewords.hpp @@ -0,0 +1,30 @@ +// +// bytewords.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_BYTEWORDS_HPP +#define BC_UR_BYTEWORDS_HPP + +#include +#include "utils.hpp" + +namespace ur { + +class Bytewords final { +public: + enum style { + standard, + uri, + minimal + }; + + static std::string encode(style style, const ByteVector& bytes); + static ByteVector decode(style style, const std::string& string); +}; + +} + +#endif // BC_UR_BYTEWORDS_HPP diff --git a/src/otsur/bcur/cbor-lite.hpp b/src/otsur/bcur/cbor-lite.hpp new file mode 100644 index 0000000000..267474a9b4 --- /dev/null +++ b/src/otsur/bcur/cbor-lite.hpp @@ -0,0 +1,558 @@ +#ifndef BC_UR_CBOR_LITE_HPP +#define BC_UR_CBOR_LITE_HPP + +// From: https://bitbucket.org/isode/cbor-lite/raw/6c770624a97e3229e3f200be092c1b9c70a60ef1/include/cbor-lite/codec.h + +// This file is part of CBOR-lite which is copyright Isode Limited +// and others and released under a MIT license. For details, see the +// COPYRIGHT.md file in the top-level folder of the CBOR-lite software +// distribution. + +#include +#include +#include +#include +#include + +#ifndef __BYTE_ORDER__ +#error __BYTE_ORDER__ not defined +#elif (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) && (__BYTE_ORDER__ != __ORDER_BIG_ENDIAN__) +#error __BYTE_ORDER__ neither __ORDER_BIG_ENDIAN__ nor __ORDER_LITTLE_ENDIAN__ +#endif + +namespace CborLite { + +class Exception : public std::exception { +public: + Exception() noexcept { + } + virtual ~Exception() noexcept = default; + + explicit Exception(const char* d) noexcept { + what_ += std::string(": ") + d; + } + + explicit Exception(const std::string& d) noexcept { + what_ += ": " + d; + } + + Exception(const Exception& e) noexcept : what_(e.what_) { + } + + Exception(Exception&& e) noexcept : what_(std::move(e.what_)) { + // Note that e.what_ is not re-initialized to "CBOR Exception" as + // the moved-from object is not expected to ever be reused. + } + + Exception& operator=(const Exception&) = delete; + Exception& operator=(Exception&&) = delete; + + virtual const char* what() const noexcept { + return what_.c_str(); + } + +private: + std::string what_ = "CBOR Exception"; +}; + +using Tag = std::uint_fast64_t; + +namespace Major { +constexpr Tag unsignedInteger = 0u; +constexpr Tag negativeInteger = 1u << 5; +constexpr Tag byteString = 2u << 5; +constexpr Tag textString = 3u << 5; +constexpr Tag array = 4u << 5; +constexpr Tag map = 5u << 5; +constexpr Tag semantic = 6u << 5; +constexpr Tag floatingPoint = 7u << 5; +constexpr Tag simple = 7u << 5; +constexpr Tag mask = 0xe0u; +} // namespace Major + +namespace Minor { +constexpr Tag length1 = 24u; +constexpr Tag length2 = 25u; +constexpr Tag length4 = 26u; +constexpr Tag length8 = 27u; + +constexpr Tag False = 20u; +constexpr Tag True = 21u; +constexpr Tag null = 22u; +constexpr Tag undefined = 23u; +constexpr Tag halfFloat = 25u; // not implemented +constexpr Tag singleFloat = 26u; +constexpr Tag doubleFloat = 27u; + +constexpr Tag dataTime = 0u; +constexpr Tag epochDataTime = 1u; +constexpr Tag positiveBignum = 2u; +constexpr Tag negativeBignum = 3u; +constexpr Tag decimalFraction = 4u; +constexpr Tag bigfloat = 5u; +constexpr Tag convertBase64Url = 21u; +constexpr Tag convertBase64 = 22u; +constexpr Tag convertBase16 = 23u; +constexpr Tag cborEncodedData = 24u; +constexpr Tag uri = 32u; +constexpr Tag base64Url = 33u; +constexpr Tag base64 = 34u; +constexpr Tag regex = 35u; +constexpr Tag mimeMessage = 36u; +constexpr Tag selfDescribeCbor = 55799u; + +constexpr Tag mask = 0x1fu; +} // namespace Minor + +constexpr Tag undefined = Major::semantic + Minor::undefined; + +using Flags = unsigned; +namespace Flag { +constexpr Flags none = 0; +constexpr Flags requireMinimalEncoding = 1 << 0; +} // namespace Flag + +template +typename std::enable_if::value, std::size_t>::type length(Type val) { + if (val < 24) return 0; + for (std::size_t i = 1; i <= ((sizeof val) >> 1); i <<= 1) { + if (!(val >> (i << 3))) return i; + } + return sizeof val; +} + +template +typename std::enable_if::value, std::size_t>::type encodeTagAndAdditional( + Buffer& buffer, Tag tag, Tag additional) { + buffer.push_back(static_cast(tag + additional)); + return 1; +} + +template +typename std::enable_if::value, std::size_t>::type decodeTagAndAdditional( + InputIterator& pos, InputIterator end, Tag& tag, Tag& additional, Flags = Flag::none) { + if (pos == end) throw Exception("not enough input"); + auto octet = *(pos++); + tag = octet & Major::mask; + additional = octet & Minor::mask; + return 1; +} + +template +typename std::enable_if::value && std::is_unsigned::value, std::size_t>::type encodeTagAndValue( + Buffer& buffer, Tag tag, const Type t) { + auto len = length(t); + buffer.reserve(buffer.size() + len + 1); + + switch (len) { + case 8: + encodeTagAndAdditional(buffer, tag, Minor::length8); + break; + case 4: + encodeTagAndAdditional(buffer, tag, Minor::length4); + break; + case 2: + encodeTagAndAdditional(buffer, tag, Minor::length2); + break; + case 1: + encodeTagAndAdditional(buffer, tag, Minor::length1); + break; + case 0: + return encodeTagAndAdditional(buffer, tag, t); + default: + throw Exception("too long"); + } + + switch (len) { + case 8: + buffer.push_back((t >> 56) & 0xffU); + buffer.push_back((t >> 48) & 0xffU); + buffer.push_back((t >> 40) & 0xffU); + buffer.push_back((t >> 32) & 0xffU); + case 4: + buffer.push_back((t >> 24) & 0xffU); + buffer.push_back((t >> 16) & 0xffU); + case 2: + buffer.push_back((t >> 8) & 0xffU); + case 1: + buffer.push_back(t & 0xffU); + } + + return 1 + len; +} + +template +typename std::enable_if::value && std::is_unsigned::value, std::size_t>::type decodeTagAndValue( + InputIterator& pos, InputIterator end, Tag& tag, Type& t, Flags flags = Flag::none) { + if (pos == end) throw Exception("not enough input"); + auto additional = Minor::undefined; + auto len = decodeTagAndAdditional(pos, end, tag, additional, flags); + if (additional < Minor::length1) { + t = additional; + return len; + } + t = 0u; + switch (additional) { + case Minor::length8: + if (std::distance(pos, end) < 8) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 56; + t |= static_cast(reinterpret_cast(*(pos++))) << 48; + t |= static_cast(reinterpret_cast(*(pos++))) << 40; + t |= static_cast(reinterpret_cast(*(pos++))) << 32; + len += 4; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length4: + if (std::distance(pos, end) < 4) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 24; + t |= static_cast(reinterpret_cast(*(pos++))) << 16; + len += 2; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length2: + if (std::distance(pos, end) < 2) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))) << 8; + len++; + if ((flags & Flag::requireMinimalEncoding) && !t) throw Exception("encoding not minimal"); + case Minor::length1: + if (std::distance(pos, end) < 1) throw Exception("not enough input"); + t |= static_cast(reinterpret_cast(*(pos++))); + len++; + if ((flags & Flag::requireMinimalEncoding) && t < 24) throw Exception("encoding not minimal"); + return len; + } + throw Exception("bad additional value"); +} + +template +typename std::enable_if::value, std::size_t>::type encodeUnsigned(Buffer& buffer, const Type& t) { + return encodeTagAndValue(buffer, Major::unsignedInteger, t); +} + +template +typename std::enable_if::value && std::is_unsigned::value && !std::is_const::value, + std::size_t>::type +decodeUnsigned(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto len = decodeTagAndValue(pos, end, tag, t, flags); + if (tag != Major::unsignedInteger) throw Exception("not Unsigned"); + return len; +} + +template +typename std::enable_if::value, std::size_t>::type encodeNegative(Buffer& buffer, const Type& t) { + return encodeTagAndValue(buffer, Major::negativeInteger, t); +} + +template +typename std::enable_if::value && std::is_unsigned::value && !std::is_const::value, + std::size_t>::type +decodeNegative(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto len = decodeTagAndValue(pos, end, tag, t, flags); + if (tag != Major::negativeInteger) throw Exception("not Unsigned"); + return len; +} + +template +typename std::enable_if::value, std::size_t>::type encodeInteger(Buffer& buffer, const Type& t) { + if (t >= 0) { + unsigned long long val = t; + return encodeUnsigned(buffer, val); + } else { + unsigned long long val = -t - 1; + return encodeNegative(buffer, val); + } +} + +template +typename std::enable_if::value && std::is_signed::value && !std::is_const::value, + std::size_t>::type +decodeInteger(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + unsigned long long val; + auto len = decodeTagAndValue(pos, end, tag, val, flags); + switch (tag) { + case Major::unsignedInteger: + t = val; + break; + case Major::negativeInteger: + t = -1 - static_cast(val); + break; + default: + throw Exception("not integer"); + } + return len; +} + +template +typename std::enable_if::value && std::is_same::value, std::size_t>::type encodeBool( + Buffer& buffer, const Type& t) { + return encodeTagAndAdditional(buffer, Major::simple, t ? Minor::True : Minor::False); +} + +template +typename std::enable_if::value && std::is_same::value && !std::is_const::value, + std::size_t>::type +decodeBool(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag == Major::simple) { + if (value == Minor::True) { + t = true; + return len; + } else if (value == Minor::False) { + t = false; + return len; + } + throw Exception("not Boolean"); + } + throw Exception("not Simple"); +} + +template +typename std::enable_if::value, std::size_t>::type encodeBytes(Buffer& buffer, const Type& t) { + auto len = encodeTagAndValue(buffer, Major::byteString, t.size()); + buffer.insert(std::end(buffer), std::begin(t), std::end(t)); + return len + t.size(); +} + +template +typename std::enable_if::value && !std::is_const::value, std::size_t>::type decodeBytes( + InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::byteString) throw Exception("not ByteString"); + + auto dist = std::distance(pos, end); + if (dist < static_cast(value)) throw Exception("not enough input"); + t.insert(std::end(t), pos, pos + value); + std::advance(pos, value); + return len + value; +} + +template +typename std::enable_if::value && std::is_unsigned::value, std::size_t>::type encodeEncodedBytesPrefix( + Buffer& buffer, const Type& t) { + auto len = encodeTagAndValue(buffer, Major::semantic, Minor::cborEncodedData); + return len + encodeTagAndValue(buffer, Major::byteString, t); +} + +template +typename std::enable_if::value && !std::is_const::value, std::size_t>::type +decodeEncodedBytesPrefix(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::semantic || value != Minor::cborEncodedData) { + throw Exception("not CBOR Encoded Data"); + } + tag = undefined; + len += decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::byteString) throw Exception("not ByteString"); + t = value; + return len; +} + +template +typename std::enable_if::value, std::size_t>::type encodeEncodedBytes(Buffer& buffer, const Type& t) { + auto len = encodeTagAndValue(buffer, Major::semantic, Minor::cborEncodedData); + return len + encodeBytes(buffer, t); +} + +template +typename std::enable_if::value && !std::is_const::value, std::size_t>::type decodeEncodedBytes( + InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::semantic || value != Minor::cborEncodedData) { + throw Exception("not CBOR Encoded Data"); + } + return len + decodeBytes(pos, end, t, flags); +} + +template +typename std::enable_if::value, std::size_t>::type encodeText(Buffer& buffer, const Type& t) { + auto len = encodeTagAndValue(buffer, Major::textString, t.size()); + buffer.insert(std::end(buffer), std::begin(t), std::end(t)); + return len + t.size(); +} + +template +typename std::enable_if::value && !std::is_const::value, std::size_t>::type decodeText( + InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::textString) throw Exception("not TextString"); + + auto dist = std::distance(pos, end); + if (dist < static_cast(value)) throw Exception("not enough input"); + t.insert(std::end(t), pos, pos + value); + std::advance(pos, value); + return len + value; +} + +template +typename std::enable_if::value && std::is_unsigned::value, std::size_t>::type encodeArraySize( + Buffer& buffer, const Type& t) { + return encodeTagAndValue(buffer, Major::array, t); +} + +template +typename std::enable_if::value && !std::is_const::value && std::is_unsigned::value, + std::size_t>::type +decodeArraySize(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::array) throw Exception("not Array"); + t = value; + return len; +} + +template +typename std::enable_if::value && std::is_unsigned::value, std::size_t>::type encodeMapSize( + Buffer& buffer, const Type& t) { + return encodeTagAndValue(buffer, Major::map, t); +} + +template +typename std::enable_if::value && !std::is_const::value && std::is_unsigned::value, + std::size_t>::type +decodeMapSize(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndValue(pos, end, tag, value, flags); + if (tag != Major::map) throw Exception("not Map"); + t = value; + return len; +} + +// +// codec-fp.h +// + +template +typename std::enable_if::value && std::is_floating_point::value, std::size_t>::type encodeSingleFloat( + Buffer& buffer, const Type& t) { + static_assert(sizeof(float) == 4, "sizeof(float) expected to be 4"); + auto len = encodeTagAndAdditional(buffer, Major::floatingPoint, Minor::singleFloat); + const char* p; + float ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = static_cast(t); + p = reinterpret_cast(&ft); + } +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + buffer.push_back(p[i]); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + buffer.push_back(p[sizeof(ft) - i]); + } +#endif + return len + sizeof(ft); +} + +template +typename std::enable_if::value && std::is_floating_point::value, std::size_t>::type encodeDoubleFloat( + Buffer& buffer, const Type& t) { + static_assert(sizeof(double) == 8, "sizeof(double) expected to be 8"); + auto len = encodeTagAndAdditional(buffer, Major::floatingPoint, Minor::doubleFloat); + const char* p; + double ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = t; + p = reinterpret_cast(&ft); + } +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + buffer.push_back(p[i]); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + buffer.push_back(p[sizeof(ft) - i]); + } +#endif + return len + sizeof(ft); +} + +template +typename std::enable_if::value && std::is_floating_point::value && !std::is_const::value, + std::size_t>::type +decodeSingleFloat(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + static_assert(sizeof(float) == 4, "sizeof(float) expected to be 4"); + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndAdditional(pos, end, tag, value, flags); + if (tag != Major::floatingPoint) throw Exception("not floating-point"); + if (value != Minor::singleFloat) throw Exception("not single-precision floating-point"); + if (std::distance(pos, end) < static_cast(sizeof(float))) throw Exception("not enough input"); + + char* p; + float ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = static_cast(t); + p = reinterpret_cast(&ft); + } + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + p[i] = *(pos++); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + p[sizeof(ft) - i] = *(pos++); + } +#endif + if (sizeof(t) != sizeof(ft)) t = ft; + return len + sizeof(ft); +} + +template +typename std::enable_if::value && std::is_floating_point::value && !std::is_const::value, + std::size_t>::type +decodeDoubleFloat(InputIterator& pos, InputIterator end, Type& t, Flags flags = Flag::none) { + static_assert(sizeof(double) == 8, "sizeof(double) expected to be 8"); + auto tag = undefined; + auto value = undefined; + auto len = decodeTagAndAdditional(pos, end, tag, value, flags); + if (tag != Major::floatingPoint) throw Exception("not floating-point"); + if (value != Minor::doubleFloat) throw Exception("not double-precision floating-point"); + if (std::distance(pos, end) < static_cast(sizeof(double))) throw Exception("not enough input"); + + char* p; + double ft; + if (sizeof(t) == sizeof(ft)) { + p = reinterpret_cast(&t); + } else { + ft = t; + p = reinterpret_cast(&ft); + } + +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ + for (auto i = 0u; i < sizeof(ft); ++i) { + p[i] = *(pos++); + } +#else + for (auto i = 1u; i <= sizeof(ft); ++i) { + p[sizeof(ft) - i] = *(pos++); + } +#endif + + if (sizeof(t) != sizeof(ft)) t = ft; + return len + sizeof(ft); +} + +} // namespace CborLite + +#endif // BC_UR_CBOR_LITE_HPP diff --git a/src/otsur/bcur/crc32.c b/src/otsur/bcur/crc32.c new file mode 100644 index 0000000000..38e739a7ee --- /dev/null +++ b/src/otsur/bcur/crc32.c @@ -0,0 +1,43 @@ +// +// crc32.c +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "crc32.h" +#include + +#ifdef ARDUINO +#define htonl(x) __builtin_bswap32((uint32_t) (x)) +#elif _WIN32 +#include +#else +#include +#endif + +uint32_t ur_crc32(const uint8_t* bytes, size_t len) { + static uint32_t* table = NULL; + + if(table == NULL) { + table = malloc(256 * sizeof(uint32_t)); + for(int i = 0; i < 256; i++) { + uint32_t c = i; + for(int j = 0; j < 8; j++) { + c = (c % 2 == 0) ? (c >> 1) : (0xEDB88320 ^ (c >> 1)); + } + table[i] = c; + } + } + + uint32_t crc = ~0; + for(int i = 0; i < len; i++) { + uint32_t byte = bytes[i]; + crc = (crc >> 8) ^ table[(crc ^ byte) & 0xFF]; + } + return ~crc; +} + +uint32_t ur_crc32n(const uint8_t* bytes, size_t len) { + return htonl(ur_crc32(bytes, len)); +} diff --git a/src/otsur/bcur/crc32.h b/src/otsur/bcur/crc32.h new file mode 100644 index 0000000000..cfa38ef75f --- /dev/null +++ b/src/otsur/bcur/crc32.h @@ -0,0 +1,28 @@ +// +// crc32.h +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_CRC32_H +#define BC_UR_CRC32_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Returns the CRC-32 checksum of the input buffer. +uint32_t ur_crc32(const uint8_t* bytes, size_t len); + +// Returns the CRC-32 checksum of the input buffer in network byte order (big endian). +uint32_t ur_crc32n(const uint8_t* bytes, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif // BC_UR_CRC32_H diff --git a/src/otsur/bcur/fountain-decoder.cpp b/src/otsur/bcur/fountain-decoder.cpp new file mode 100644 index 0000000000..0bd0ec78ca --- /dev/null +++ b/src/otsur/bcur/fountain-decoder.cpp @@ -0,0 +1,254 @@ +// +// fountain-decoder.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "fountain-decoder.hpp" +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace ur { + +FountainDecoder::FountainDecoder() { } + +FountainDecoder::Part::Part(const FountainEncoder::Part& p) + : indexes_(choose_fragments(p.seq_num(), p.seq_len(), p.checksum())) + , data_(p.data()) +{ +} + +FountainDecoder::Part::Part(PartIndexes& indexes, ByteVector& data) + : indexes_(indexes) + , data_(data) +{ +} + +const ByteVector FountainDecoder::join_fragments(const vector& fragments, size_t message_len) { + auto message = join(fragments); + return take_first(message, message_len); +} + +double FountainDecoder::estimated_percent_complete() const { + if(is_complete()) return 1; + if(!_expected_part_indexes.has_value()) return 0; + auto estimated_input_parts = expected_part_count() * 1.75; + return min(0.99, processed_parts_count_ / estimated_input_parts); +} + +bool FountainDecoder::receive_part(FountainEncoder::Part& encoder_part) { + // Don't process the part if we're already done + if(is_complete()) return false; + + // Don't continue if this part doesn't validate + if(!validate_part(encoder_part)) return false; + + // Add this part to the queue + auto p = Part(encoder_part); + last_part_indexes_ = p.indexes(); + enqueue(p); + + // Process the queue until we're done or the queue is empty + while(!is_complete() && !_queued_parts.empty()) { + process_queue_item(); + } + + // Keep track of how many parts we've processed + processed_parts_count_ += 1; + + //print_part_end(); + + return true; +} + +void FountainDecoder::enqueue(Part &&p) { + _queued_parts.push_back(p); +} + +void FountainDecoder::enqueue(const Part &p) { + _queued_parts.push_back(p); +} + +void FountainDecoder::process_queue_item() { + auto part = _queued_parts.front(); + //print_part(part); + _queued_parts.pop_front(); + if(part.is_simple()) { + process_simple_part(part); + } else { + process_mixed_part(part); + } + //print_state(); +} + +void FountainDecoder::reduce_mixed_by(const Part& p) { + // Reduce all the current mixed parts by the given part + vector reduced_parts; + for(auto i = _mixed_parts.begin(); i != _mixed_parts.end(); i++) { + reduced_parts.push_back(reduce_part_by_part(i->second, p)); + } + + // Collect all the remaining mixed parts + PartDict new_mixed; + for(auto reduced_part: reduced_parts) { + // If this reduced part is now simple + if(reduced_part.is_simple()) { + // Add it to the queue + enqueue(reduced_part); + } else { + // Otherwise, add it to the list of current mixed parts + new_mixed.insert(pair(reduced_part.indexes(), reduced_part)); + } + } + _mixed_parts = new_mixed; +} + +FountainDecoder::Part FountainDecoder::reduce_part_by_part(const Part& a, const Part& b) const { + // If the fragments mixed into `b` are a strict (proper) subset of those in `a`... + if(is_strict_subset(b.indexes(), a.indexes())) { + // The new fragments in the revised part are `a` - `b`. + auto new_indexes = set_difference(a.indexes(), b.indexes()); + // The new data in the revised part are `a` XOR `b` + auto new_data = xor_with(a.data(), b.data()); + return Part(new_indexes, new_data); + } else { + // `a` is not reducable by `b`, so return a + return a; + } +} + +void FountainDecoder::process_simple_part(Part& p) { + // Don't process duplicate parts + auto fragment_index = p.index(); + if(contains(received_part_indexes_, fragment_index)) return; + + // Record this part + _simple_parts.insert(pair(p.indexes(), p)); + received_part_indexes_.insert(fragment_index); + + // If we've received all the parts + if(received_part_indexes_ == _expected_part_indexes) { + // Reassemble the message from its fragments + vector sorted_parts; + transform(_simple_parts.begin(), _simple_parts.end(), back_inserter(sorted_parts), [&](auto elem) { return elem.second; }); + sort(sorted_parts.begin(), sorted_parts.end(), + [](const Part& a, const Part& b) -> bool { + return a.index() < b.index(); + } + ); + vector fragments; + transform(sorted_parts.begin(), sorted_parts.end(), back_inserter(fragments), [&](auto part) { return part.data(); }); + auto message = join_fragments(fragments, *_expected_message_len); + + // Verify the message checksum and note success or failure + auto checksum = crc32_int(message); + if(checksum == _expected_checksum) { + result_ = message; + } else { + result_ = InvalidChecksum(); + } + } else { + // Reduce all the mixed parts by this part + reduce_mixed_by(p); + } +} + +void FountainDecoder::process_mixed_part(const Part& p) { + // Don't process duplicate parts + if(any_of(_mixed_parts.begin(), _mixed_parts.end(), [&](auto r) { return r.first == p.indexes(); })) { + return; + } + + // Reduce this part by all the others + auto p2 = accumulate(_simple_parts.begin(), _simple_parts.end(), p, [&](auto p, auto r) { return reduce_part_by_part(p, r.second); }); + p2 = accumulate(_mixed_parts.begin(), _mixed_parts.end(), p2, [&](auto p, auto r) { return reduce_part_by_part(p, r.second); }); + + // If the part is now simple + if(p2.is_simple()) { + // Add it to the queue + enqueue(p2); + } else { + // Reduce all the mixed parts by this one + reduce_mixed_by(p2); + // Record this new mixed part + _mixed_parts.insert(pair(p2.indexes(), p2)); + } +} + +bool FountainDecoder::validate_part(const FountainEncoder::Part& p) { + // If this is the first part we've seen + if(!_expected_part_indexes.has_value()) { + // Record the things that all the other parts we see will have to match to be valid. + _expected_part_indexes = PartIndexes(); + for(size_t i = 0; i < p.seq_len(); i++) { _expected_part_indexes->insert(i); } + _expected_message_len = p.message_len(); + _expected_checksum = p.checksum(); + _expected_fragment_len = p.data().size(); + } else { + // If this part's values don't match the first part's values, throw away the part + if(expected_part_count() != p.seq_len()) return false; + if(_expected_message_len != p.message_len()) return false; + if(_expected_checksum != p.checksum()) return false; + if(_expected_fragment_len != p.data().size()) return false; + } + // This part should be processed + return true; +} + +string FountainDecoder::indexes_to_string(const PartIndexes& indexes) { + auto i = vector(indexes.begin(), indexes.end()); + sort(i.begin(), i.end()); + StringVector s; + transform(i.begin(), i.end(), back_inserter(s), [](size_t a) { return to_string(a); }); + return "[" + join(s, ", ") + "]"; +} + +void FountainDecoder::print_part(const Part& p) const { + cout << "part indexes: " << indexes_to_string(p.indexes()) << endl; +} + +void FountainDecoder::print_part_end() const { + auto expected = _expected_part_indexes.has_value() ? to_string(expected_part_count()) : "nil"; + auto percent = int(round(estimated_percent_complete() * 100)); + cout << "processed: " << processed_parts_count_ << ", expected: " << expected << ", received: " << received_part_indexes_.size() << ", percent: " << percent << "%" << endl; +} + +string FountainDecoder::result_description() const { + string desc; + if(!result_.has_value()) { + desc = "nil"; + } else { + auto r = *result_; + if(holds_alternative(r)) { + desc = to_string(get(r).size()) + " bytes"; + } else if(holds_alternative(r)) { + desc = get(r).what(); + } else { + assert(false); + } + } + return desc; +} + +void FountainDecoder::print_state() const { + auto parts = _expected_part_indexes.has_value() ? to_string(expected_part_count()) : "nil"; + auto received = indexes_to_string(received_part_indexes_); + StringVector mixed; + transform(_mixed_parts.begin(), _mixed_parts.end(), back_inserter(mixed), [](const pair& p) { + return indexes_to_string(p.first); + }); + auto mixed_s = "[" + join(mixed, ", ") + "]"; + auto queued = _queued_parts.size(); + auto res = result_description(); + cout << "parts: " << parts << ", received: " << received << ", mixed: " << mixed_s << ", queued: " << queued << ", result: " << res << endl; +} + +} diff --git a/src/otsur/bcur/fountain-decoder.hpp b/src/otsur/bcur/fountain-decoder.hpp new file mode 100644 index 0000000000..6dc92d70dc --- /dev/null +++ b/src/otsur/bcur/fountain-decoder.hpp @@ -0,0 +1,103 @@ +// +// fountain-decoder.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_FOUNTAIN_DECODER_HPP +#define BC_UR_FOUNTAIN_DECODER_HPP + +#include "utils.hpp" +#include "fountain-encoder.hpp" +#include +#include +#include +#include +#include + +namespace ur { + +class FountainDecoder final { +public: + typedef std::optional > Result; + + class InvalidPart: public std::exception { }; + class InvalidChecksum: public std::exception { }; + + FountainDecoder(); + + size_t expected_part_count() const { return _expected_part_indexes.value().size(); } + const PartIndexes& received_part_indexes() const { return received_part_indexes_; } + const PartIndexes& last_part_indexes() const { return last_part_indexes_.value(); } + size_t processed_parts_count() const { return processed_parts_count_; } + const Result& result() const { return result_; } + bool is_success() const { return result() && std::holds_alternative(result().value()); } + bool is_failure() const { return result() && std::holds_alternative(result().value()); } + bool is_complete() const { return result().has_value(); } + const ByteVector& result_message() const { return std::get(result().value()); } + const std::exception& result_error() const { return std::get(result().value()); } + + double estimated_percent_complete() const; + bool receive_part(FountainEncoder::Part& encoder_part); + + // Join all the fragments of a message together, throwing away any padding + static const ByteVector join_fragments(const std::vector& fragments, size_t message_len); + +private: + class Part { + private: + PartIndexes indexes_; + ByteVector data_; + + public: + explicit Part(const FountainEncoder::Part& p); + Part(PartIndexes& indexes, ByteVector& data); + + const PartIndexes& indexes() const { return indexes_; } + const ByteVector& data() const { return data_; } + bool is_simple() const { return indexes_.size() == 1; } + size_t index() const { return *indexes_.begin(); } + }; + + PartIndexes received_part_indexes_; + std::optional last_part_indexes_; + size_t processed_parts_count_ = 0; + + Result result_; + + typedef std::map PartDict; + + std::optional _expected_part_indexes; + std::optional _expected_fragment_len; + std::optional _expected_message_len; + std::optional _expected_checksum; + + PartDict _simple_parts; + PartDict _mixed_parts; + std::deque _queued_parts; + + void enqueue(const Part &p); + void enqueue(Part &&p); + void process_queue_item(); + void reduce_mixed_by(const Part& p); + Part reduce_part_by_part(const Part& a, const Part& b) const; + void process_simple_part(Part& p); + void process_mixed_part(const Part& p); + bool validate_part(const FountainEncoder::Part& p); + + // debugging + static std::string indexes_to_string(const PartIndexes& indexes); + std::string result_description() const; + + // cppcheck-suppress unusedPrivateFunction + void print_part(const Part& p) const; + // cppcheck-suppress unusedPrivateFunction + void print_part_end() const; + // cppcheck-suppress unusedPrivateFunction + void print_state() const; +}; + +} + +#endif // BC_UR_FOUNTAIN_DECODER_HPP diff --git a/src/otsur/bcur/fountain-encoder.cpp b/src/otsur/bcur/fountain-encoder.cpp new file mode 100644 index 0000000000..f7f6cf97c9 --- /dev/null +++ b/src/otsur/bcur/fountain-encoder.cpp @@ -0,0 +1,126 @@ +// +// fountain-encoder.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "fountain-encoder.hpp" +#include +#include +#include +#include +#include +#include "cbor-lite.hpp" + +using namespace std; + +namespace ur { + +size_t FountainEncoder::find_nominal_fragment_length(size_t message_len, size_t min_fragment_len, size_t max_fragment_len) { + assert(message_len > 0); + assert(min_fragment_len > 0); + assert(max_fragment_len >= min_fragment_len); + auto max_fragment_count = message_len / min_fragment_len; + optional fragment_len; + for(size_t fragment_count = 1; fragment_count <= max_fragment_count; fragment_count++) { + fragment_len = size_t(ceil(double(message_len) / fragment_count)); + if(fragment_len <= max_fragment_len) { + break; + } + } + assert(fragment_len.has_value()); + return *fragment_len; +} + +vector FountainEncoder::partition_message(const ByteVector &message, size_t fragment_len) { + auto remaining = message; + vector fragments; + while(!remaining.empty()) { + auto a = split(remaining, fragment_len); + auto fragment = a.first; + remaining = a.second; + auto padding = fragment_len - fragment.size(); + while(padding > 0) { + fragment.push_back(0); + padding--; + } + fragments.push_back(fragment); + } + return fragments; +} + +FountainEncoder::Part::Part(const ByteVector& cbor) { + try { + auto i = cbor.begin(); + auto end = cbor.end(); + size_t array_size; + CborLite::decodeArraySize(i, end, array_size); + if(array_size != 5) { throw InvalidHeader(); } + + uint64_t n; + + CborLite::decodeUnsigned(i, end, n); + if(n > std::numeric_limits::max()) { throw InvalidHeader(); } + seq_num_ = n; + + CborLite::decodeUnsigned(i, end, n); + if(n > std::numeric_limits::max()) { throw InvalidHeader(); } + seq_len_ = n; + + CborLite::decodeUnsigned(i, end, n); + if(n > std::numeric_limits::max()) { throw InvalidHeader(); } + message_len_ = n; + + CborLite::decodeUnsigned(i, end, n); + if(n > std::numeric_limits::max()) { throw InvalidHeader(); } + checksum_ = n; + + CborLite::decodeBytes(i, end, data_); + } catch(...) { + throw InvalidHeader(); + } +} + +ByteVector FountainEncoder::Part::cbor() const { + using namespace CborLite; + + ByteVector result; + + encodeArraySize(result, (size_t)5); + encodeInteger(result, seq_num()); + encodeInteger(result, seq_len()); + encodeInteger(result, message_len()); + encodeInteger(result, checksum()); + encodeBytes(result, data()); + + return result; +} + +FountainEncoder::FountainEncoder(const ByteVector& message, size_t max_fragment_len, uint32_t first_seq_num, size_t min_fragment_len) { + assert(message.size() <= std::numeric_limits::max()); + message_len_ = message.size(); + checksum_ = crc32_int(message); + fragment_len_ = find_nominal_fragment_length(message_len_, min_fragment_len, max_fragment_len); + fragments_ = partition_message(message, fragment_len_); + seq_num_ = first_seq_num; +} + +ByteVector FountainEncoder::mix(const PartIndexes& indexes) const { + ByteVector result(fragment_len_, 0); + for(auto index: indexes) { xor_into(result, fragments_[index]); } + return result; +} + +FountainEncoder::Part FountainEncoder::next_part() { + seq_num_ += 1; // wrap at period 2^32 + auto indexes = choose_fragments(seq_num_, seq_len(), checksum_); + auto mixed = mix(indexes); + return Part(seq_num_, seq_len(), message_len_, checksum_, mixed); +} + +string FountainEncoder::Part::description() const { + return "seqNum:" + to_string(seq_num_) + ", seqLen:" + to_string(seq_len_) + ", messageLen:" + to_string(message_len_) + ", checksum:" + to_string(checksum_) + ", data:" + data_to_hex(data_); +} + +} diff --git a/src/otsur/bcur/fountain-encoder.hpp b/src/otsur/bcur/fountain-encoder.hpp new file mode 100644 index 0000000000..1381dd50f1 --- /dev/null +++ b/src/otsur/bcur/fountain-encoder.hpp @@ -0,0 +1,81 @@ +// +// fountain-encoder.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_FOUNTAIN_ENCODER_HPP +#define BC_UR_FOUNTAIN_ENCODER_HPP + +#include +#include +#include +#include "utils.hpp" +#include "fountain-utils.hpp" + +namespace ur { + +// Implements Luby transform code rateless coding +// https://en.wikipedia.org/wiki/Luby_transform_code + +class FountainEncoder final { +public: + class Part { + public: + class InvalidHeader: public std::exception { }; + + Part(uint32_t seq_num, size_t seq_len, size_t message_len, uint32_t checksum, const ByteVector& data) + : seq_num_(seq_num), seq_len_(seq_len), message_len_(message_len), checksum_(checksum), data_(data) + { } + explicit Part(const ByteVector& cbor); + + uint32_t seq_num() const { return seq_num_; } + size_t seq_len() const { return seq_len_; } + size_t message_len() const { return message_len_; } + uint32_t checksum() const { return checksum_; } + const ByteVector& data() const { return data_; } + + ByteVector cbor() const; + std::string description() const; + + private: + uint32_t seq_num_; + size_t seq_len_; + size_t message_len_; + uint32_t checksum_; + ByteVector data_; + }; + + FountainEncoder(const ByteVector& message, size_t max_fragment_len, uint32_t first_seq_num = 0, size_t min_fragment_len = 10); + + static size_t find_nominal_fragment_length(size_t message_len, size_t min_fragment_len, size_t max_fragment_len); + static std::vector partition_message(const ByteVector &message, size_t fragment_len); + + uint32_t seq_num() const { return seq_num_; } + const PartIndexes& last_part_indexes() const { return last_part_indexes_; } + size_t seq_len() const { return fragments_.size(); } + + // This becomes `true` when the minimum number of parts + // to relay the complete message have been generated + bool is_complete() const { return seq_num_ >= seq_len(); } + + /// True if only a single part will be generated. + bool is_single_part() const { return seq_len() == 1; } + + Part next_part(); + +private: + size_t message_len_; + uint32_t checksum_; + size_t fragment_len_; + std::vector fragments_; + uint32_t seq_num_; + PartIndexes last_part_indexes_; + + ByteVector mix(const PartIndexes& indexes) const; +}; + +} + +#endif // BC_UR_FOUNTAIN_ENCODER_HPP diff --git a/src/otsur/bcur/fountain-utils.cpp b/src/otsur/bcur/fountain-utils.cpp new file mode 100644 index 0000000000..fa59118243 --- /dev/null +++ b/src/otsur/bcur/fountain-utils.cpp @@ -0,0 +1,43 @@ +// +// fountain-utils.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "fountain-utils.hpp" +#include "random-sampler.hpp" +#include "utils.hpp" + +using namespace std; + +namespace ur { + +size_t choose_degree(size_t seq_len, Xoshiro256& rng) { + vector degree_probabilities; + for(int i = 1; i <= seq_len; i++) { + degree_probabilities.push_back(1.0 / i); + } + auto degree_chooser = RandomSampler(degree_probabilities); + return degree_chooser.next([&]() { return rng.next_double(); }) + 1; +} + +set choose_fragments(uint32_t seq_num, size_t seq_len, uint32_t checksum) { + // The first `seq_len` parts are the "pure" fragments, not mixed with any + // others. This means that if you only generate the first `seq_len` parts, + // then you have all the parts you need to decode the message. + if(seq_num <= seq_len) { + return set({seq_num - 1}); + } else { + auto seed = join(vector({int_to_bytes(seq_num), int_to_bytes(checksum)})); + auto rng = Xoshiro256(seed); + auto degree = choose_degree(seq_len, rng); + vector indexes; + indexes.reserve(seq_len); + for(int i = 0; i < seq_len; i++) { indexes.push_back(i); } + auto shuffled_indexes = shuffled(indexes, rng); + return set(shuffled_indexes.begin(), shuffled_indexes.begin() + degree); + } +} + +} diff --git a/src/otsur/bcur/fountain-utils.hpp b/src/otsur/bcur/fountain-utils.hpp new file mode 100644 index 0000000000..602cef74b5 --- /dev/null +++ b/src/otsur/bcur/fountain-utils.hpp @@ -0,0 +1,61 @@ +// +// fountain-utils.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_FOUNTAIN_UTILS_HPP +#define BC_UR_FOUNTAIN_UTILS_HPP + +#include +#include +#include +#include +#include +#include +#include "xoshiro256.hpp" + +namespace ur { + +typedef std::set PartIndexes; + +// Fisher-Yates shuffle +template +std::vector shuffled(const std::vector& items, Xoshiro256& rng) { + auto remaining = items; + std::vector result; + while(!remaining.empty()) { + auto index = rng.next_int(0, remaining.size() - 1); + auto item = remaining[index]; + remaining.erase(remaining.begin() + index); + result.push_back(item); + } + return result; +} + +// Return `true` if `a` is a strict subset of `b`. +template +bool is_strict_subset(const std::set& a, const std::set& b) { + if(a == b) { return false; } + return std::includes(b.begin(), b.end(), a.begin(), a.end()); +} + +template +std::set set_difference(const std::set& a, const std::set& b) { + std::set result; + std::set_difference(a.begin(), a.end(), b.begin(), b.end(), std::inserter(result, result.begin())); + return result; +} + +template +bool contains(const std::set& s, const T& v) { + return s.find(v) != s.end(); +} + +size_t choose_degree(size_t seq_len, Xoshiro256& rng); +std::set choose_fragments(uint32_t seq_num, size_t seq_len, uint32_t checksum); + +} + +#endif // BC_UR_FOUNTAIN_UTILS_HPP diff --git a/src/otsur/bcur/memzero.c b/src/otsur/bcur/memzero.c new file mode 100644 index 0000000000..b19923a944 --- /dev/null +++ b/src/otsur/bcur/memzero.c @@ -0,0 +1,84 @@ +#include "memzero.h" + +#ifndef __STDC_WANT_LIB_EXT1__ +#define __STDC_WANT_LIB_EXT1__ 1 // C11's bounds-checking interface. +#endif +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef __unix__ +#include +#include +#endif + +// C11's bounds-checking interface. +#if defined(__STDC_LIB_EXT1__) +#define HAVE_MEMSET_S 1 +#endif + +// GNU C Library version 2.25 or later. +#if defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)) +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// Newlib +#if defined(__NEWLIB__) +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// FreeBSD version 11.0 or later. +#if defined(__FreeBSD__) && __FreeBSD_version >= 1100037 +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// OpenBSD version 5.5 or later. +#if defined(__OpenBSD__) && OpenBSD >= 201405 +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// NetBSD version 7.2 or later. +#if defined(__NetBSD__) && __NetBSD_Version__ >= 702000000 +#define HAVE_EXPLICIT_MEMSET 1 +#endif + +// Adapted from +// https://github.com/jedisct1/libsodium/blob/1647f0d53ae0e370378a9195477e3df0a792408f/src/libsodium/sodium/utils.c#L102-L130 + +void memzero(void *const pnt, const size_t len) { +#ifdef _WIN32 + SecureZeroMemory(pnt, len); +#elif defined(HAVE_MEMSET_S) + memset_s(pnt, (rsize_t)len, 0, (rsize_t)len); +#elif defined(HAVE_EXPLICIT_BZERO) + bzero(pnt, len); +#elif defined(HAVE_EXPLICIT_MEMSET) + explicit_memset(pnt, 0, len); +#else + volatile unsigned char *volatile pnt_ = (volatile unsigned char *volatile)pnt; + size_t i = (size_t)0U; + + while (i < len) { + pnt_[i++] = 0U; + } + + /* Memory barrier that scares the compiler away from optimizing out + * the above loop. + * + * Quoting Adam Langley in commit + * ad1907fe73334d6c696c8539646c21b11178f20f of BoringSSL (ISC License): + * + * As best as we can tell, this is sufficient to break any optimisations + * that might try to eliminate "superfluous" memsets. This method is used + * in memzero_explicit() the Linux kernel, too. Its advantage is that it + * is pretty efficient because the compiler can still implement the + * memset() efficiently, just not remove it entirely. See "Dead Store + * Elimination (Still) Considered Harmful" by Yang et al. (USENIX Security + * 2017) for more background. + */ + __asm__ __volatile__("" : : "r"(pnt_) : "memory"); +#endif +} diff --git a/src/otsur/bcur/memzero.h b/src/otsur/bcur/memzero.h new file mode 100644 index 0000000000..0a950de2f7 --- /dev/null +++ b/src/otsur/bcur/memzero.h @@ -0,0 +1,8 @@ +#ifndef BC_UR_MEMZERO_H +#define BC_UR_MEMZERO_H + +#include + +void memzero(void* const pnt, const size_t len); + +#endif // BC_UR_MEMZERO_H diff --git a/src/otsur/bcur/random-sampler.cpp b/src/otsur/bcur/random-sampler.cpp new file mode 100644 index 0000000000..7822d6ecda --- /dev/null +++ b/src/otsur/bcur/random-sampler.cpp @@ -0,0 +1,84 @@ +// +// random-sampler.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "random-sampler.hpp" +#include +#include +#include + +using namespace std; + +namespace ur { + +RandomSampler::RandomSampler(std::vector probs) { + for(auto p: probs) { assert(p >= 0); } + + // Normalize given probabilities + auto sum = accumulate(probs.begin(), probs.end(), 0.0); + assert(sum > 0); + + auto n = probs.size(); + + vector P; + P.reserve(n); + transform(probs.begin(), probs.end(), back_inserter(P), [&](double d) { return d * double(n) / sum; }); + + vector S; + S.reserve(n); + vector L; + L.reserve(n); + + // Set separate index lists for small and large probabilities: + for(int i = n - 1; i >= 0; i--) { + // at variance from Schwarz, we reverse the index order + if(P[i] < 1) { + S.push_back(i); + } else { + L.push_back(i); + } + } + + // Work through index lists + vector _probs(n, 0); + vector _aliases(n, 0); + while(!S.empty() && !L.empty()) { + auto a = S.back(); S.pop_back(); // Schwarz's l + auto g = L.back(); L.pop_back(); // Schwarz's g + _probs[a] = P[a]; + _aliases[a] = g; + P[g] += P[a] - 1; + if(P[g] < 1) { + S.push_back(g); + } else { + L.push_back(g); + } + } + + while(!L.empty()) { + _probs[L.back()] = 1; + L.pop_back(); + } + + while(!S.empty()) { + // can only happen through numeric instability + _probs[S.back()] = 1; + S.pop_back(); + } + + this->probs_ = _probs; + this->aliases_ = _aliases; +} + +int RandomSampler::next(std::function rng) { + auto r1 = rng(); + auto r2 = rng(); + auto n = probs_.size(); + auto i = int(double(n) * r1); + return r2 < probs_[i] ? i : aliases_[i]; +} + +} diff --git a/src/otsur/bcur/random-sampler.hpp b/src/otsur/bcur/random-sampler.hpp new file mode 100644 index 0000000000..24b79b4652 --- /dev/null +++ b/src/otsur/bcur/random-sampler.hpp @@ -0,0 +1,38 @@ +// +// random-sampler.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_RANDOM_SAMPLER_HPP +#define BC_UR_RANDOM_SAMPLER_HPP + +#include +#include + +// Random-number sampling using the Walker-Vose alias method, +// as described by Keith Schwarz (2011) +// http://www.keithschwarz.com/darts-dice-coins + +// Based on C implementation: +// https://jugit.fz-juelich.de/mlz/ransampl + +// Translated to C++ by Wolf McNally + +namespace ur { + +class RandomSampler final { +public: + explicit RandomSampler(std::vector probs); + + int next(std::function rng); + +private: + std::vector probs_; + std::vector aliases_; +}; + +} + +#endif // BC_UR_RANDOM_SAMPLER_HPP diff --git a/src/otsur/bcur/sha2.c b/src/otsur/bcur/sha2.c new file mode 100644 index 0000000000..4d2227b41d --- /dev/null +++ b/src/otsur/bcur/sha2.c @@ -0,0 +1,889 @@ +/** + * Copyright (c) 2000-2001 Aaron D. Gifford + * Copyright (c) 2013-2014 Pavol Rusnak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include "sha2.h" +#include "memzero.h" + +/* + * ASSERT NOTE: + * Some sanity checking code is included using assert(). On my FreeBSD + * system, this additional code can be removed by compiling with NDEBUG + * defined. Check your own systems manpage on assert() to see how to + * compile WITHOUT the sanity checking code on your system. + * + * UNROLLED TRANSFORM LOOP NOTE: + * You can define SHA2_UNROLL_TRANSFORM to use the unrolled transform + * loop version for the hash transform rounds (defined using macros + * later in this file). Either define on the command line, for example: + * + * cc -DSHA2_UNROLL_TRANSFORM -o sha2 sha2.c sha2prog.c + * + * or define below: + * + * #define SHA2_UNROLL_TRANSFORM + * + */ + + +/*** SHA-256/384/512 Machine Architecture Definitions *****************/ +/* + * BYTE_ORDER NOTE: + * + * Please make sure that your system defines BYTE_ORDER. If your + * architecture is little-endian, make sure it also defines + * LITTLE_ENDIAN and that the two (BYTE_ORDER and LITTLE_ENDIAN) are + * equivilent. + * + * If your system does not define the above, then you can do so by + * hand like this: + * + * #define LITTLE_ENDIAN 1234 + * #define BIG_ENDIAN 4321 + * + * And for little-endian machines, add: + * + * #define BYTE_ORDER LITTLE_ENDIAN + * + * Or for big-endian machines: + * + * #define BYTE_ORDER BIG_ENDIAN + * + * The FreeBSD machine this was written on defines BYTE_ORDER + * appropriately by including (which in turn includes + * where the appropriate definitions are actually + * made). + */ + +#if !defined(BYTE_ORDER) || (BYTE_ORDER != LITTLE_ENDIAN && BYTE_ORDER != BIG_ENDIAN) +#error Define BYTE_ORDER to be equal to either LITTLE_ENDIAN or BIG_ENDIAN +#endif + +typedef uint8_t sha2_byte; /* Exactly 1 byte */ +typedef uint32_t sha2_word32; /* Exactly 4 bytes */ +typedef uint64_t sha2_word64; /* Exactly 8 bytes */ + +/*** SHA-256/384/512 Various Length Definitions ***********************/ +/* NOTE: Most of these are in sha2.h */ +#define SHA256_SHORT_BLOCK_LENGTH (SHA256_BLOCK_LENGTH - 8) +#define SHA512_SHORT_BLOCK_LENGTH (SHA512_BLOCK_LENGTH - 16) + +/* + * Macro for incrementally adding the unsigned 64-bit integer n to the + * unsigned 128-bit integer (represented using a two-element array of + * 64-bit words): + */ +#define ADDINC128(w,n) { \ + (w)[0] += (sha2_word64)(n); \ + if ((w)[0] < (n)) { \ + (w)[1]++; \ + } \ +} + +#define MEMCPY_BCOPY(d,s,l) memcpy((d), (s), (l)) + +/*** THE SIX LOGICAL FUNCTIONS ****************************************/ +/* + * Bit shifting and rotation (used by the six SHA-XYZ logical functions: + * + * NOTE: In the original SHA-256/384/512 document, the shift-right + * function was named R and the rotate-right function was called S. + * (See: http://csrc.nist.gov/cryptval/shs/sha256-384-512.pdf on the + * web.) + * + * The newer NIST FIPS 180-2 document uses a much clearer naming + * scheme, SHR for shift-right, ROTR for rotate-right, and ROTL for + * rotate-left. (See: + * http://csrc.nist.gov/publications/fips/fips180-2/fips180-2.pdf + * on the web.) + * + * WARNING: These macros must be used cautiously, since they reference + * supplied parameters sometimes more than once, and thus could have + * unexpected side-effects if used without taking this into account. + */ + +/* Shift-right (used in SHA-256, SHA-384, and SHA-512): */ +#define SHR(b,x) ((x) >> (b)) +/* 32-bit Rotate-right (used in SHA-256): */ +#define ROTR32(b,x) (((x) >> (b)) | ((x) << (32 - (b)))) +/* 64-bit Rotate-right (used in SHA-384 and SHA-512): */ +#define ROTR64(b,x) (((x) >> (b)) | ((x) << (64 - (b)))) +/* 32-bit Rotate-left (used in SHA-1): */ +#define ROTL32(b,x) (((x) << (b)) | ((x) >> (32 - (b)))) + +/* Two of six logical functions used in SHA-1, SHA-256, SHA-384, and SHA-512: */ +#define Ch(x,y,z) (((x) & (y)) ^ ((~(x)) & (z))) +#define Maj(x,y,z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) + +/* Function used in SHA-1: */ +#define Parity(x,y,z) ((x) ^ (y) ^ (z)) + +/* Four of six logical functions used in SHA-256: */ +#define Sigma0_256(x) (ROTR32(2, (x)) ^ ROTR32(13, (x)) ^ ROTR32(22, (x))) +#define Sigma1_256(x) (ROTR32(6, (x)) ^ ROTR32(11, (x)) ^ ROTR32(25, (x))) +#define sigma0_256(x) (ROTR32(7, (x)) ^ ROTR32(18, (x)) ^ SHR(3 , (x))) +#define sigma1_256(x) (ROTR32(17, (x)) ^ ROTR32(19, (x)) ^ SHR(10, (x))) + +/* Four of six logical functions used in SHA-384 and SHA-512: */ +#define Sigma0_512(x) (ROTR64(28, (x)) ^ ROTR64(34, (x)) ^ ROTR64(39, (x))) +#define Sigma1_512(x) (ROTR64(14, (x)) ^ ROTR64(18, (x)) ^ ROTR64(41, (x))) +#define sigma0_512(x) (ROTR64( 1, (x)) ^ ROTR64( 8, (x)) ^ SHR( 7, (x))) +#define sigma1_512(x) (ROTR64(19, (x)) ^ ROTR64(61, (x)) ^ SHR( 6, (x))) + +/*** INTERNAL FUNCTION PROTOTYPES *************************************/ +/* NOTE: These should not be accessed directly from outside this + * library -- they are intended for private internal visibility/use + * only. + */ +static void sha512_Last(SHA512_CTX*); + + +/*** SHA-XYZ INITIAL HASH VALUES AND CONSTANTS ************************/ + +/* Hash constant words K for SHA-256: */ +static const sha2_word32 K256[64] = { + 0x428a2f98UL, 0x71374491UL, 0xb5c0fbcfUL, 0xe9b5dba5UL, + 0x3956c25bUL, 0x59f111f1UL, 0x923f82a4UL, 0xab1c5ed5UL, + 0xd807aa98UL, 0x12835b01UL, 0x243185beUL, 0x550c7dc3UL, + 0x72be5d74UL, 0x80deb1feUL, 0x9bdc06a7UL, 0xc19bf174UL, + 0xe49b69c1UL, 0xefbe4786UL, 0x0fc19dc6UL, 0x240ca1ccUL, + 0x2de92c6fUL, 0x4a7484aaUL, 0x5cb0a9dcUL, 0x76f988daUL, + 0x983e5152UL, 0xa831c66dUL, 0xb00327c8UL, 0xbf597fc7UL, + 0xc6e00bf3UL, 0xd5a79147UL, 0x06ca6351UL, 0x14292967UL, + 0x27b70a85UL, 0x2e1b2138UL, 0x4d2c6dfcUL, 0x53380d13UL, + 0x650a7354UL, 0x766a0abbUL, 0x81c2c92eUL, 0x92722c85UL, + 0xa2bfe8a1UL, 0xa81a664bUL, 0xc24b8b70UL, 0xc76c51a3UL, + 0xd192e819UL, 0xd6990624UL, 0xf40e3585UL, 0x106aa070UL, + 0x19a4c116UL, 0x1e376c08UL, 0x2748774cUL, 0x34b0bcb5UL, + 0x391c0cb3UL, 0x4ed8aa4aUL, 0x5b9cca4fUL, 0x682e6ff3UL, + 0x748f82eeUL, 0x78a5636fUL, 0x84c87814UL, 0x8cc70208UL, + 0x90befffaUL, 0xa4506cebUL, 0xbef9a3f7UL, 0xc67178f2UL +}; + +/* Initial hash value H for SHA-256: */ +const sha2_word32 sha256_initial_hash_value[8] = { + 0x6a09e667UL, + 0xbb67ae85UL, + 0x3c6ef372UL, + 0xa54ff53aUL, + 0x510e527fUL, + 0x9b05688cUL, + 0x1f83d9abUL, + 0x5be0cd19UL +}; + +/* Hash constant words K for SHA-384 and SHA-512: */ +static const sha2_word64 K512[80] = { + 0x428a2f98d728ae22ULL, 0x7137449123ef65cdULL, + 0xb5c0fbcfec4d3b2fULL, 0xe9b5dba58189dbbcULL, + 0x3956c25bf348b538ULL, 0x59f111f1b605d019ULL, + 0x923f82a4af194f9bULL, 0xab1c5ed5da6d8118ULL, + 0xd807aa98a3030242ULL, 0x12835b0145706fbeULL, + 0x243185be4ee4b28cULL, 0x550c7dc3d5ffb4e2ULL, + 0x72be5d74f27b896fULL, 0x80deb1fe3b1696b1ULL, + 0x9bdc06a725c71235ULL, 0xc19bf174cf692694ULL, + 0xe49b69c19ef14ad2ULL, 0xefbe4786384f25e3ULL, + 0x0fc19dc68b8cd5b5ULL, 0x240ca1cc77ac9c65ULL, + 0x2de92c6f592b0275ULL, 0x4a7484aa6ea6e483ULL, + 0x5cb0a9dcbd41fbd4ULL, 0x76f988da831153b5ULL, + 0x983e5152ee66dfabULL, 0xa831c66d2db43210ULL, + 0xb00327c898fb213fULL, 0xbf597fc7beef0ee4ULL, + 0xc6e00bf33da88fc2ULL, 0xd5a79147930aa725ULL, + 0x06ca6351e003826fULL, 0x142929670a0e6e70ULL, + 0x27b70a8546d22ffcULL, 0x2e1b21385c26c926ULL, + 0x4d2c6dfc5ac42aedULL, 0x53380d139d95b3dfULL, + 0x650a73548baf63deULL, 0x766a0abb3c77b2a8ULL, + 0x81c2c92e47edaee6ULL, 0x92722c851482353bULL, + 0xa2bfe8a14cf10364ULL, 0xa81a664bbc423001ULL, + 0xc24b8b70d0f89791ULL, 0xc76c51a30654be30ULL, + 0xd192e819d6ef5218ULL, 0xd69906245565a910ULL, + 0xf40e35855771202aULL, 0x106aa07032bbd1b8ULL, + 0x19a4c116b8d2d0c8ULL, 0x1e376c085141ab53ULL, + 0x2748774cdf8eeb99ULL, 0x34b0bcb5e19b48a8ULL, + 0x391c0cb3c5c95a63ULL, 0x4ed8aa4ae3418acbULL, + 0x5b9cca4f7763e373ULL, 0x682e6ff3d6b2b8a3ULL, + 0x748f82ee5defb2fcULL, 0x78a5636f43172f60ULL, + 0x84c87814a1f0ab72ULL, 0x8cc702081a6439ecULL, + 0x90befffa23631e28ULL, 0xa4506cebde82bde9ULL, + 0xbef9a3f7b2c67915ULL, 0xc67178f2e372532bULL, + 0xca273eceea26619cULL, 0xd186b8c721c0c207ULL, + 0xeada7dd6cde0eb1eULL, 0xf57d4f7fee6ed178ULL, + 0x06f067aa72176fbaULL, 0x0a637dc5a2c898a6ULL, + 0x113f9804bef90daeULL, 0x1b710b35131c471bULL, + 0x28db77f523047d84ULL, 0x32caab7b40c72493ULL, + 0x3c9ebe0a15c9bebcULL, 0x431d67c49c100d4cULL, + 0x4cc5d4becb3e42b6ULL, 0x597f299cfc657e2aULL, + 0x5fcb6fab3ad6faecULL, 0x6c44198c4a475817ULL +}; + +/* Initial hash value H for SHA-512 */ +const sha2_word64 sha512_initial_hash_value[8] = { + 0x6a09e667f3bcc908ULL, + 0xbb67ae8584caa73bULL, + 0x3c6ef372fe94f82bULL, + 0xa54ff53a5f1d36f1ULL, + 0x510e527fade682d1ULL, + 0x9b05688c2b3e6c1fULL, + 0x1f83d9abfb41bd6bULL, + 0x5be0cd19137e2179ULL +}; + +/* + * Constant used by SHA256/384/512_End() functions for converting the + * digest to a readable hexadecimal character string: + */ +static const char *sha2_hex_digits = "0123456789abcdef"; + + +/*** SHA-256: *********************************************************/ +void sha256_Init(SHA256_CTX* context) { + if (context == (SHA256_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha256_initial_hash_value, SHA256_DIGEST_LENGTH); + memzero(context->buffer, SHA256_BLOCK_LENGTH); + context->bitcount = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-256 round macros: */ + +#define ROUND256_0_TO_15(a,b,c,d,e,f,g,h) \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + \ + K256[j] + (W256[j] = *data++); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +#define ROUND256(a,b,c,d,e,f,g,h) \ + s0 = W256[(j+1)&0x0f]; \ + s0 = sigma0_256(s0); \ + s1 = W256[(j+14)&0x0f]; \ + s1 = sigma1_256(s1); \ + T1 = (h) + Sigma1_256(e) + Ch((e), (f), (g)) + K256[j] + \ + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); \ + (d) += T1; \ + (h) = T1 + Sigma0_256(a) + Maj((a), (b), (c)); \ + j++ + +void sha256_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { + sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0; + sha2_word32 T1 = 0; + sha2_word32 W256[16] = {0}; + int j = 0; + + /* Initialize registers with the prev. intermediate value */ + a = state_in[0]; + b = state_in[1]; + c = state_in[2]; + d = state_in[3]; + e = state_in[4]; + f = state_in[5]; + g = state_in[6]; + h = state_in[7]; + + j = 0; + do { + /* Rounds 0 to 15 (unrolled): */ + ROUND256_0_TO_15(a,b,c,d,e,f,g,h); + ROUND256_0_TO_15(h,a,b,c,d,e,f,g); + ROUND256_0_TO_15(g,h,a,b,c,d,e,f); + ROUND256_0_TO_15(f,g,h,a,b,c,d,e); + ROUND256_0_TO_15(e,f,g,h,a,b,c,d); + ROUND256_0_TO_15(d,e,f,g,h,a,b,c); + ROUND256_0_TO_15(c,d,e,f,g,h,a,b); + ROUND256_0_TO_15(b,c,d,e,f,g,h,a); + } while (j < 16); + + /* Now for the remaining rounds to 64: */ + do { + ROUND256(a,b,c,d,e,f,g,h); + ROUND256(h,a,b,c,d,e,f,g); + ROUND256(g,h,a,b,c,d,e,f); + ROUND256(f,g,h,a,b,c,d,e); + ROUND256(e,f,g,h,a,b,c,d); + ROUND256(d,e,f,g,h,a,b,c); + ROUND256(c,d,e,f,g,h,a,b); + ROUND256(b,c,d,e,f,g,h,a); + } while (j < 64); + + /* Compute the current intermediate hash value */ + state_out[0] = state_in[0] + a; + state_out[1] = state_in[1] + b; + state_out[2] = state_in[2] + c; + state_out[3] = state_in[3] + d; + state_out[4] = state_in[4] + e; + state_out[5] = state_in[5] + f; + state_out[6] = state_in[6] + g; + state_out[7] = state_in[7] + h; + + /* Clean up */ + // cppcheck-suppress unreadVariable + a = b = c = d = e = f = g = h = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +void sha256_Transform(const sha2_word32* state_in, const sha2_word32* data, sha2_word32* state_out) { + sha2_word32 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0; + sha2_word32 T1 = 0, T2 = 0 , W256[16] = {0}; + int j = 0; + + /* Initialize registers with the prev. intermediate value */ + a = state_in[0]; + b = state_in[1]; + c = state_in[2]; + d = state_in[3]; + e = state_in[4]; + f = state_in[5]; + g = state_in[6]; + h = state_in[7]; + + j = 0; + do { + /* Apply the SHA-256 compression function to update a..h with copy */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + (W256[j] = *data++); + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 16); + + do { + /* Part of the message block expansion: */ + sha2_word32 s0 = 0, s1 = 0; + s0 = W256[(j+1)&0x0f]; + s0 = sigma0_256(s0); + s1 = W256[(j+14)&0x0f]; + s1 = sigma1_256(s1); + + /* Apply the SHA-256 compression function to update a..h */ + T1 = h + Sigma1_256(e) + Ch(e, f, g) + K256[j] + + (W256[j&0x0f] += s1 + W256[(j+9)&0x0f] + s0); + T2 = Sigma0_256(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 64); + + /* Compute the current intermediate hash value */ + state_out[0] = state_in[0] + a; + state_out[1] = state_in[1] + b; + state_out[2] = state_in[2] + c; + state_out[3] = state_in[3] + d; + state_out[4] = state_in[4] + e; + state_out[5] = state_in[5] + f; + state_out[6] = state_in[6] + g; + state_out[7] = state_in[7] + h; + + /* Clean up */ + // cppcheck-suppress unreadVariable + a = b = c = d = e = f = g = h = T1 = T2 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void sha256_Update(SHA256_CTX* context, const sha2_byte *data, size_t len) { + unsigned int freespace = 0, usedspace = 0; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA256_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, freespace); + context->bitcount += freespace << 3; + len -= freespace; + data += freespace; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + sha256_Transform(context->state, context->buffer, context->state); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, len); + context->bitcount += len << 3; + /* Clean up: */ + // cppcheck-suppress unreadVariable + usedspace = freespace = 0; + return; + } + } + while (len >= SHA256_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + MEMCPY_BCOPY(context->buffer, data, SHA256_BLOCK_LENGTH); +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + sha256_Transform(context->state, context->buffer, context->state); + context->bitcount += SHA256_BLOCK_LENGTH << 3; + len -= SHA256_BLOCK_LENGTH; + data += SHA256_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + context->bitcount += len << 3; + } + /* Clean up: */ + // cppcheck-suppress unreadVariable + usedspace = freespace = 0; +} + +void sha256_Final(SHA256_CTX* context, sha2_byte digest[]) { + unsigned int usedspace = 0; + + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + usedspace = (context->bitcount >> 3) % SHA256_BLOCK_LENGTH; + /* Begin padding with a 1 bit: */ + ((uint8_t*)context->buffer)[usedspace++] = 0x80; + + if (usedspace > SHA256_SHORT_BLOCK_LENGTH) { + memzero(((uint8_t*)context->buffer) + usedspace, SHA256_BLOCK_LENGTH - usedspace); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + /* Do second-to-last transform: */ + sha256_Transform(context->state, context->buffer, context->state); + + /* And prepare the last transform: */ + usedspace = 0; + } + /* Set-up for the last transform: */ + memzero(((uint8_t*)context->buffer) + usedspace, SHA256_SHORT_BLOCK_LENGTH - usedspace); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 14; j++) { + REVERSE32(context->buffer[j],context->buffer[j]); + } +#endif + /* Set the bit count: */ + context->buffer[14] = context->bitcount >> 32; + context->buffer[15] = context->bitcount & 0xffffffff; + + /* Final transform: */ + sha256_Transform(context->state, context->buffer, context->state); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + for (int j = 0; j < 8; j++) { + REVERSE32(context->state[j],context->state[j]); + } +#endif + MEMCPY_BCOPY(digest, context->state, SHA256_DIGEST_LENGTH); + } + + /* Clean up state data: */ + memzero(context, sizeof(SHA256_CTX)); + // cppcheck-suppress unreadVariable + usedspace = 0; +} + +char *sha256_End(SHA256_CTX* context, char buffer[]) { + sha2_byte digest[SHA256_DIGEST_LENGTH] = {0}, *d = digest; + + if (buffer != (char*)0) { + sha256_Final(context, digest); + + for (int i = 0; i < SHA256_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + memzero(context, sizeof(SHA256_CTX)); + } + memzero(digest, SHA256_DIGEST_LENGTH); + return buffer; +} + +void sha256_Raw(const sha2_byte* data, size_t len, uint8_t digest[SHA256_DIGEST_LENGTH]) { + SHA256_CTX context = {0}; + sha256_Init(&context); + sha256_Update(&context, data, len); + sha256_Final(&context, digest); +} + +char* sha256_Data(const sha2_byte* data, size_t len, char digest[SHA256_DIGEST_STRING_LENGTH]) { + SHA256_CTX context = {0}; + + sha256_Init(&context); + sha256_Update(&context, data, len); + return sha256_End(&context, digest); +} + + +/*** SHA-512: *********************************************************/ +void sha512_Init(SHA512_CTX* context) { + if (context == (SHA512_CTX*)0) { + return; + } + MEMCPY_BCOPY(context->state, sha512_initial_hash_value, SHA512_DIGEST_LENGTH); + memzero(context->buffer, SHA512_BLOCK_LENGTH); + context->bitcount[0] = context->bitcount[1] = 0; +} + +#ifdef SHA2_UNROLL_TRANSFORM + +/* Unrolled SHA-512 round macros: */ +#define ROUND512_0_TO_15(a,b,c,d,e,f,g,h) \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + \ + K512[j] + (W512[j] = *data++); \ + (d) += T1; \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ + j++ + +#define ROUND512(a,b,c,d,e,f,g,h) \ + s0 = W512[(j+1)&0x0f]; \ + s0 = sigma0_512(s0); \ + s1 = W512[(j+14)&0x0f]; \ + s1 = sigma1_512(s1); \ + T1 = (h) + Sigma1_512(e) + Ch((e), (f), (g)) + K512[j] + \ + (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); \ + (d) += T1; \ + (h) = T1 + Sigma0_512(a) + Maj((a), (b), (c)); \ + j++ + +void sha512_Transform(const sha2_word64* state_in, const sha2_word64* data, sha2_word64* state_out) { + sha2_word64 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0; + sha2_word64 T1 = 0, W512[16] = {0}; + int j = 0; + + /* Initialize registers with the prev. intermediate value */ + a = state_in[0]; + b = state_in[1]; + c = state_in[2]; + d = state_in[3]; + e = state_in[4]; + f = state_in[5]; + g = state_in[6]; + h = state_in[7]; + + j = 0; + do { + ROUND512_0_TO_15(a,b,c,d,e,f,g,h); + ROUND512_0_TO_15(h,a,b,c,d,e,f,g); + ROUND512_0_TO_15(g,h,a,b,c,d,e,f); + ROUND512_0_TO_15(f,g,h,a,b,c,d,e); + ROUND512_0_TO_15(e,f,g,h,a,b,c,d); + ROUND512_0_TO_15(d,e,f,g,h,a,b,c); + ROUND512_0_TO_15(c,d,e,f,g,h,a,b); + ROUND512_0_TO_15(b,c,d,e,f,g,h,a); + } while (j < 16); + + /* Now for the remaining rounds up to 79: */ + do { + ROUND512(a,b,c,d,e,f,g,h); + ROUND512(h,a,b,c,d,e,f,g); + ROUND512(g,h,a,b,c,d,e,f); + ROUND512(f,g,h,a,b,c,d,e); + ROUND512(e,f,g,h,a,b,c,d); + ROUND512(d,e,f,g,h,a,b,c); + ROUND512(c,d,e,f,g,h,a,b); + ROUND512(b,c,d,e,f,g,h,a); + } while (j < 80); + + /* Compute the current intermediate hash value */ + state_out[0] = state_in[0] + a; + state_out[1] = state_in[1] + b; + state_out[2] = state_in[2] + c; + state_out[3] = state_in[3] + d; + state_out[4] = state_in[4] + e; + state_out[5] = state_in[5] + f; + state_out[6] = state_in[6] + g; + state_out[7] = state_in[7] + h; + + /* Clean up */ + // cppcheck-suppress unreadVariable + a = b = c = d = e = f = g = h = T1 = 0; +} + +#else /* SHA2_UNROLL_TRANSFORM */ + +void sha512_Transform(const sha2_word64* state_in, const sha2_word64* data, sha2_word64* state_out) { + sha2_word64 a = 0, b = 0, c = 0, d = 0, e = 0, f = 0, g = 0, h = 0; + sha2_word64 T1 = 0, T2 = 0, W512[16] = {0}; + int j = 0; + + /* Initialize registers with the prev. intermediate value */ + a = state_in[0]; + b = state_in[1]; + c = state_in[2]; + d = state_in[3]; + e = state_in[4]; + f = state_in[5]; + g = state_in[6]; + h = state_in[7]; + + j = 0; + do { + /* Apply the SHA-512 compression function to update a..h with copy */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + (W512[j] = *data++); + T2 = Sigma0_512(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 16); + + do { + /* Part of the message block expansion: */ + sha2_word64 s0 = 0, s1 = 0; + s0 = W512[(j+1)&0x0f]; + s0 = sigma0_512(s0); + s1 = W512[(j+14)&0x0f]; + s1 = sigma1_512(s1); + + /* Apply the SHA-512 compression function to update a..h */ + T1 = h + Sigma1_512(e) + Ch(e, f, g) + K512[j] + + (W512[j&0x0f] += s1 + W512[(j+9)&0x0f] + s0); + T2 = Sigma0_512(a) + Maj(a, b, c); + h = g; + g = f; + f = e; + e = d + T1; + d = c; + c = b; + b = a; + a = T1 + T2; + + j++; + } while (j < 80); + + /* Compute the current intermediate hash value */ + state_out[0] = state_in[0] + a; + state_out[1] = state_in[1] + b; + state_out[2] = state_in[2] + c; + state_out[3] = state_in[3] + d; + state_out[4] = state_in[4] + e; + state_out[5] = state_in[5] + f; + state_out[6] = state_in[6] + g; + state_out[7] = state_in[7] + h; + + /* Clean up */ + // cppcheck-suppress unreadVariable + a = b = c = d = e = f = g = h = T1 = T2 = 0; +} + +#endif /* SHA2_UNROLL_TRANSFORM */ + +void sha512_Update(SHA512_CTX* context, const sha2_byte *data, size_t len) { + unsigned int freespace = 0, usedspace = 0; + + if (len == 0) { + /* Calling with no data is valid - we do nothing */ + return; + } + + usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; + if (usedspace > 0) { + /* Calculate how much free space is available in the buffer */ + freespace = SHA512_BLOCK_LENGTH - usedspace; + + if (len >= freespace) { + /* Fill the buffer completely and process it */ + MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, freespace); + ADDINC128(context->bitcount, freespace << 3); + len -= freespace; + data += freespace; +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE64(context->buffer[j],context->buffer[j]); + } +#endif + sha512_Transform(context->state, context->buffer, context->state); + } else { + /* The buffer is not yet full */ + MEMCPY_BCOPY(((uint8_t*)context->buffer) + usedspace, data, len); + ADDINC128(context->bitcount, len << 3); + /* Clean up: */ + // cppcheck-suppress unreadVariable + usedspace = freespace = 0; + return; + } + } + while (len >= SHA512_BLOCK_LENGTH) { + /* Process as many complete blocks as we can */ + MEMCPY_BCOPY(context->buffer, data, SHA512_BLOCK_LENGTH); +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE64(context->buffer[j],context->buffer[j]); + } +#endif + sha512_Transform(context->state, context->buffer, context->state); + ADDINC128(context->bitcount, SHA512_BLOCK_LENGTH << 3); + len -= SHA512_BLOCK_LENGTH; + data += SHA512_BLOCK_LENGTH; + } + if (len > 0) { + /* There's left-overs, so save 'em */ + MEMCPY_BCOPY(context->buffer, data, len); + ADDINC128(context->bitcount, len << 3); + } + /* Clean up: */ + // cppcheck-suppress unreadVariable + usedspace = freespace = 0; +} + +static void sha512_Last(SHA512_CTX* context) { + unsigned int usedspace = 0; + + usedspace = (context->bitcount[0] >> 3) % SHA512_BLOCK_LENGTH; + /* Begin padding with a 1 bit: */ + ((uint8_t*)context->buffer)[usedspace++] = 0x80; + + if (usedspace > SHA512_SHORT_BLOCK_LENGTH) { + memzero(((uint8_t*)context->buffer) + usedspace, SHA512_BLOCK_LENGTH - usedspace); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 16; j++) { + REVERSE64(context->buffer[j],context->buffer[j]); + } +#endif + /* Do second-to-last transform: */ + sha512_Transform(context->state, context->buffer, context->state); + + /* And prepare the last transform: */ + usedspace = 0; + } + /* Set-up for the last transform: */ + memzero(((uint8_t*)context->buffer) + usedspace, SHA512_SHORT_BLOCK_LENGTH - usedspace); + +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert TO host byte order */ + for (int j = 0; j < 14; j++) { + REVERSE64(context->buffer[j],context->buffer[j]); + } +#endif + /* Store the length of input data (in bits): */ + context->buffer[14] = context->bitcount[1]; + context->buffer[15] = context->bitcount[0]; + + /* Final transform: */ + sha512_Transform(context->state, context->buffer, context->state); +} + +void sha512_Final(SHA512_CTX* context, sha2_byte digest[]) { + /* If no digest buffer is passed, we don't bother doing this: */ + if (digest != (sha2_byte*)0) { + sha512_Last(context); + + /* Save the hash data for output: */ +#if BYTE_ORDER == LITTLE_ENDIAN + /* Convert FROM host byte order */ + for (int j = 0; j < 8; j++) { + REVERSE64(context->state[j],context->state[j]); + } +#endif + MEMCPY_BCOPY(digest, context->state, SHA512_DIGEST_LENGTH); + } + + /* Zero out state data */ + memzero(context, sizeof(SHA512_CTX)); +} + +char *sha512_End(SHA512_CTX* context, char buffer[]) { + sha2_byte digest[SHA512_DIGEST_LENGTH] = {0}, *d = digest; + + if (buffer != (char*)0) { + sha512_Final(context, digest); + + for (int i = 0; i < SHA512_DIGEST_LENGTH; i++) { + *buffer++ = sha2_hex_digits[(*d & 0xf0) >> 4]; + *buffer++ = sha2_hex_digits[*d & 0x0f]; + d++; + } + *buffer = (char)0; + } else { + memzero(context, sizeof(SHA512_CTX)); + } + memzero(digest, SHA512_DIGEST_LENGTH); + return buffer; +} + +void sha512_Raw(const sha2_byte* data, size_t len, uint8_t digest[SHA512_DIGEST_LENGTH]) { + SHA512_CTX context = {0}; + sha512_Init(&context); + sha512_Update(&context, data, len); + sha512_Final(&context, digest); +} + +char* sha512_Data(const sha2_byte* data, size_t len, char digest[SHA512_DIGEST_STRING_LENGTH]) { + SHA512_CTX context = {0}; + + sha512_Init(&context); + sha512_Update(&context, data, len); + return sha512_End(&context, digest); +} diff --git a/src/otsur/bcur/sha2.h b/src/otsur/bcur/sha2.h new file mode 100644 index 0000000000..9426e56a57 --- /dev/null +++ b/src/otsur/bcur/sha2.h @@ -0,0 +1,100 @@ +/** + * Copyright (c) 2000-2001 Aaron D. Gifford + * Copyright (c) 2013-2014 Pavol Rusnak + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the names of contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef BC_UR_SHA2_H +#define BC_UR_SHA2_H + +#include +#include + +#define SHA256_BLOCK_LENGTH 64 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_STRING_LENGTH (SHA256_DIGEST_LENGTH * 2 + 1) +#define SHA512_BLOCK_LENGTH 128 +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_STRING_LENGTH (SHA512_DIGEST_LENGTH * 2 + 1) + +typedef struct _SHA256_CTX { + uint32_t state[8]; + uint64_t bitcount; + uint32_t buffer[SHA256_BLOCK_LENGTH/sizeof(uint32_t)]; +} SHA256_CTX; +typedef struct _SHA512_CTX { + uint64_t state[8]; + uint64_t bitcount[2]; + uint64_t buffer[SHA512_BLOCK_LENGTH/sizeof(uint64_t)]; +} SHA512_CTX; + +/*** ENDIAN REVERSAL MACROS *******************************************/ +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN 1234 +#define BIG_ENDIAN 4321 +#endif + +#ifndef BYTE_ORDER +#define BYTE_ORDER LITTLE_ENDIAN +#endif + +#if BYTE_ORDER == LITTLE_ENDIAN +#define REVERSE32(w,x) { \ + uint32_t tmp = (w); \ + tmp = (tmp >> 16) | (tmp << 16); \ + (x) = ((tmp & 0xff00ff00UL) >> 8) | ((tmp & 0x00ff00ffUL) << 8); \ +} +#define REVERSE64(w,x) { \ + uint64_t tmp = (w); \ + tmp = (tmp >> 32) | (tmp << 32); \ + tmp = ((tmp & 0xff00ff00ff00ff00ULL) >> 8) | \ + ((tmp & 0x00ff00ff00ff00ffULL) << 8); \ + (x) = ((tmp & 0xffff0000ffff0000ULL) >> 16) | \ + ((tmp & 0x0000ffff0000ffffULL) << 16); \ +} +#endif /* BYTE_ORDER == LITTLE_ENDIAN */ + +extern const uint32_t sha256_initial_hash_value[8]; +extern const uint64_t sha512_initial_hash_value[8]; + +void sha256_Transform(const uint32_t* state_in, const uint32_t* data, uint32_t* state_out); +void sha256_Init(SHA256_CTX *); +void sha256_Update(SHA256_CTX*, const uint8_t*, size_t); +void sha256_Final(SHA256_CTX*, uint8_t[SHA256_DIGEST_LENGTH]); +char* sha256_End(SHA256_CTX*, char[SHA256_DIGEST_STRING_LENGTH]); +void sha256_Raw(const uint8_t*, size_t, uint8_t[SHA256_DIGEST_LENGTH]); +char* sha256_Data(const uint8_t*, size_t, char[SHA256_DIGEST_STRING_LENGTH]); + +void sha512_Transform(const uint64_t* state_in, const uint64_t* data, uint64_t* state_out); +void sha512_Init(SHA512_CTX*); +void sha512_Update(SHA512_CTX*, const uint8_t*, size_t); +void sha512_Final(SHA512_CTX*, uint8_t[SHA512_DIGEST_LENGTH]); +char* sha512_End(SHA512_CTX*, char[SHA512_DIGEST_STRING_LENGTH]); +void sha512_Raw(const uint8_t*, size_t, uint8_t[SHA512_DIGEST_LENGTH]); +char* sha512_Data(const uint8_t*, size_t, char[SHA512_DIGEST_STRING_LENGTH]); + +#endif // BC_UR_SHA2_H diff --git a/src/otsur/bcur/ur-decoder.cpp b/src/otsur/bcur/ur-decoder.cpp new file mode 100644 index 0000000000..ec6cea2909 --- /dev/null +++ b/src/otsur/bcur/ur-decoder.cpp @@ -0,0 +1,119 @@ +// +// ur-decoder.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "ur-decoder.hpp" +#include "bytewords.hpp" + +using namespace std; + +namespace ur { + +UR URDecoder::decode(const string& s) { + auto [type, components] = parse(s); + + if(components.empty()) throw InvalidPathLength(); + auto body = components.front(); + + return decode(type, body); +} + +URDecoder::URDecoder() { } + +UR URDecoder::decode(const std::string& type, const std::string& body) { + auto cbor = Bytewords::decode(Bytewords::style::minimal, body); + return UR(type, cbor); +} + +pair URDecoder::parse(const string& s) { + // Don't consider case + auto lowered = to_lowercase(s); + + // Validate URI scheme + if(!has_prefix(lowered, "ur:")) throw InvalidScheme(); + auto path = drop_first(lowered, 3); + + // Split the remainder into path components + auto components = split(path, '/'); + + // Make sure there are at least two path components + if(components.size() < 2) throw InvalidPathLength(); + + // Validate the type + auto type = components.front(); + if(!is_ur_type(type)) throw InvalidType(); + + auto comps = StringVector(components.begin() + 1, components.end()); + return pair(type, comps); +} + +pair URDecoder::parse_sequence_component(const string& s) { + try { + auto comps = split(s, '-'); + if(comps.size() != 2) throw InvalidSequenceComponent(); + uint32_t seq_num = stoul(comps[0]); + size_t seq_len = stoul(comps[1]); + if(seq_num < 1 || seq_len < 1) throw InvalidSequenceComponent(); + return pair(seq_num, seq_len); + } catch(...) { + throw InvalidSequenceComponent(); + } +} + +bool URDecoder::validate_part(const std::string& type) { + if(!expected_type_.has_value()) { + if(!is_ur_type(type)) return false; + expected_type_ = type; + return true; + } else { + return type == expected_type_; + } +} + +bool URDecoder::receive_part(const std::string& s) { + try { + // Don't process the part if we're already done + if(result_.has_value()) return false; + + // Don't continue if this part doesn't validate + auto [type, components] = parse(s); + if(!validate_part(type)) return false; + + // If this is a single-part UR then we're done + if(components.size() == 1) { + auto body = components.front(); + result_ = decode(type, body); + return true; + } + + // Multi-part URs must have two path components: seq/fragment + if(components.size() != 2) throw InvalidPathLength(); + auto seq = components[0]; + auto fragment = components[1]; + + // Parse the sequence component and the fragment, and + // make sure they agree. + auto [seq_num, seq_len] = parse_sequence_component(seq); + auto cbor = Bytewords::decode(Bytewords::style::minimal, fragment); + auto part = FountainEncoder::Part(cbor); + if(seq_num != part.seq_num() || seq_len != part.seq_len()) return false; + + // Process the part + if(!fountain_decoder.receive_part(part)) return false; + + if(fountain_decoder.is_success()) { + result_ = UR(type, fountain_decoder.result_message()); + } else if(fountain_decoder.is_failure()) { + result_ = fountain_decoder.result_error(); + } + + return true; + } catch(...) { + return false; + } +} + +} diff --git a/src/otsur/bcur/ur-decoder.hpp b/src/otsur/bcur/ur-decoder.hpp new file mode 100644 index 0000000000..132fa779b4 --- /dev/null +++ b/src/otsur/bcur/ur-decoder.hpp @@ -0,0 +1,66 @@ +// +// ur-decoder.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_DECODER_HPP +#define BC_UR_DECODER_HPP + +#include +#include +#include +#include + +#include "ur.hpp" +#include "fountain-decoder.hpp" + +namespace ur { + +class URDecoder final { +public: + typedef std::optional > Result; + + class InvalidScheme: public std::exception { }; + class InvalidType: public std::exception { }; + class InvalidPathLength: public std::exception { }; + class InvalidSequenceComponent: public std::exception { }; + class InvalidFragment: public std::exception { }; + + // Decode a single-part UR. + static UR decode(const std::string& string); + + // Start decoding a (possibly) multi-part UR. + URDecoder(); + + const std::optional& expected_type() const { return expected_type_; } + size_t expected_part_count() const { return fountain_decoder.expected_part_count(); } + const PartIndexes& received_part_indexes() const { return fountain_decoder.received_part_indexes(); } + const PartIndexes& last_part_indexes() const { return fountain_decoder.last_part_indexes(); } + size_t processed_parts_count() const { return fountain_decoder.processed_parts_count(); } + double estimated_percent_complete() const { return fountain_decoder.estimated_percent_complete(); } + const Result& result() const { return result_; } + bool is_success() const { return result() && std::holds_alternative(result().value()); } + bool is_failure() const { return result() && std::holds_alternative(result().value()); } + bool is_complete() const { return result().has_value(); } + const UR& result_ur() const { return std::get(result().value()); } + const std::exception& result_error() const { return std::get(result().value()); } + + bool receive_part(const std::string& s); + +private: + FountainDecoder fountain_decoder; + + std::optional expected_type_; + Result result_; + + static std::pair parse(const std::string& string); + static std::pair parse_sequence_component(const std::string& string); + static UR decode(const std::string& type, const std::string& body); + bool validate_part(const std::string& type); +}; + +} + +#endif // BC_UR_DECODER_HPP \ No newline at end of file diff --git a/src/otsur/bcur/ur-encoder.cpp b/src/otsur/bcur/ur-encoder.cpp new file mode 100644 index 0000000000..873137bb54 --- /dev/null +++ b/src/otsur/bcur/ur-encoder.cpp @@ -0,0 +1,50 @@ +// +// ur-encoder.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "ur-encoder.hpp" +#include "bytewords.hpp" + +using namespace std; + +namespace ur { + +string UREncoder::encode(const UR& ur) { + auto body = Bytewords::encode(Bytewords::style::minimal, ur.cbor()); + return encode_ur({ur.type(), body}); +} + +UREncoder::UREncoder(const UR& ur, size_t max_fragment_len, uint32_t first_seq_num, size_t min_fragment_len) + : ur_(ur), + fountain_encoder_(FountainEncoder(ur.cbor(), max_fragment_len, first_seq_num, min_fragment_len)) +{ +} + +std::string UREncoder::next_part() { + auto part = fountain_encoder_.next_part(); + if(is_single_part()) { + return encode(ur_); + } else { + return encode_part(ur_.type(), part); + } +} + +string UREncoder::encode_part(const string& type, const FountainEncoder::Part& part) { + auto seq = to_string(part.seq_num()) + "-" + to_string(part.seq_len()); + auto body = Bytewords::encode(Bytewords::style::minimal, part.cbor()); + return encode_ur({type, seq, body}); +} + +string UREncoder::encode_uri(const string& scheme, const StringVector& path_components) { + auto path = join(path_components, "/"); + return join({scheme, path}, ":"); +} + +string UREncoder::encode_ur(const StringVector& path_components) { + return encode_uri("ur", path_components); +} + +} diff --git a/src/otsur/bcur/ur-encoder.hpp b/src/otsur/bcur/ur-encoder.hpp new file mode 100644 index 0000000000..5b2dec4a2a --- /dev/null +++ b/src/otsur/bcur/ur-encoder.hpp @@ -0,0 +1,52 @@ +// +// ur-encoder.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_ENCODER_HPP +#define BC_UR_ENCODER_HPP + +#include +#include "ur.hpp" +#include "utils.hpp" +#include "fountain-encoder.hpp" + +namespace ur { + +class UREncoder final { +public: + // Encode a single-part UR. + static std::string encode(const UR& ur); + + // Start encoding a (possibly) multi-part UR. + UREncoder(const UR& ur, size_t max_fragment_len, uint32_t first_seq_num = 0, size_t min_fragment_len = 10); + + uint32_t seq_num() const { return fountain_encoder_.seq_num(); } + size_t seq_len() const { return fountain_encoder_.seq_len(); } + PartIndexes last_part_indexes() const { return fountain_encoder_.last_part_indexes(); } + + // `true` if the minimal number of parts to transmit the message have been + // generated. Parts generated when this is `true` will be fountain codes + // containing various mixes of the part data. + bool is_complete() const { return fountain_encoder_.is_complete(); } + + // `true` if this UR can be contained in a single part. If `true`, repeated + // calls to `next_part()` will all return the same single-part UR. + bool is_single_part() const { return fountain_encoder_.is_single_part(); } + + std::string next_part(); + +private: + UR ur_; + FountainEncoder fountain_encoder_; + + static std::string encode_part(const std::string& type, const FountainEncoder::Part& part); + static std::string encode_uri(const std::string& scheme, const StringVector& path_components); + static std::string encode_ur(const StringVector& path_components); +}; + +} + +#endif // BC_UR_ENCODER_HPP \ No newline at end of file diff --git a/src/otsur/bcur/ur.cpp b/src/otsur/bcur/ur.cpp new file mode 100644 index 0000000000..222734048d --- /dev/null +++ b/src/otsur/bcur/ur.cpp @@ -0,0 +1,28 @@ +// +// ur.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "ur.hpp" + +#include + +using namespace std; + +namespace ur { + +UR::UR(const std::string &type, const ByteVector &cbor) + : type_(type), cbor_(cbor) +{ + if (!is_ur_type(type)) { + throw invalid_type(); + } +} + +bool operator==(const UR& lhs, const UR& rhs) { + return lhs.type() == rhs.type() && lhs.cbor() == rhs.cbor(); +} + +} // namespace ur diff --git a/src/otsur/bcur/ur.hpp b/src/otsur/bcur/ur.hpp new file mode 100644 index 0000000000..c26943c4d2 --- /dev/null +++ b/src/otsur/bcur/ur.hpp @@ -0,0 +1,34 @@ +// +// ur.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef BC_UR_UR_HPP +#define BC_UR_UR_HPP + +#include +#include +#include "utils.hpp" + +namespace ur { + +class UR final { +private: + std::string type_; + ByteVector cbor_; +public: + class invalid_type: public std::exception { }; + + const std::string& type() const { return type_; } + const ByteVector& cbor() const { return cbor_; } + + UR(const std::string& type, const ByteVector& cbor); +}; + +bool operator==(const UR& lhs, const UR& rhs); + +} + +#endif // BC_UR_UR_HPP \ No newline at end of file diff --git a/src/otsur/bcur/utils.cpp b/src/otsur/bcur/utils.cpp new file mode 100644 index 0000000000..b75fcdacb7 --- /dev/null +++ b/src/otsur/bcur/utils.cpp @@ -0,0 +1,172 @@ +// +// utils.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include +#include "utils.hpp" + +extern "C" { + +#include "sha2.h" +#include "crc32.h" + +} + +#include +#include +#include +#include + +using namespace std; + +namespace ur { + +ByteVector sha256(const ByteVector &buf) { + uint8_t digest[SHA256_DIGEST_LENGTH]; + sha256_Raw(&buf[0], buf.size(), digest); + return ByteVector(digest, digest + SHA256_DIGEST_LENGTH); +} + +ByteVector crc32_bytes(const ByteVector &buf) { + uint32_t checksum = ur_crc32n(&buf[0], buf.size()); + auto cbegin = (uint8_t*)&checksum; + auto cend = cbegin + sizeof(uint32_t); + return ByteVector(cbegin, cend); +} + +uint32_t crc32_int(const ByteVector &buf) { + return ur_crc32(&buf[0], buf.size()); +} + +ByteVector string_to_bytes(const string& s) { + return ByteVector(s.begin(), s.end()); +} + +string data_to_hex(const ByteVector& in) { + auto hex = "0123456789abcdef"; + string result; + for(auto c: in) { + result.append(1, hex[(c >> 4) & 0xF]); + result.append(1, hex[c & 0xF]); + } + return result; +} + +string data_to_hex(uint32_t n) { + return data_to_hex(int_to_bytes(n)); +} + +ByteVector int_to_bytes(uint32_t n) { + ByteVector b; + b.reserve(4); + b.push_back((n >> 24 & 0xff)); + b.push_back((n >> 16) & 0xff); + b.push_back((n >> 8) & 0xff); + b.push_back(n & 0xff); + return b; +} + +uint32_t bytes_to_int(const ByteVector& in) { + assert(in.size() >= 4); + uint32_t result = 0; + result |= in[0] << 24; + result |= in[1] << 16; + result |= in[2] << 8; + result |= in[3]; + return result; +} + +string join(const StringVector &strings, const string &separator) { + ostringstream result; + bool first = true; + for(auto s: strings) { + if(!first) { + result << separator; + } + result << s; + first = false; + } + return result.str(); +} + +StringVector split(const string& s, char separator) { + StringVector result; + string buf; + + for(auto c: s) { + if(c != separator) { + buf += c; + } else if(c == separator && buf.length() > 0) { + result.push_back(buf); + buf = ""; + } + } + + if(buf != "") { + result.push_back(buf); + } + + return result; +} + +StringVector partition(const string& s, size_t size) { + StringVector result; + auto remaining = s; + while(remaining.length() > 0) { + result.push_back(take_first(remaining, size)); + remaining = drop_first(remaining, size); + } + return result; +} + +string take_first(const string &s, size_t count) { + auto first = s.begin(); + auto c = min(s.size(), count); + auto last = first + c; + return string(first, last); +} + +string drop_first(const string& s, size_t count) { + if(count >= s.length()) { return ""; } + return string(s.begin() + count, s.end()); +} + +void xor_into(ByteVector& target, const ByteVector& source) { + auto count = target.size(); + assert(count == source.size()); + for(int i = 0; i < count; i++) { + target[i] ^= source[i]; + } +} + +ByteVector xor_with(const ByteVector& a, const ByteVector& b) { + auto target = a; + xor_into(target, b); + return target; +} + +bool is_ur_type(char c) { + if('a' <= c && c <= 'z') return true; + if('0' <= c && c <= '9') return true; + if(c == '-') return true; + return false; +} + +bool is_ur_type(const string& s) { + return none_of(s.begin(), s.end(), [](auto c) { return !is_ur_type(c); }); +} + +string to_lowercase(const string& s) { + string result; + transform(s.begin(), s.end(), back_inserter(result), [](char c){ return tolower(c); }); + return result; +} + +bool has_prefix(const string& s, const string& prefix) { + return s.rfind(prefix, 0) == 0; +} + +} diff --git a/src/otsur/bcur/utils.hpp b/src/otsur/bcur/utils.hpp new file mode 100644 index 0000000000..1b7c6e3402 --- /dev/null +++ b/src/otsur/bcur/utils.hpp @@ -0,0 +1,88 @@ +// +// utils.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef UTILS_HPP +#define UTILS_HPP + +#include +#include +#include +#include +#include + +namespace ur { + +typedef std::vector ByteVector; +typedef std::vector StringVector; + +ByteVector sha256(const ByteVector &buf); +ByteVector crc32_bytes(const ByteVector &buf); +uint32_t crc32_int(const ByteVector &buf); + +ByteVector string_to_bytes(const std::string& s); + +std::string data_to_hex(const ByteVector& in); +std::string data_to_hex(uint32_t n); + +ByteVector int_to_bytes(uint32_t n); +uint32_t bytes_to_int(const ByteVector& in); + +std::string join(const StringVector &strings, const std::string &separator); +StringVector split(const std::string& s, char separator); + +StringVector partition(const std::string& string, size_t size); + +std::string take_first(const std::string &s, size_t count); +std::string drop_first(const std::string &s, size_t count); + +template +void append(std::vector& target, const std::vector& source) { + target.insert(target.end(), source.begin(), source.end()); +} + +template +void append(std::vector& target, const std::array& source) { + target.insert(target.end(), source.begin(), source.end()); +} + +template +std::vector join(const std::vector>& parts) { + std::vector result; + for(auto part: parts) { append(result, part); } + return result; +} + +template +std::pair, std::vector> split(const std::vector& buf, size_t count) { + auto first = buf.begin(); + auto c = std::min(buf.size(), count); + auto last = first + c; + auto a = std::vector(first, last); + auto b = std::vector(last, buf.end()); + return std::make_pair(a, b); +} + +template +std::vector take_first(const std::vector &buf, size_t count) { + auto first = buf.begin(); + auto c = std::min(buf.size(), count); + auto last = first + c; + return std::vector(first, last); +} + +void xor_into(ByteVector& target, const ByteVector& source); +ByteVector xor_with(const ByteVector& a, const ByteVector& b); + +bool is_ur_type(char c); +bool is_ur_type(const std::string& s); + +std::string to_lowercase(const std::string& s); +bool has_prefix(const std::string& s, const std::string& prefix); + +} + +#endif // UTILS_HPP diff --git a/src/otsur/bcur/xoshiro256.cpp b/src/otsur/bcur/xoshiro256.cpp new file mode 100644 index 0000000000..2f40b8bcd3 --- /dev/null +++ b/src/otsur/bcur/xoshiro256.cpp @@ -0,0 +1,177 @@ +// +// xoshiro256.cpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#include "xoshiro256.hpp" +#include +#include + +/* Written in 2018 by David Blackman and Sebastiano Vigna (vigna@acm.org) + +To the extent possible under law, the author has dedicated all copyright +and related and neighboring rights to this software to the public domain +worldwide. This software is distributed without any warranty. + +See . */ + +/* This is xoshiro256** 1.0, one of our all-purpose, rock-solid + generators. It has excellent (sub-ns) speed, a state (256 bits) that is + large enough for any parallel application, and it passes all tests we + are aware of. + + For generating just floating-point numbers, xoshiro256+ is even faster. + + The state must be seeded so that it is not everywhere zero. If you have + a 64-bit seed, we suggest to seed a splitmix64 generator and use its + output to fill s. */ + +namespace ur { + +static inline uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +Xoshiro256::Xoshiro256(const std::array& a) { + s[0] = a[0]; + s[1] = a[1]; + s[2] = a[2]; + s[3] = a[3]; +} + +void Xoshiro256::set_s(const std::array& a) { + for(int i = 0; i < 4; i++) { + auto o = i * 8; + uint64_t v = 0; + for(int n = 0; n < 8; n++) { + v <<= 8; + v |= a[o + n]; + } + s[i] = v; + } +} + +void Xoshiro256::hash_then_set_s(const ByteVector& bytes) { + auto digest = sha256(bytes); + std::array a; + memcpy(a.data(), &digest[0], 32); + set_s(a); +} + +Xoshiro256::Xoshiro256(const std::array& a) { + set_s(a); +} + +Xoshiro256::Xoshiro256(const ByteVector& bytes) { + hash_then_set_s(bytes); +} + +Xoshiro256::Xoshiro256(const std::string& s) { + ByteVector bytes(s.begin(), s.end()); + hash_then_set_s(bytes); +} + +Xoshiro256::Xoshiro256(uint32_t crc32) { + auto bytes = int_to_bytes(crc32); + hash_then_set_s(bytes); +} + +double Xoshiro256::next_double() { + auto m = ((double)std::numeric_limits::max()) + 1; + return next() / m; +} + +uint64_t Xoshiro256::next_int(uint64_t low, uint64_t high) { + return uint64_t(next_double() * (high - low + 1)) + low; +} + +uint8_t Xoshiro256::next_byte() { + return uint8_t(next_int(0, 255)); +} + +ByteVector Xoshiro256::next_data(size_t count) { + ByteVector result; + result.reserve(count); + for(int i = 0; i < count; i++) { + result.push_back(next_byte()); + } + return result; +} + +uint64_t Xoshiro256::next() { + const uint64_t result = rotl(s[1] * 5, 7) * 9; + + const uint64_t t = s[1] << 17; + + s[2] ^= s[0]; + s[3] ^= s[1]; + s[1] ^= s[2]; + s[0] ^= s[3]; + + s[2] ^= t; + + s[3] = rotl(s[3], 45); + + return result; +} + +/* This is the jump function for the generator. It is equivalent + to 2^128 calls to next(); it can be used to generate 2^128 + non-overlapping subsequences for parallel computations. */ + +void Xoshiro256::jump() { + static const uint64_t JUMP[] = { 0x180ec6d33cfd0aba, 0xd5a61266f0c9392c, 0xa9582618e03fc9aa, 0x39abdc4529b1661c }; + + uint64_t s0 = 0; + uint64_t s1 = 0; + uint64_t s2 = 0; + uint64_t s3 = 0; + for(int i = 0; i < sizeof JUMP / sizeof *JUMP; i++) + for(int b = 0; b < 64; b++) { + if (JUMP[i] & UINT64_C(1) << b) { + s0 ^= s[0]; + s1 ^= s[1]; + s2 ^= s[2]; + s3 ^= s[3]; + } + next(); + } + + s[0] = s0; + s[1] = s1; + s[2] = s2; + s[3] = s3; +} + +/* This is the long-jump function for the generator. It is equivalent to + 2^192 calls to next(); it can be used to generate 2^64 starting points, + from each of which jump() will generate 2^64 non-overlapping + subsequences for parallel distributed computations. */ + +void Xoshiro256::long_jump() { + static const uint64_t LONG_JUMP[] = { 0x76e15d3efefdcbbf, 0xc5004e441c522fb3, 0x77710069854ee241, 0x39109bb02acbe635 }; + + uint64_t s0 = 0; + uint64_t s1 = 0; + uint64_t s2 = 0; + uint64_t s3 = 0; + for(int i = 0; i < sizeof LONG_JUMP / sizeof *LONG_JUMP; i++) + for(int b = 0; b < 64; b++) { + if (LONG_JUMP[i] & UINT64_C(1) << b) { + s0 ^= s[0]; + s1 ^= s[1]; + s2 ^= s[2]; + s3 ^= s[3]; + } + next(); + } + + s[0] = s0; + s[1] = s1; + s[2] = s2; + s[3] = s3; +} + +} diff --git a/src/otsur/bcur/xoshiro256.hpp b/src/otsur/bcur/xoshiro256.hpp new file mode 100644 index 0000000000..2654cbc61a --- /dev/null +++ b/src/otsur/bcur/xoshiro256.hpp @@ -0,0 +1,45 @@ +// +// xoshiro256.hpp +// +// Copyright © 2020 by Blockchain Commons, LLC +// Licensed under the "BSD-2-Clause Plus Patent License" +// + +#ifndef XOSHIRO256_HPP +#define XOSHIRO256_HPP + +#include +#include +#include +#include "utils.hpp" + +namespace ur { + +class Xoshiro256 { +public: + explicit Xoshiro256(const std::array& a); + explicit Xoshiro256(const std::array& a); + + explicit Xoshiro256(const ByteVector& bytes); + explicit Xoshiro256(const std::string& s); + explicit Xoshiro256(uint32_t crc32); + + uint64_t next(); + double next_double(); + uint64_t next_int(uint64_t low, uint64_t high); + uint8_t next_byte(); + ByteVector next_data(size_t count); + + void jump(); + void long_jump(); + +private: + uint64_t s[4]; + + void set_s(const std::array& a); + void hash_then_set_s(const ByteVector& bytes); +}; + +} + +#endif // XOSHIRO256_HPP diff --git a/src/otsur/data/CMakeLists.txt b/src/otsur/data/CMakeLists.txt new file mode 100644 index 0000000000..6afe97f7f9 --- /dev/null +++ b/src/otsur/data/CMakeLists.txt @@ -0,0 +1,23 @@ +set(QRCODE_SOURCE + MoneroData.cpp +) +set(QRCODE_HEADERS + MoneroData.h + MoneroTxData.h + MoneroWalletData.h + UrTypes.h +) + +if (QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core REQUIRED) +else() + find_package(Qt5 COMPONENTS Core REQUIRED) +endif() +message("qrcode Qt version: " ${QT_VERSION} "(Qt" ${QT_VERSION_MAJOR} ")") + +add_library(otsdata STATIC ${QRCODE_SOURCE} ${QRCODE_HEADERS}) +target_include_directories(otsdata PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(otsdata PRIVATE + Qt${QT_VERSION_MAJOR}::Core +) diff --git a/src/otsur/data/MoneroData.cpp b/src/otsur/data/MoneroData.cpp new file mode 100644 index 0000000000..b64f50c3a7 --- /dev/null +++ b/src/otsur/data/MoneroData.cpp @@ -0,0 +1,110 @@ +#include "MoneroData.h" +#include +#include +#include +#include + +MoneroData::MoneroData(QObject *parent) : QObject(parent) {} + +MoneroData::~MoneroData() {} + +MoneroTxData *MoneroData::parseTxData(const QString &data) +{ + return parseTxDataUri(data); +} + +MoneroWalletData *MoneroData::parseWalletData(const QString &data, bool fallbackToJson) +{ + MoneroWalletData* out = parseWalletDataUri(data); + if(out == nullptr && fallbackToJson) + out = parseWalletDataJson(data); + return out; +} + +MoneroTxData* MoneroData::parseTxDataUri(const QString &uri) { + QUrl url(uri); + if (url.scheme()!= "monero") + return nullptr; // invalid scheme + MoneroTxData* data = new MoneroTxData(); + data->setAddress(url.path()); + QUrlQuery query(url.query()); + data->setTxPaymentId(query.queryItemValue(URI_TX_PARAM_PAYMENT_ID)); + data->setRecipientName(query.queryItemValue(URI_TX_PARAM_RECIPIENT_NAME)); + data->setTxAmount(query.queryItemValue(URI_TX_PARAM_TX_AMOUNT)); + data->setTxDescription(query.queryItemValue(URI_TX_PARAM_TX_DESCRIPTION)); + return data; +} + +MoneroWalletData* MoneroData::parseWalletDataUri(const QString &uri) { + QUrl url(uri.trimmed().replace(URI_WALLET_CURRENT_BAD_SCHEME, URI_WALLET_CORRECT_SCHEME)); // fix broken monero_wallet scheme, us trimmed to get a non-const QString for replace. Even if it get's corrected it would need to stay for backward compability. + if (url.scheme()!= URI_WALLET_CORRECT_SCHEME) + return nullptr; // invalid scheme + MoneroWalletData* data = new MoneroWalletData(); + data->setAddress(url.path()); + QUrlQuery query(url.query()); + data->setSpendKey(query.queryItemValue(URI_WALLET_PARAM_SPEND_KEY)); + data->setViewKey(query.queryItemValue(URI_WALLET_PARAM_VIEW_KEY)); + data->setMnemonicSeed(query.queryItemValue(URI_WALLET_PARAM_MNEMONIC_SEED)); + data->setHeight(query.queryItemValue(URI_WALLET_PARAM_HEIGHT).toInt()); + return data; +} + +QString MoneroData::buildTxDataUri(MoneroTxData &data) { + QUrl url; + url.setScheme(URI_TX_SCHEME); + url.setPath(data.address()); + QUrlQuery query; + if (!data.txPaymentId().isEmpty()) + query.addQueryItem(URI_TX_PARAM_PAYMENT_ID, data.txPaymentId()); + if (!data.recipientName().isEmpty()) + query.addQueryItem(URI_TX_PARAM_RECIPIENT_NAME, data.recipientName()); + if (!data.txAmount().isEmpty()) + query.addQueryItem(URI_TX_PARAM_TX_AMOUNT, data.txAmount()); + if (!data.txDescription().isEmpty()) + query.addQueryItem(URI_TX_PARAM_TX_DESCRIPTION, data.txDescription()); + url.setQuery(query); + return url.toString(); +} + +QString MoneroData::buildWalletDataUri(MoneroWalletData &data, bool correctedScheme) { + QUrl url; + url.setScheme(correctedScheme?URI_WALLET_CORRECT_SCHEME:URI_WALLET_CURRENT_BAD_SCHEME); + url.setPath(data.address()); + QUrlQuery query; + if (!data.spendKey().isEmpty()) + query.addQueryItem(URI_WALLET_PARAM_SPEND_KEY, data.spendKey()); + if (!data.viewKey().isEmpty()) + query.addQueryItem(URI_WALLET_PARAM_VIEW_KEY, data.viewKey()); + if (!data.mnemonicSeed().isEmpty()) + query.addQueryItem(URI_WALLET_PARAM_MNEMONIC_SEED, data.mnemonicSeed()); + if (data.height()!= 0) + query.addQueryItem(URI_WALLET_PARAM_HEIGHT, QString::number(data.height())); + url.setQuery(query); + return url.toString(); +} + +MoneroWalletData* MoneroData::parseWalletDataJson(const QString &json) { + QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8()); + if (doc.isNull() || !doc.isObject()) + return nullptr; // invalid JSON + QJsonObject obj = doc.object(); + MoneroWalletData* data = new MoneroWalletData(); + data->setAddress(obj[JSON_WALLET_ADDRESS].toString()); + data->setSpendKey(obj[JSON_WALLET_SPEND_KEY].toString()); + data->setViewKey(obj[JSON_WALLET_VIEW_KEY].toString()); + data->setHeight(obj[JSON_WALLET_HEIGHT].toInt()); + return data; +} + +QString MoneroData::buildWalletDataJson(MoneroWalletData &data) { + QJsonObject obj; + obj[JSON_WALLET_ADDRESS] = data.address(); + if (!data.spendKey().isEmpty()) + obj[JSON_WALLET_SPEND_KEY] = data.spendKey(); + if (!data.viewKey().isEmpty()) + obj[JSON_WALLET_VIEW_KEY] = data.viewKey(); + if (data.height() != 0) + obj[JSON_WALLET_HEIGHT] = data.height(); + QJsonDocument doc(obj); + return QString(doc.toJson(QJsonDocument::Compact)); +} diff --git a/src/otsur/data/MoneroData.h b/src/otsur/data/MoneroData.h new file mode 100644 index 0000000000..974cd49908 --- /dev/null +++ b/src/otsur/data/MoneroData.h @@ -0,0 +1,41 @@ +#ifndef MONERO_DATA_H +#define MONERO_DATA_H + +#include +#include "MoneroTxData.h" +#include "MoneroWalletData.h" + +#define URI_TX_SCHEME "monero" +#define URI_TX_PARAM_PAYMENT_ID "tx_payment_id" +#define URI_TX_PARAM_RECIPIENT_NAME "recipient_name" +#define URI_TX_PARAM_TX_AMOUNT "tx_amount" +#define URI_TX_PARAM_TX_DESCRIPTION "tx_description" + +#define URI_WALLET_CURRENT_BAD_SCHEME "monero_wallet" +#define URI_WALLET_CORRECT_SCHEME "monero-wallet" +#define URI_WALLET_PARAM_SPEND_KEY "spend_key" +#define URI_WALLET_PARAM_VIEW_KEY "view_key" +#define URI_WALLET_PARAM_MNEMONIC_SEED "mnemonic_seed" +#define URI_WALLET_PARAM_HEIGHT "height" + +#define JSON_WALLET_ADDRESS "primaryAddress" +#define JSON_WALLET_SPEND_KEY "privateSpendKey" +#define JSON_WALLET_VIEW_KEY "privateViewKey" +#define JSON_WALLET_HEIGHT "restoreHeight" + +class MoneroData : public QObject { + Q_OBJECT + +public: + MoneroData(QObject *parent = nullptr); + ~MoneroData(); + Q_INVOKABLE static MoneroTxData* parseTxData(const QString &data); + Q_INVOKABLE static MoneroWalletData* parseWalletData(const QString &data, bool fallbackToJson = true); + Q_INVOKABLE static MoneroTxData* parseTxDataUri(const QString &uri); + Q_INVOKABLE static MoneroWalletData* parseWalletDataUri(const QString &uri); + Q_INVOKABLE static MoneroWalletData* parseWalletDataJson(const QString &json); // for ANONERO and feather + Q_INVOKABLE static QString buildTxDataUri(MoneroTxData &data); + Q_INVOKABLE static QString buildWalletDataUri(MoneroWalletData &data, bool correctedScheme = false); + Q_INVOKABLE static QString buildWalletDataJson(MoneroWalletData &data); // for ANONERO and feather +}; +#endif // MONERO_DATA_H diff --git a/src/otsur/data/MoneroTxData.h b/src/otsur/data/MoneroTxData.h new file mode 100644 index 0000000000..9323aade19 --- /dev/null +++ b/src/otsur/data/MoneroTxData.h @@ -0,0 +1,49 @@ +#ifndef MONEROTXDATA_H +#define MONEROTXDATA_H + +#include + +class MoneroTxData : public QObject { + Q_OBJECT + Q_PROPERTY(QString address READ address WRITE setAddress FINAL) + Q_PROPERTY(QString txPaymentId READ txPaymentId WRITE setTxPaymentId FINAL) + Q_PROPERTY(QString recipientName READ recipientName WRITE setRecipientName FINAL) + Q_PROPERTY(QString txAmount READ txAmount WRITE setTxAmount FINAL) + Q_PROPERTY(QString txDescription READ txDescription WRITE setTxDescription FINAL) + Q_PROPERTY(bool isValid READ isValid CONSTANT FINAL) + +public: + MoneroTxData( + const QString &address = "", + const QString &tx_payment_id = "", + const QString &recipient_name = "", + const QString &tx_amount = "", + const QString &tx_description = "" + ) : m_address(address) + , m_tx_payment_id(tx_payment_id) + , m_recipient_name(recipient_name) + , m_tx_amount(tx_amount) + , m_tx_description(tx_description) + {} + + QString address() { return m_address; } + void setAddress(const QString value) { m_address = value; } + QString txPaymentId() { return m_tx_payment_id; } + void setTxPaymentId(const QString value) { m_tx_payment_id = value; } + QString recipientName() { return m_recipient_name; } + void setRecipientName(const QString value) { m_recipient_name = value; } + QString txAmount() { return m_tx_amount; } + void setTxAmount(const QString value) { m_tx_amount = value; } + QString txDescription() { return m_tx_description; } + void setTxDescription(const QString value) { m_tx_description = value; } + bool isValid() { return !m_address.isEmpty(); } + +protected: + QString m_address; + QString m_tx_payment_id; + QString m_recipient_name; + QString m_tx_amount; + QString m_tx_description; +}; + +#endif // MONEROTXDATA_H diff --git a/src/otsur/data/MoneroWalletData.h b/src/otsur/data/MoneroWalletData.h new file mode 100644 index 0000000000..8bf3e5ae0b --- /dev/null +++ b/src/otsur/data/MoneroWalletData.h @@ -0,0 +1,61 @@ +#ifndef MONEROWALLETDATA_H +#define MONEROWALLETDATA_H + +#include + +class MoneroWalletData : public QObject { + Q_OBJECT + Q_PROPERTY(QString address READ address WRITE setAddress FINAL) + Q_PROPERTY(QString spendKey READ spendKey WRITE setSpendKey FINAL) + Q_PROPERTY(QString viewKey READ viewKey WRITE setViewKey FINAL) + Q_PROPERTY(QString mnemonicSeed READ mnemonicSeed WRITE setMnemonicSeed FINAL) + Q_PROPERTY(int height READ height WRITE setHeight FINAL) + Q_PROPERTY(bool isWallet READ isWallet CONSTANT FINAL) + Q_PROPERTY(bool isViewOnly READ isViewOnly CONSTANT FINAL) + Q_PROPERTY(bool isSeed READ isSeed CONSTANT FINAL) + Q_PROPERTY(bool isValid READ isValid CONSTANT FINAL) + +public: + MoneroWalletData( + const QString &address = "", + const QString &spend_key = "", + const QString &view_key = "", + const QString &mnemonic_seed = "", + int height = 0 + ) : m_address(address) + , m_spend_key(spend_key) + , m_view_key(view_key) + , m_mnemonic_seed(mnemonic_seed) + , m_height(height) + {} + + QString address() { return m_address; } + void setAddress(const QString value) { m_address = value; } + + QString spendKey() { return m_spend_key; } + void setSpendKey(const QString value) { m_spend_key = value; } + + QString viewKey() { return m_view_key; } + void setViewKey(const QString value) { m_view_key = value; } + + QString mnemonicSeed() { return m_mnemonic_seed; } + void setMnemonicSeed(const QString value) { m_mnemonic_seed = value; } + + int height() { return m_height; } + void setHeight(int value) { m_height = value; } + + bool isWallet() { return !m_address.isEmpty() && !m_spend_key.isEmpty() && !m_view_key.isEmpty(); } + bool isViewOnly() { return m_spend_key.isEmpty() && !m_view_key.isEmpty() && !m_address.isEmpty(); } + bool isSeed() { return !m_mnemonic_seed.isEmpty() && m_spend_key.isEmpty() && m_view_key.isEmpty(); } + bool isValid() { return isWallet() || isViewOnly() || isSeed(); } + Q_INVOKABLE QStringList seedWords() { return m_mnemonic_seed.split(" "); } + +protected: + QString m_address; + QString m_spend_key; + QString m_view_key; + QString m_mnemonic_seed; + int m_height; +}; + +#endif // MONEROWALLETDATA_H diff --git a/src/otsur/data/UrTypes.h b/src/otsur/data/UrTypes.h new file mode 100644 index 0000000000..22a72061e7 --- /dev/null +++ b/src/otsur/data/UrTypes.h @@ -0,0 +1,9 @@ +#ifndef URTYPES_H +#define URTYPES_H + +#define XMR_OUTPUT "xmr-output" // 610 +#define XMR_KEY_IMAGE "xmr-keyimage" // 611 +#define XMR_TX_UNSIGNED "xmr-txunsigned" // 612 +#define XMR_TX_SIGNED "xmr-txsigned" // 613 + +#endif // URTYPES_H diff --git a/src/otsur/qrcode/CMakeLists.txt b/src/otsur/qrcode/CMakeLists.txt new file mode 100644 index 0000000000..a663b7dbb6 --- /dev/null +++ b/src/otsur/qrcode/CMakeLists.txt @@ -0,0 +1,25 @@ +set(QRCODE_SOURCE + QrCode.cpp +) +set(QRCODE_HEADERS + QrCode.h + QrCode_p.h +) + +if (QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Gui Widgets Svg REQUIRED) +else() + find_package(Qt5 COMPONENTS Core Gui Widgets Svg REQUIRED) +endif() +message("qrcode Qt version: " ${QT_VERSION} "(Qt" ${QT_VERSION_MAJOR} ")") + +add_library(qrcode STATIC ${QRCODE_SOURCE} ${QRCODE_HEADERS}) +target_include_directories(qrcode PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) +target_link_libraries(qrcode PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Gui + Qt${QT_VERSION_MAJOR}::Widgets + Qt${QT_VERSION_MAJOR}::Svg + qrencode +) diff --git a/src/otsur/qrcode/QrCode.cpp b/src/otsur/qrcode/QrCode.cpp new file mode 100644 index 0000000000..7326826e57 --- /dev/null +++ b/src/otsur/qrcode/QrCode.cpp @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Portions copyright (c) 2020, The Monero Project + */ + +#include "QrCode.h" +#include "QrCode_p.h" + +#include +#include +#include +#include + +#include + +QrCodePrivate::QrCodePrivate() + : m_qrcode(nullptr) +{ +} + +QrCodePrivate::~QrCodePrivate() +{ + if (m_qrcode) { + QRcode_free(m_qrcode); + } +} + +QrCode::QrCode() + : d_ptr(new QrCodePrivate()) +{ +} + +QrCode::QrCode(const QString& data, const Version version, const ErrorCorrectionLevel ecl, const bool caseSensitive) + : d_ptr(new QrCodePrivate()) +{ + init(data, version, ecl, caseSensitive); +} + +QrCode::QrCode(const QByteArray& data, const Version version, const ErrorCorrectionLevel ecl) + : d_ptr(new QrCodePrivate()) +{ + init(data, version, ecl); +} + +QrCode::~QrCode() = default; + +void QrCode::init(const QString& data, const Version version, const ErrorCorrectionLevel ecl, bool caseSensitive) +{ + if (data.isEmpty()) { + return; + } + + d_ptr->m_qrcode = QRcode_encodeString(data.toLocal8Bit().data(), + static_cast(version), + static_cast(ecl), + QR_MODE_8, + caseSensitive ? 1 : 0); +} + +void QrCode::init(const QByteArray& data, const Version version, const ErrorCorrectionLevel ecl) +{ + if (data.isEmpty()) { + return; + } + + d_ptr->m_qrcode = QRcode_encodeData(data.size(), + reinterpret_cast(data.data()), + static_cast(version), + static_cast(ecl)); +} + +bool QrCode::isValid() const +{ + return d_ptr->m_qrcode != nullptr; +} + +void QrCode::writeSvg(QIODevice* outputDevice, const int dpi, const int margin) const +{ + if (margin < 0 || d_ptr->m_qrcode == nullptr || outputDevice == nullptr) { + return; + } + + const int width = d_ptr->m_qrcode->width + margin * 2; + + QSvgGenerator generator; + generator.setSize(QSize(width, width)); + generator.setViewBox(QRect(0, 0, width, width)); + generator.setResolution(dpi); + generator.setOutputDevice(outputDevice); + + QPainter painter; + painter.begin(&generator); + + // Background + painter.setClipRect(QRect(0, 0, width, width)); + painter.fillRect(QRect(0, 0, width, width), Qt::white); + + // Foreground + // "Dots" are stored in a quint8 x quint8 array using row-major order. + // A dot is black if the LSB of its corresponding quint8 is 1. + const QPen pen(Qt::black, 0, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); + const QBrush brush(Qt::black); + painter.setPen(pen); + painter.setBrush(brush); + + const int rowSize = d_ptr->m_qrcode->width; + unsigned char* dot = d_ptr->m_qrcode->data; + for (int y = 0; y < rowSize; ++y) { + for (int x = 0; x < rowSize; ++x) { + if (quint8(0x01) == (static_cast(*dot++) & quint8(0x01))) { + painter.drawRect(margin + x, margin + y, 1, 1); + } + } + } + + painter.end(); +} + +QPixmap QrCode::toPixmap(const int margin) const +{ + if (margin < 0 || d_ptr->m_qrcode == nullptr) { + return QPixmap(); + } + + const int width = d_ptr->m_qrcode->width + margin * 2; + + QPixmap pixmap(width, width); + pixmap.fill(Qt::white); + + QPainter painter; + painter.begin(&pixmap); + + // Foreground + // "Dots" are stored in a quint8 x quint8 array using row-major order. + // A dot is black if the LSB of its corresponding quint8 is 1. + const QPen pen(Qt::black, 1); + const QBrush brush(Qt::black); + painter.setPen(pen); + painter.setBrush(brush); + + const int rowSize = d_ptr->m_qrcode->width; + unsigned char* dot = d_ptr->m_qrcode->data; + for (int y = 0; y < rowSize; ++y) { + for (int x = 0; x < rowSize; ++x) { + if (quint8(0x01) == (static_cast(*dot++) & quint8(0x01))) { + painter.drawPoint(margin +x, margin + y); + } + } + } + painter.end(); + + return pixmap; +} + +int QrCode::width() { + if (!isValid()) { + return 0; + } + + return d_ptr->m_qrcode->width; +} + +unsigned char* QrCode::data() { + if (!isValid()) { + return nullptr; + } + + return d_ptr->m_qrcode->data; +} diff --git a/src/otsur/qrcode/QrCode.h b/src/otsur/qrcode/QrCode.h new file mode 100644 index 0000000000..d1e5a35a8a --- /dev/null +++ b/src/otsur/qrcode/QrCode.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Portions copyright (c) 2020, The Monero Project + */ + +#ifndef QRCODE_H +#define QRCODE_H + +#include +#include +#include + +class QImage; +class QIODevice; +class QString; +class QByteArray; + +struct QrCodePrivate; + +class QrCode +{ + +public: + enum class ErrorCorrectionLevel : int + { + LOW = 0, + MEDIUM, + QUARTILE, + HIGH + }; + + // See: http://www.qrcode.com/en/about/version.html + // clang-format off + enum class Version : int + { + AUTO = 0, + V1, V2, V3, V4, V5, V6, V7, V8, V9, V10, V11, V12, V13, V14, V15, V16, V17, V18, V19, V20, + V21, V22, V23, V24, V25, V26, V27, V28, V29, V30, V31, V32, V33, V34, V35, V36, V37, V38, V39, V40 + }; + // clang-format on + + // Uses QRcode_encodeString (can't contain NUL characters) + explicit QrCode(const QString& data, + const Version version = Version::AUTO, + const ErrorCorrectionLevel ecl = ErrorCorrectionLevel::HIGH, + const bool caseSensitive = true); + + // Uses QRcode_encodeData (can contain NUL characters) + explicit QrCode(const QByteArray& data, + const Version version = Version::AUTO, + const ErrorCorrectionLevel ecl = ErrorCorrectionLevel::HIGH); + + QrCode(); + ~QrCode(); + + bool isValid() const; + void writeSvg(QIODevice* outputDevice, const int dpi, const int margin = 4) const; + QPixmap toPixmap(const int margin = 4) const; + + int width(); + unsigned char* data(); + +private: + void init(const QString& data, const Version version, const ErrorCorrectionLevel ecl, const bool caseSensitive); + + void init(const QByteArray& data, const Version version, const ErrorCorrectionLevel ecl); + + QScopedPointer d_ptr; +}; + +#endif // QRCODE_H diff --git a/src/otsur/qrcode/QrCode_p.h b/src/otsur/qrcode/QrCode_p.h new file mode 100644 index 0000000000..a412970619 --- /dev/null +++ b/src/otsur/qrcode/QrCode_p.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/* This class exists to isolate from the rest of the code base. */ + +#ifndef QRCODEPRIVATE_H +#define QRCODEPRIVATE_H + +#include "../qrencode/qrencode.h" + +struct QrCodePrivate +{ + QRcode* m_qrcode; + + QrCodePrivate(); + ~QrCodePrivate(); +}; + +#endif // QRCODEPRIVATE_H diff --git a/src/otsur/qrencode/CMakeLists.txt b/src/otsur/qrencode/CMakeLists.txt new file mode 100644 index 0000000000..6182618cdc --- /dev/null +++ b/src/otsur/qrencode/CMakeLists.txt @@ -0,0 +1,29 @@ +set(QRENCODE_SOURCES + qrencode.c + qrinput.c + qrspec.c + rsecc.c + mask.c + split.c + bitstream.c + mmask.c + mqrspec.c +) + +set(QRENCODE_HEADERS + qrencode_inner.h + qrencode.h + qrinput.h + qrspec.h + rsecc.h + mask.h + split.h + bitstream.h + mmask.h + mqrspec.h +) + +add_library(qrencode STATIC ${QRENCODE_SOURCES} ${QRENCODE_HEADERS}) +target_compile_definitions(qrencode PRIVATE HAVE_CONFIG_H STATIC_IN_RELEASE=static) + +set_target_properties(qrencode PROPERTIES C_STANDARD 99 C_STANDARD_REQUIRED ON) diff --git a/src/otsur/qrencode/bitstream.c b/src/otsur/qrencode/bitstream.c new file mode 100644 index 0000000000..f620050dc6 --- /dev/null +++ b/src/otsur/qrencode/bitstream.c @@ -0,0 +1,231 @@ +/* + * qrencode - QR Code encoder + * + * Binary sequence class. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include + +#include "bitstream.h" + +#define DEFAULT_BUFSIZE (128) + +BitStream *BitStream_new(void) +{ + BitStream *bstream; + + bstream = (BitStream *)malloc(sizeof(BitStream)); + if(bstream == NULL) return NULL; + + bstream->length = 0; + bstream->data = (unsigned char *)malloc(DEFAULT_BUFSIZE); + if(bstream->data == NULL) { + free(bstream); + return NULL; + } + bstream->datasize = DEFAULT_BUFSIZE; + + return bstream; +} + +#ifdef WITH_TESTS +BitStream *BitStream_newWithBits(size_t size, unsigned char *bits) +{ + BitStream *bstream; + + if(size == 0) return BitStream_new(); + + bstream = (BitStream *)malloc(sizeof(BitStream)); + if(bstream == NULL) return NULL; + + bstream->data = (unsigned char *)malloc(size); + if(bstream->data == NULL) { + free(bstream); + return NULL; + } + + bstream->length = size; + bstream->datasize = size; + memcpy(bstream->data, bits, size); + + return bstream; +} +#endif + +static int BitStream_expand(BitStream *bstream) +{ + unsigned char *data; + + data = (unsigned char *)realloc(bstream->data, bstream->datasize * 2); + if(data == NULL) { + return -1; + } + + bstream->data = data; + bstream->datasize *= 2; + + return 0; +} + +static void BitStream_writeNum(unsigned char *dest, size_t bits, unsigned int num) +{ + unsigned int mask; + size_t i; + unsigned char *p; + + p = dest; + mask = 1U << (bits - 1); + for(i = 0; i < bits; i++) { + if(num & mask) { + *p = 1; + } else { + *p = 0; + } + p++; + mask = mask >> 1; + } +} + +static void BitStream_writeBytes(unsigned char *dest, size_t size, unsigned char *data) +{ + unsigned char mask; + size_t i, j; + unsigned char *p; + + p = dest; + for(i = 0; i < size; i++) { + mask = 0x80; + for(j = 0; j < 8; j++) { + if(data[i] & mask) { + *p = 1; + } else { + *p = 0; + } + p++; + mask = mask >> 1; + } + } +} + +int BitStream_append(BitStream *bstream, BitStream *arg) +{ + int ret; + + if(arg == NULL) { + return -1; + } + if(arg->length == 0) { + return 0; + } + + while(bstream->length + arg->length > bstream->datasize) { + ret = BitStream_expand(bstream); + if(ret < 0) return ret; + } + + memcpy(bstream->data + bstream->length, arg->data, arg->length); + bstream->length += arg->length; + + return 0; +} + +int BitStream_appendNum(BitStream *bstream, size_t bits, unsigned int num) +{ + int ret; + + if(bits == 0) return 0; + + while(bstream->datasize - bstream->length < bits) { + ret = BitStream_expand(bstream); + if(ret < 0) return ret; + } + BitStream_writeNum(bstream->data + bstream->length, bits, num); + bstream->length += bits; + + return 0; +} + +int BitStream_appendBytes(BitStream *bstream, size_t size, unsigned char *data) +{ + int ret; + + if(size == 0) return 0; + + while(bstream->datasize - bstream->length < size * 8) { + ret = BitStream_expand(bstream); + if(ret < 0) return ret; + } + BitStream_writeBytes(bstream->data + bstream->length, size, data); + bstream->length += size * 8; + + return 0; +} + +unsigned char *BitStream_toByte(BitStream *bstream) +{ + size_t i, j, size, bytes, oddbits; + unsigned char *data, v; + unsigned char *p; + + size = BitStream_size(bstream); + if(size == 0) { + return NULL; + } + data = (unsigned char *)malloc((size + 7) / 8); + if(data == NULL) { + return NULL; + } + + bytes = size / 8; + + p = bstream->data; + for(i = 0; i < bytes; i++) { + v = 0; + for(j = 0; j < 8; j++) { + v = (unsigned char)(v << 1); + v |= *p; + p++; + } + data[i] = v; + } + oddbits = size & 7; + if(oddbits > 0) { + v = 0; + for(j = 0; j < oddbits; j++) { + v = (unsigned char)(v << 1); + v |= *p; + p++; + } + data[bytes] = (unsigned char)(v << (8 - oddbits)); + } + + return data; +} + +void BitStream_free(BitStream *bstream) +{ + if(bstream != NULL) { + free(bstream->data); + free(bstream); + } +} diff --git a/src/otsur/qrencode/bitstream.h b/src/otsur/qrencode/bitstream.h new file mode 100644 index 0000000000..70f3e1dfc5 --- /dev/null +++ b/src/otsur/qrencode/bitstream.h @@ -0,0 +1,43 @@ +/* + * qrencode - QR Code encoder + * + * Binary sequence class. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BITSTREAM_H +#define BITSTREAM_H + +typedef struct { + size_t length; + size_t datasize; + unsigned char *data; +} BitStream; + +extern BitStream *BitStream_new(void); +#ifdef WITH_TESTS +extern BitStream *BitStream_newWithBits(size_t size, unsigned char *bits); +#endif +extern int BitStream_append(BitStream *bstream, BitStream *arg); +extern int BitStream_appendNum(BitStream *bstream, size_t bits, unsigned int num); +extern int BitStream_appendBytes(BitStream *bstream, size_t size, unsigned char *data); +#define BitStream_size(__bstream__) (__bstream__->length) +#define BitStream_reset(__bstream__) (__bstream__->length = 0) +extern unsigned char *BitStream_toByte(BitStream *bstream); +extern void BitStream_free(BitStream *bstream); + +#endif /* BITSTREAM_H */ diff --git a/src/otsur/qrencode/config.h b/src/otsur/qrencode/config.h new file mode 100644 index 0000000000..e429a9464d --- /dev/null +++ b/src/otsur/qrencode/config.h @@ -0,0 +1,43 @@ +#ifndef __CONFIG_H__ +#define __CONFIG_H__ + +/* Standard includes */ +#define HAVE_STDLIB_H +#define HAVE_STRING_H +#define HAVE_STRINGS_H +#define HAVE_STDINT_H +#define HAVE_ERRNO_H + +/* Version information */ +#define MAJOR_VERSION 4 +#define MINOR_VERSION 1 +#define MICRO_VERSION 1 +#define VERSION "4.1.1" + +/* Function availability */ +#define HAVE_STRDUP 1 + +/* Disable features we don't need */ +#undef HAVE_LIBPTHREAD +#undef HAVE_PTHREAD_MUTEX_RECURSIVE +#undef HAVE_LIBZ +#undef USE_FAST_COPY +#undef WITH_TESTS + +/* Disable Micro QR Code support */ +#undef HAVE_MQRSPEC_H + +/* Define static functions for release builds */ +#define STATIC_IN_RELEASE static + +/* Other configurations */ +#define QRSPEC_VERSION_MAX 40 +#define QRSPEC_WIDTH_MAX 177 + +/* System-dependent definitions */ +#if defined(_WIN32) || defined(_WIN64) +#define HAVE_WINDOWS_H +#define HAVE_STRINGIZE +#endif + +#endif /* __CONFIG_H__ */ diff --git a/src/otsur/qrencode/mask.c b/src/otsur/qrencode/mask.c new file mode 100644 index 0000000000..4bf2371765 --- /dev/null +++ b/src/otsur/qrencode/mask.c @@ -0,0 +1,357 @@ +/* + * qrencode - QR Code encoder + * + * Masking. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include + +#include "qrencode.h" +#include "qrspec.h" +#include "mask.h" + +STATIC_IN_RELEASE int Mask_writeFormatInformation(int width, unsigned char *frame, int mask, QRecLevel level) +{ + unsigned int format; + unsigned char v; + int i; + int blacks = 0; + + format = QRspec_getFormatInfo(mask, level); + + for(i = 0; i < 8; i++) { + if(format & 1) { + blacks += 2; + v = 0x85; + } else { + v = 0x84; + } + frame[width * 8 + width - 1 - i] = v; + if(i < 6) { + frame[width * i + 8] = v; + } else { + frame[width * (i + 1) + 8] = v; + } + format= format >> 1; + } + for(i = 0; i < 7; i++) { + if(format & 1) { + blacks += 2; + v = 0x85; + } else { + v = 0x84; + } + frame[width * (width - 7 + i) + 8] = v; + if(i == 0) { + frame[width * 8 + 7] = v; + } else { + frame[width * 8 + 6 - i] = v; + } + format= format >> 1; + } + + return blacks; +} + +/** + * Demerit coefficients. + * See Section 8.8.2, pp.45, JIS X0510:2004. + */ +#define N1 (3) +#define N2 (3) +#define N3 (40) +#define N4 (10) + +#define MASKMAKER(__exp__) \ + int x, y;\ + int b = 0;\ +\ + for(y = 0; y < width; y++) {\ + for(x = 0; x < width; x++) {\ + if(*s & 0x80) {\ + *d = *s;\ + } else {\ + *d = *s ^ ((__exp__) == 0);\ + }\ + b += (int)(*d & 1);\ + s++; d++;\ + }\ + }\ + return b; + +static int Mask_mask0(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((x+y)&1) +} + +static int Mask_mask1(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(y&1) +} + +static int Mask_mask2(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(x%3) +} + +static int Mask_mask3(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((x+y)%3) +} + +static int Mask_mask4(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(((y/2)+(x/3))&1) +} + +static int Mask_mask5(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(((x*y)&1)+(x*y)%3) +} + +static int Mask_mask6(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((((x*y)&1)+(x*y)%3)&1) +} + +static int Mask_mask7(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((((x*y)%3)+((x+y)&1))&1) +} + +#define maskNum (8) +typedef int MaskMaker(int, const unsigned char *, unsigned char *); +static MaskMaker *maskMakers[maskNum] = { + Mask_mask0, Mask_mask1, Mask_mask2, Mask_mask3, + Mask_mask4, Mask_mask5, Mask_mask6, Mask_mask7 +}; + +#ifdef WITH_TESTS +unsigned char *Mask_makeMaskedFrame(int width, unsigned char *frame, int mask) +{ + unsigned char *masked; + + masked = (unsigned char *)malloc((size_t)(width * width)); + if(masked == NULL) return NULL; + + maskMakers[mask](width, frame, masked); + + return masked; +} +#endif + +unsigned char *Mask_makeMask(int width, unsigned char *frame, int mask, QRecLevel level) +{ + unsigned char *masked; + + if(mask < 0 || mask >= maskNum) { + errno = EINVAL; + return NULL; + } + + masked = (unsigned char *)malloc((size_t)(width * width)); + if(masked == NULL) return NULL; + + maskMakers[mask](width, frame, masked); + Mask_writeFormatInformation(width, masked, mask, level); + + return masked; +} + + +//static int n1; +//static int n2; +//static int n3; +//static int n4; + +STATIC_IN_RELEASE int Mask_calcN1N3(int length, int *runLength) +{ + int i; + int demerit = 0; + int fact; + + for(i = 0; i < length; i++) { + if(runLength[i] >= 5) { + demerit += N1 + (runLength[i] - 5); + //n1 += N1 + (runLength[i] - 5); + } + if((i & 1)) { + if(i >= 3 && i < length-2 && (runLength[i] % 3) == 0) { + fact = runLength[i] / 3; + if(runLength[i-2] == fact && + runLength[i-1] == fact && + runLength[i+1] == fact && + runLength[i+2] == fact) { + if(i == 3 || runLength[i-3] >= 4 * fact) { + demerit += N3; + //n3 += N3; + } else if(i+4 >= length || runLength[i+3] >= 4 * fact) { + demerit += N3; + //n3 += N3; + } + } + } + } + } + + return demerit; +} + +STATIC_IN_RELEASE int Mask_calcN2(int width, unsigned char *frame) +{ + int x, y; + unsigned char *p; + unsigned char b22, w22; + int demerit = 0; + + p = frame + width + 1; + for(y = 1; y < width; y++) { + for(x = 1; x < width; x++) { + b22 = p[0] & p[-1] & p[-width] & p [-width-1]; + w22 = p[0] | p[-1] | p[-width] | p [-width-1]; + if((b22 | (w22 ^ 1))&1) { + demerit += N2; + } + p++; + } + p++; + } + + return demerit; +} + +STATIC_IN_RELEASE int Mask_calcRunLengthH(int width, unsigned char *frame, int *runLength) +{ + int head; + int i; + unsigned char prev; + + if(frame[0] & 1) { + runLength[0] = -1; + head = 1; + } else { + head = 0; + } + runLength[head] = 1; + prev = frame[0]; + + for(i = 1; i < width; i++) { + if((frame[i] ^ prev) & 1) { + head++; + runLength[head] = 1; + prev = frame[i]; + } else { + runLength[head]++; + } + } + + return head + 1; +} + +STATIC_IN_RELEASE int Mask_calcRunLengthV(int width, unsigned char *frame, int *runLength) +{ + int head; + int i; + unsigned char prev; + + if(frame[0] & 1) { + runLength[0] = -1; + head = 1; + } else { + head = 0; + } + runLength[head] = 1; + prev = frame[0]; + + for(i = 1; i < width; i++) { + if((frame[i * width] ^ prev) & 1) { + head++; + runLength[head] = 1; + prev = frame[i * width]; + } else { + runLength[head]++; + } + } + + return head + 1; +} + +STATIC_IN_RELEASE int Mask_evaluateSymbol(int width, unsigned char *frame) +{ + int x, y; + int demerit = 0; + int runLength[QRSPEC_WIDTH_MAX + 1]; + int length; + + demerit += Mask_calcN2(width, frame); + + for(y = 0; y < width; y++) { + length = Mask_calcRunLengthH(width, frame + y * width, runLength); + demerit += Mask_calcN1N3(length, runLength); + } + + for(x = 0; x < width; x++) { + length = Mask_calcRunLengthV(width, frame + x, runLength); + demerit += Mask_calcN1N3(length, runLength); + } + + return demerit; +} + +unsigned char *Mask_mask(int width, unsigned char *frame, QRecLevel level) +{ + int i; + unsigned char *mask, *bestMask; + int minDemerit = INT_MAX; + int blacks; + int bratio; + int demerit; + int w2 = width * width; + + mask = (unsigned char *)malloc((size_t)w2); + if(mask == NULL) return NULL; + bestMask = (unsigned char *)malloc((size_t)w2); + if(bestMask == NULL) { + free(mask); + return NULL; + } + + for(i = 0; i < maskNum; i++) { +// n1 = n2 = n3 = n4 = 0; + demerit = 0; + blacks = maskMakers[i](width, frame, mask); + blacks += Mask_writeFormatInformation(width, mask, i, level); + bratio = (200 * blacks + w2) / w2 / 2; /* (int)(100*blacks/w2+0.5) */ + demerit = (abs(bratio - 50) / 5) * N4; +// n4 = demerit; + demerit += Mask_evaluateSymbol(width, mask); +// printf("(%d,%d,%d,%d)=%d\n", n1, n2, n3 ,n4, demerit); + if(demerit < minDemerit) { + minDemerit = demerit; + memcpy(bestMask, mask, (size_t)w2); + } + } + free(mask); + return bestMask; +} diff --git a/src/otsur/qrencode/mask.h b/src/otsur/qrencode/mask.h new file mode 100644 index 0000000000..169e64b2bb --- /dev/null +++ b/src/otsur/qrencode/mask.h @@ -0,0 +1,38 @@ +/* + * qrencode - QR Code encoder + * + * Masking. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MASK_H +#define MASK_H + +extern unsigned char *Mask_makeMask(int width, unsigned char *frame, int mask, QRecLevel level); +extern unsigned char *Mask_mask(int width, unsigned char *frame, QRecLevel level); + +#ifdef WITH_TESTS +extern int Mask_calcN2(int width, unsigned char *frame); +extern int Mask_calcN1N3(int length, int *runLength); +extern int Mask_calcRunLengthH(int width, unsigned char *frame, int *runLength); +extern int Mask_calcRunLengthV(int width, unsigned char *frame, int *runLength); +extern int Mask_evaluateSymbol(int width, unsigned char *frame); +extern int Mask_writeFormatInformation(int width, unsigned char *frame, int mask, QRecLevel level); +extern unsigned char *Mask_makeMaskedFrame(int width, unsigned char *frame, int mask); +#endif + +#endif /* MASK_H */ diff --git a/src/otsur/qrencode/mmask.c b/src/otsur/qrencode/mmask.c new file mode 100644 index 0000000000..5f09a1e981 --- /dev/null +++ b/src/otsur/qrencode/mmask.c @@ -0,0 +1,177 @@ +/* + * qrencode - QR Code encoder + * + * Masking for Micro QR Code. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include + +#include "qrencode.h" +#include "mqrspec.h" +#include "mmask.h" + +STATIC_IN_RELEASE void MMask_writeFormatInformation(int version, int width, unsigned char *frame, int mask, QRecLevel level) +{ + unsigned int format; + unsigned char v; + int i; + + format = MQRspec_getFormatInfo(mask, version, level); + + for(i = 0; i < 8; i++) { + v = 0x84 | (format & 1); + frame[width * (i + 1) + 8] = v; + format = format >> 1; + } + for(i = 0; i < 7; i++) { + v = 0x84 | (format & 1); + frame[width * 8 + 7 - i] = v; + format = format >> 1; + } +} + +#define MASKMAKER(__exp__) \ + int x, y;\ +\ + for(y = 0; y < width; y++) {\ + for(x = 0; x < width; x++) {\ + if(*s & 0x80) {\ + *d = *s;\ + } else {\ + *d = *s ^ ((__exp__) == 0);\ + }\ + s++; d++;\ + }\ + } + +static void Mask_mask0(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(y&1) +} + +static void Mask_mask1(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER(((y/2)+(x/3))&1) +} + +static void Mask_mask2(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((((x*y)&1)+(x*y)%3)&1) +} + +static void Mask_mask3(int width, const unsigned char *s, unsigned char *d) +{ + MASKMAKER((((x+y)&1)+((x*y)%3))&1) +} + +#define maskNum (4) +typedef void MaskMaker(int, const unsigned char *, unsigned char *); +static MaskMaker *maskMakers[maskNum] = { + Mask_mask0, Mask_mask1, Mask_mask2, Mask_mask3 +}; + +#ifdef WITH_TESTS +unsigned char *MMask_makeMaskedFrame(int width, unsigned char *frame, int mask) +{ + unsigned char *masked; + + masked = (unsigned char *)malloc((size_t)(width * width)); + if(masked == NULL) return NULL; + + maskMakers[mask](width, frame, masked); + + return masked; +} +#endif + +unsigned char *MMask_makeMask(int version, unsigned char *frame, int mask, QRecLevel level) +{ + unsigned char *masked; + int width; + + if(mask < 0 || mask >= maskNum) { + errno = EINVAL; + return NULL; + } + + width = MQRspec_getWidth(version); + masked = (unsigned char *)malloc((size_t)(width * width)); + if(masked == NULL) return NULL; + + maskMakers[mask](width, frame, masked); + MMask_writeFormatInformation(version, width, masked, mask, level); + + return masked; +} + +STATIC_IN_RELEASE int MMask_evaluateSymbol(int width, unsigned char *frame) +{ + int x, y; + unsigned char *p; + int sum1 = 0, sum2 = 0; + + p = frame + width * (width - 1); + for(x = 1; x < width; x++) { + sum1 += (p[x] & 1); + } + + p = frame + width * 2 - 1; + for(y = 1; y < width; y++) { + sum2 += (*p & 1); + p += width; + } + + return (sum1 <= sum2)?(sum1 * 16 + sum2):(sum2 * 16 + sum1); +} + +unsigned char *MMask_mask(int version, unsigned char *frame, QRecLevel level) +{ + int i; + unsigned char *mask, *bestMask; + int maxScore = 0; + int score; + int width; + + width = MQRspec_getWidth(version); + + mask = (unsigned char *)malloc((size_t)(width * width)); + if(mask == NULL) return NULL; + bestMask = NULL; + + for(i = 0; i < maskNum; i++) { + score = 0; + maskMakers[i](width, frame, mask); + MMask_writeFormatInformation(version, width, mask, i, level); + score = MMask_evaluateSymbol(width, mask); + if(score > maxScore) { + maxScore = score; + free(bestMask); + bestMask = mask; + mask = (unsigned char *)malloc((size_t)(width * width)); + if(mask == NULL) break; + } + } + free(mask); + return bestMask; +} diff --git a/src/otsur/qrencode/mmask.h b/src/otsur/qrencode/mmask.h new file mode 100644 index 0000000000..56a58cd2d7 --- /dev/null +++ b/src/otsur/qrencode/mmask.h @@ -0,0 +1,34 @@ +/* + * qrencode - QR Code encoder + * + * Masking for Micro QR Code. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MMASK_H +#define MMASK_H + +extern unsigned char *MMask_makeMask(int version, unsigned char *frame, int mask, QRecLevel level); +extern unsigned char *MMask_mask(int version, unsigned char *frame, QRecLevel level); + +#ifdef WITH_TESTS +extern int MMask_evaluateSymbol(int width, unsigned char *frame); +extern void MMask_writeFormatInformation(int version, int width, unsigned char *frame, int mask, QRecLevel level); +extern unsigned char *MMask_makeMaskedFrame(int width, unsigned char *frame, int mask); +#endif + +#endif /* MMASK_H */ diff --git a/src/otsur/qrencode/mqrspec.c b/src/otsur/qrencode/mqrspec.c new file mode 100644 index 0000000000..87205fe970 --- /dev/null +++ b/src/otsur/qrencode/mqrspec.c @@ -0,0 +1,232 @@ +/* + * qrencode - QR Code encoder + * + * Micro QR Code specification in convenient format. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * The following data / specifications are taken from + * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) + * or + * "Automatic identification and data capture techniques -- + * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include + +#include "mqrspec.h" + +/****************************************************************************** + * Version and capacity + *****************************************************************************/ + +typedef struct { + int width; ///< Edge length of the symbol + int ec[4]; ///< Number of ECC code (bytes) +} MQRspec_Capacity; + +/** + * Table of the capacity of symbols + * See Table 1 (pp.106) and Table 8 (pp.113) of Appendix 1, JIS X0510:2004. + */ +static const MQRspec_Capacity mqrspecCapacity[MQRSPEC_VERSION_MAX + 1] = { + { 0, {0, 0, 0, 0}}, + { 11, {2, 0, 0, 0}}, + { 13, {5, 6, 0, 0}}, + { 15, {6, 8, 0, 0}}, + { 17, {8, 10, 14, 0}} +}; + +int MQRspec_getDataLengthBit(int version, QRecLevel level) +{ + int w; + int ecc; + + w = mqrspecCapacity[version].width - 1; + ecc = mqrspecCapacity[version].ec[level]; + if(ecc == 0) return 0; + return w * w - 64 - ecc * 8; +} + +int MQRspec_getDataLength(int version, QRecLevel level) +{ + return (MQRspec_getDataLengthBit(version, level) + 4) / 8; +} + +int MQRspec_getECCLength(int version, QRecLevel level) +{ + return mqrspecCapacity[version].ec[level]; +} + +int MQRspec_getWidth(int version) +{ + return mqrspecCapacity[version].width; +} + +/****************************************************************************** + * Length indicator + *****************************************************************************/ + +/** + * See Table 3 (pp.107) of Appendix 1, JIS X0510:2004. + */ +static const int lengthTableBits[4][4] = { + { 3, 4, 5, 6}, + { 0, 3, 4, 5}, + { 0, 0, 4, 5}, + { 0, 0, 3, 4} +}; + +int MQRspec_lengthIndicator(QRencodeMode mode, int version) +{ + return lengthTableBits[mode][version - 1]; +} + +int MQRspec_maximumWords(QRencodeMode mode, int version) +{ + int bits; + int words; + + bits = lengthTableBits[mode][version - 1]; + words = (1 << bits) - 1; + if(mode == QR_MODE_KANJI) { + words *= 2; // the number of bytes is required + } + + return words; +} + +/****************************************************************************** + * Format information + *****************************************************************************/ + +/* See calcFormatInfo in tests/test_mqrspec.c */ +static const unsigned int formatInfo[4][8] = { + {0x4445, 0x55ae, 0x6793, 0x7678, 0x06de, 0x1735, 0x2508, 0x34e3}, + {0x4172, 0x5099, 0x62a4, 0x734f, 0x03e9, 0x1202, 0x203f, 0x31d4}, + {0x4e2b, 0x5fc0, 0x6dfd, 0x7c16, 0x0cb0, 0x1d5b, 0x2f66, 0x3e8d}, + {0x4b1c, 0x5af7, 0x68ca, 0x7921, 0x0987, 0x186c, 0x2a51, 0x3bba} +}; + +/* See Table 10 of Appendix 1. (pp.115) */ +static const int typeTable[MQRSPEC_VERSION_MAX + 1][3] = { + {-1, -1, -1}, + { 0, -1, -1}, + { 1, 2, -1}, + { 3, 4, -1}, + { 5, 6, 7} +}; + +unsigned int MQRspec_getFormatInfo(int mask, int version, QRecLevel level) +{ + int type; + + if(mask < 0 || mask > 3) return 0; + if(version <= 0 || version > MQRSPEC_VERSION_MAX) return 0; + if(level == QR_ECLEVEL_H) return 0; + type = typeTable[version][level]; + if(type < 0) return 0; + + return formatInfo[mask][type]; +} + +/****************************************************************************** + * Frame + *****************************************************************************/ + +/** + * Put a finder pattern. + * @param frame + * @param width + * @param ox,oy upper-left coordinate of the pattern + */ +static void putFinderPattern(unsigned char *frame, int width, int ox, int oy) +{ + static const unsigned char finder[] = { + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc1, 0xc1, 0xc1, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc1, 0xc1, 0xc1, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc1, 0xc1, 0xc1, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + }; + int x, y; + const unsigned char *s; + + frame += oy * width + ox; + s = finder; + for(y = 0; y < 7; y++) { + for(x = 0; x < 7; x++) { + frame[x] = s[x]; + } + frame += width; + s += 7; + } +} + +static unsigned char *MQRspec_createFrame(int version) +{ + unsigned char *frame, *p, *q; + int width; + int x, y; + + width = mqrspecCapacity[version].width; + frame = (unsigned char *)malloc((size_t)(width * width)); + if(frame == NULL) return NULL; + + memset(frame, 0, (size_t)(width * width)); + /* Finder pattern */ + putFinderPattern(frame, width, 0, 0); + /* Separator */ + p = frame; + for(y = 0; y < 7; y++) { + p[7] = 0xc0; + p += width; + } + memset(frame + width * 7, 0xc0, 8); + /* Mask format information area */ + memset(frame + width * 8 + 1, 0x84, 8); + p = frame + width + 8; + for(y = 0; y < 7; y++) { + *p = 0x84; + p += width; + } + /* Timing pattern */ + p = frame + 8; + q = frame + width * 8; + for(x = 1; x < width-7; x++) { + *p = 0x90 | (x & 1); + *q = 0x90 | (x & 1); + p++; + q += width; + } + + return frame; +} + +unsigned char *MQRspec_newFrame(int version) +{ + if(version < 1 || version > MQRSPEC_VERSION_MAX) return NULL; + + return MQRspec_createFrame(version); +} diff --git a/src/otsur/qrencode/mqrspec.h b/src/otsur/qrencode/mqrspec.h new file mode 100644 index 0000000000..0eaa4907f5 --- /dev/null +++ b/src/otsur/qrencode/mqrspec.h @@ -0,0 +1,150 @@ +/* + * qrencode - QR Code encoder + * + * Micro QR Code specification in convenient format. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MQRSPEC_H +#define MQRSPEC_H + +#include "qrencode.h" + +/****************************************************************************** + * Version and capacity + *****************************************************************************/ + +/** + * Maximum width of a symbol + */ +#define MQRSPEC_WIDTH_MAX 17 + +/** + * Return maximum data code length (bits) for the version. + * @param version version of the symbol + * @param level error correction level + * @return maximum size (bits) + */ +extern int MQRspec_getDataLengthBit(int version, QRecLevel level); + +/** + * Return maximum data code length (bytes) for the version. + * @param version version of the symbol + * @param level error correction level + * @return maximum size (bytes) + */ +extern int MQRspec_getDataLength(int version, QRecLevel level); + +/** + * Return maximum error correction code length (bytes) for the version. + * @param version version of the symbol + * @param level error correction level + * @return ECC size (bytes) + */ +extern int MQRspec_getECCLength(int version, QRecLevel level); + +/** + * Return a version number that satisfies the input code length. + * @param size input code length (byte) + * @param level error correction level + * @return version number + */ +extern int MQRspec_getMinimumVersion(int size, QRecLevel level); + +/** + * Return the width of the symbol for the version. + * @param version version of the symbol + * @return width + */ +extern int MQRspec_getWidth(int version); + +/** + * Return the numer of remainder bits. + * @param version version of the symbol + * @return number of remainder bits + */ +extern int MQRspec_getRemainder(int version); + +/****************************************************************************** + * Length indicator + *****************************************************************************/ + +/** + * Return the size of length indicator for the mode and version. + * @param mode encode mode + * @param version vesion of the symbol + * @return the size of the appropriate length indicator (bits). + */ +extern int MQRspec_lengthIndicator(QRencodeMode mode, int version); + +/** + * Return the maximum length for the mode and version. + * @param mode encode mode + * @param version vesion of the symbol + * @return the maximum length (bytes) + */ +extern int MQRspec_maximumWords(QRencodeMode mode, int version); + +/****************************************************************************** + * Version information pattern + *****************************************************************************/ + +/** + * Return BCH encoded version information pattern that is used for the symbol + * of version 7 or greater. Use lower 18 bits. + * @param version vesion of the symbol + * @return BCH encoded version information pattern + */ +extern unsigned int MQRspec_getVersionPattern(int version); + +/****************************************************************************** + * Format information + *****************************************************************************/ + +/** + * Return BCH encoded format information pattern. + * @param mask mask number + * @param version version of the symbol + * @param level error correction level + * @return BCH encoded format information pattern + */ +extern unsigned int MQRspec_getFormatInfo(int mask, int version, QRecLevel level); + +/****************************************************************************** + * Frame + *****************************************************************************/ + +/** + * Return a copy of initialized frame. + * @param version version of the symbol + * @return Array of unsigned char. You can free it by free(). + */ +extern unsigned char *MQRspec_newFrame(int version); + +/****************************************************************************** + * Mode indicator + *****************************************************************************/ + +/** + * Mode indicator. See Table 2 in Appendix 1 of JIS X0510:2004, pp.107. + */ +#define MQRSPEC_MODEID_NUM 0 +#define MQRSPEC_MODEID_AN 1 +#define MQRSPEC_MODEID_8 2 +#define MQRSPEC_MODEID_KANJI 3 + +#endif /* MQRSPEC_H */ diff --git a/src/otsur/qrencode/qrenc.c b/src/otsur/qrencode/qrenc.c new file mode 100644 index 0000000000..c09c4ab693 --- /dev/null +++ b/src/otsur/qrencode/qrenc.c @@ -0,0 +1,1453 @@ +/** + * qrencode - QR Code encoder + * + * QR Code encoding tool + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include +#include +#if HAVE_PNG +#include +#endif + +#include "qrencode.h" + +#define INCHES_PER_METER (100.0/2.54) + +static int casesensitive = 1; +static int eightbit = 0; +static int version = 0; +static int size = 3; +static int margin = -1; +static int dpi = 72; +static int structured = 0; +static int rle = 0; +static int svg_path = 0; +static int micro = 0; +static int inline_svg = 0; +static int strict_versioning = 0; +static QRecLevel level = QR_ECLEVEL_L; +static QRencodeMode hint = QR_MODE_8; +static unsigned char fg_color[4] = {0, 0, 0, 255}; +static unsigned char bg_color[4] = {255, 255, 255, 255}; + +static int verbose = 0; + +enum imageType { + PNG_TYPE, + PNG32_TYPE, + EPS_TYPE, + SVG_TYPE, + XPM_TYPE, + ANSI_TYPE, + ANSI256_TYPE, + ASCII_TYPE, + ASCIIi_TYPE, + UTF8_TYPE, + ANSIUTF8_TYPE, + ANSI256UTF8_TYPE, + UTF8i_TYPE, + ANSIUTF8i_TYPE +}; + +static enum imageType image_type = PNG_TYPE; + +static const struct option options[] = { + {"help" , no_argument , NULL, 'h'}, + {"output" , required_argument, NULL, 'o'}, + {"read-from" , required_argument, NULL, 'r'}, + {"level" , required_argument, NULL, 'l'}, + {"size" , required_argument, NULL, 's'}, + {"symversion" , required_argument, NULL, 'v'}, + {"margin" , required_argument, NULL, 'm'}, + {"dpi" , required_argument, NULL, 'd'}, + {"type" , required_argument, NULL, 't'}, + {"structured" , no_argument , NULL, 'S'}, + {"kanji" , no_argument , NULL, 'k'}, + {"casesensitive" , no_argument , NULL, 'c'}, + {"ignorecase" , no_argument , NULL, 'i'}, + {"8bit" , no_argument , NULL, '8'}, + {"micro" , no_argument , NULL, 'M'}, + {"rle" , no_argument , &rle, 1}, + {"svg-path" , no_argument , &svg_path, 1}, + {"inline" , no_argument , &inline_svg, 1}, + {"strict-version", no_argument , &strict_versioning, 1}, + {"foreground" , required_argument, NULL, 'f'}, + {"background" , required_argument, NULL, 'b'}, + {"version" , no_argument , NULL, 'V'}, + {"verbose" , no_argument , &verbose, 1}, + {NULL, 0, NULL, 0} +}; + +static char *optstring = "ho:r:l:s:v:m:d:t:Skci8MV"; + +static void usage(int help, int longopt, int status) +{ + FILE *out = status ? stderr : stdout; + fprintf(out, +"qrencode version %s\n" +"Copyright (C) 2006-2017 Kentaro Fukuchi\n", QRcode_APIVersionString()); + if(help) { + if(longopt) { + fprintf(out, +"Usage: qrencode [-o FILENAME] [OPTION]... [STRING]\n" +"Encode input data in a QR Code and save as a PNG or EPS image.\n\n" +" -h, --help display the help message. -h displays only the help of short\n" +" options.\n\n" +" -o FILENAME, --output=FILENAME\n" +" write image to FILENAME. If '-' is specified, the result\n" +" will be output to standard output. If -S is given, structured\n" +" symbols are written to FILENAME-01.png, FILENAME-02.png, ...\n" +" (suffix is removed from FILENAME, if specified)\n\n" +" -r FILENAME, --read-from=FILENAME\n" +" read input data from FILENAME.\n\n" +" -s NUMBER, --size=NUMBER\n" +" specify module size in dots (pixels). (default=3)\n\n" +" -l {LMQH}, --level={LMQH}\n" +" specify error correction level from L (lowest) to H (highest).\n" +" (default=L)\n\n" +" -v NUMBER, --symversion=NUMBER\n" +" specify the minimum version of the symbol. See SYMBOL VERSIONS\n" +" for more information. (default=auto)\n\n" +" -m NUMBER, --margin=NUMBER\n" +" specify the width of the margins. (default=4 (2 for Micro QR)))\n\n" +" -d NUMBER, --dpi=NUMBER\n" +" specify the DPI of the generated PNG. (default=72)\n\n" +" -t {PNG,PNG32,EPS,SVG,XPM,ANSI,ANSI256,ASCII,ASCIIi,UTF8,UTF8i,ANSIUTF8,ANSIUTF8i,ANSI256UTF8},\n" +" --type={PNG,PNG32,EPS,SVG,XPM,ANSI,ANSI256,ASCII,ASCIIi,UTF8,UTF8i,ANSIUTF8,ANSIUTF8i,ANSI256UTF8}\n" +" specify the type of the generated image. (default=PNG)\n\n" +" -S, --structured\n" +" make structured symbols. Version must be specified with '-v'.\n\n" +" -k, --kanji assume that the input text contains kanji (shift-jis).\n\n" +" -c, --casesensitive\n" +" encode lower-case alphabet characters in 8-bit mode. (default)\n\n" +" -i, --ignorecase\n" +" ignore case distinctions and use only upper-case characters.\n\n" +" -8, --8bit encode entire data in 8-bit mode. -k, -c and -i will be ignored.\n\n" +" -M, --micro encode in a Micro QR Code.\n\n" +" --rle enable run-length encoding for SVG.\n\n" +" --svg-path\n" +" use single path to draw modules for SVG.\n\n" +" --inline only useful for SVG output, generates an SVG without the XML tag.\n\n" +" --foreground=RRGGBB[AA]\n" +" --background=RRGGBB[AA]\n" +" specify foreground/background color in hexadecimal notation.\n" +" 6-digit (RGB) or 8-digit (RGBA) form are supported.\n" +" Color output support available only in PNG, EPS and SVG.\n\n" +" --strict-version\n" +" disable automatic version number adjustment. If the input data is\n" +" too large for the specified version, the program exits with the\n" +" code of 1.\n\n" +" -V, --version\n" +" display the version number and copyrights of the qrencode.\n\n" +" --verbose\n" +" display verbose information to stderr.\n\n" +" [STRING] input data. If it is not specified, data will be taken from\n" +" standard input.\n\n" +"SYMBOL VERSIONS\n" +" The symbol versions of QR Code range from Version 1 to Version\n" +" 40. Each version has a different module configuration or number\n" +" of modules, ranging from Version 1 (21 x 21 modules) up to\n" +" Version 40 (177 x 177 modules). Each higher version number\n" +" comprises 4 additional modules per side by default. See\n" +" http://www.qrcode.com/en/about/version.html for a detailed\n" +" version list.\n" + + ); + } else { + fprintf(out, +"Usage: qrencode [-o FILENAME] [OPTION]... [STRING]\n" +"Encode input data in a QR Code and save as a PNG or EPS image.\n\n" +" -h display this message.\n" +" --help display the usage of long options.\n" +" -o FILENAME write image to FILENAME. If '-' is specified, the result\n" +" will be output to standard output. If -S is given, structured\n" +" symbols are written to FILENAME-01.png, FILENAME-02.png, ...\n" +" (suffix is removed from FILENAME, if specified)\n" +" -r FILENAME read input data from FILENAME.\n" +" -s NUMBER specify module size in dots (pixels). (default=3)\n" +" -l {LMQH} specify error correction level from L (lowest) to H (highest).\n" +" (default=L)\n" +" -v NUMBER specify the minimum version of the symbol. (default=auto)\n" +" -m NUMBER specify the width of the margins. (default=4 (2 for Micro))\n" +" -d NUMBER specify the DPI of the generated PNG. (default=72)\n" +" -t {PNG,PNG32,EPS,SVG,XPM,ANSI,ANSI256,ASCII,ASCIIi,UTF8,UTF8i,ANSIUTF8,ANSIUTF8i,ANSI256UTF8}\n" +" specify the type of the generated image. (default=PNG)\n" +" -S make structured symbols. Version number must be specified with '-v'.\n" +" -k assume that the input text contains kanji (shift-jis).\n" +" -c encode lower-case alphabet characters in 8-bit mode. (default)\n" +" -i ignore case distinctions and use only upper-case characters.\n" +" -8 encode entire data in 8-bit mode. -k, -c and -i will be ignored.\n" +" -M encode in a Micro QR Code.\n" +" -V display the version number and copyrights of the qrencode.\n" +" [STRING] input data. If it is not specified, data will be taken from\n" +" standard input.\n\n" +" Try \"qrencode --help\" for more options.\n" + ); + } + } +} + +static int color_set(unsigned char color[4], const char *value) +{ + int len = strlen(value); + int i, count; + unsigned int col[4]; + if(len == 6) { + count = sscanf(value, "%02x%02x%02x%n", &col[0], &col[1], &col[2], &len); + if(count < 3 || len != 6) { + return -1; + } + for(i = 0; i < 3; i++) { + color[i] = col[i]; + } + color[3] = 255; + } else if(len == 8) { + count = sscanf(value, "%02x%02x%02x%02x%n", &col[0], &col[1], &col[2], &col[3], &len); + if(count < 4 || len != 8) { + return -1; + } + for(i = 0; i < 4; i++) { + color[i] = col[i]; + } + } else { + return -1; + } + return 0; +} + +#define MAX_DATA_SIZE (7090 * 2) /* timed by the safty factor 2 */ +static unsigned char data_buffer[MAX_DATA_SIZE]; +static unsigned char *readFile(FILE *fp, int *length) +{ + int ret; + + ret = fread(data_buffer, 1, MAX_DATA_SIZE, fp); + if(ret == 0) { + fprintf(stderr, "No input data.\n"); + exit(EXIT_FAILURE); + } + if(feof(fp) == 0) { + fprintf(stderr, "Input data is too large.\n"); + exit(EXIT_FAILURE); + } + + data_buffer[ret] = '\0'; + *length = ret; + + return data_buffer; +} + +static FILE *openFile(const char *outfile) +{ + FILE *fp; + + if(outfile == NULL || (outfile[0] == '-' && outfile[1] == '\0')) { + fp = stdout; + } else { + fp = fopen(outfile, "wb"); + if(fp == NULL) { + fprintf(stderr, "Failed to create file: %s\n", outfile); + perror(NULL); + exit(EXIT_FAILURE); + } + } + + return fp; +} + +#if HAVE_PNG +static void fillRow(unsigned char *row, int num, const unsigned char color[]) +{ + int i; + + for(i = 0; i < num; i++) { + memcpy(row, color, 4); + row += 4; + } +} +#endif + +static int writePNG(const QRcode *qrcode, const char *outfile, enum imageType type) +{ +#if HAVE_PNG + static FILE *fp; // avoid clobbering by setjmp. + png_structp png_ptr; + png_infop info_ptr; + png_colorp palette = NULL; + png_byte alpha_values[2]; + unsigned char *row, *p, *q; + int x, y, xx, yy, bit; + int realwidth; + + realwidth = (qrcode->width + margin * 2) * size; + if(type == PNG_TYPE) { + row = (unsigned char *)malloc((size_t)((realwidth + 7) / 8)); + } else if(type == PNG32_TYPE) { + row = (unsigned char *)malloc((size_t)realwidth * 4); + } else { + fprintf(stderr, "Internal error.\n"); + exit(EXIT_FAILURE); + } + if(row == NULL) { + fprintf(stderr, "Failed to allocate memory.\n"); + exit(EXIT_FAILURE); + } + + if(outfile[0] == '-' && outfile[1] == '\0') { + fp = stdout; + } else { + fp = fopen(outfile, "wb"); + if(fp == NULL) { + fprintf(stderr, "Failed to create file: %s\n", outfile); + perror(NULL); + exit(EXIT_FAILURE); + } + } + + png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(png_ptr == NULL) { + fprintf(stderr, "Failed to initialize PNG writer.\n"); + exit(EXIT_FAILURE); + } + + info_ptr = png_create_info_struct(png_ptr); + if(info_ptr == NULL) { + fprintf(stderr, "Failed to initialize PNG write.\n"); + exit(EXIT_FAILURE); + } + + if(setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fprintf(stderr, "Failed to write PNG image.\n"); + exit(EXIT_FAILURE); + } + + if(type == PNG_TYPE) { + palette = (png_colorp) malloc(sizeof(png_color) * 2); + if(palette == NULL) { + fprintf(stderr, "Failed to allocate memory.\n"); + exit(EXIT_FAILURE); + } + palette[0].red = fg_color[0]; + palette[0].green = fg_color[1]; + palette[0].blue = fg_color[2]; + palette[1].red = bg_color[0]; + palette[1].green = bg_color[1]; + palette[1].blue = bg_color[2]; + alpha_values[0] = fg_color[3]; + alpha_values[1] = bg_color[3]; + png_set_PLTE(png_ptr, info_ptr, palette, 2); + png_set_tRNS(png_ptr, info_ptr, alpha_values, 2, NULL); + } + + png_init_io(png_ptr, fp); + if(type == PNG_TYPE) { + png_set_IHDR(png_ptr, info_ptr, + (unsigned int)realwidth, (unsigned int)realwidth, + 1, + PNG_COLOR_TYPE_PALETTE, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + } else { + png_set_IHDR(png_ptr, info_ptr, + (unsigned int)realwidth, (unsigned int)realwidth, + 8, + PNG_COLOR_TYPE_RGB_ALPHA, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + } + png_set_pHYs(png_ptr, info_ptr, + dpi * INCHES_PER_METER, + dpi * INCHES_PER_METER, + PNG_RESOLUTION_METER); + png_write_info(png_ptr, info_ptr); + + if(type == PNG_TYPE) { + /* top margin */ + memset(row, 0xff, (size_t)((realwidth + 7) / 8)); + for(y = 0; y < margin * size; y++) { + png_write_row(png_ptr, row); + } + + /* data */ + p = qrcode->data; + for(y = 0; y < qrcode->width; y++) { + memset(row, 0xff, (size_t)((realwidth + 7) / 8)); + q = row; + q += margin * size / 8; + bit = 7 - (margin * size % 8); + for(x = 0; x < qrcode->width; x++) { + for(xx = 0; xx < size; xx++) { + *q ^= (*p & 1) << bit; + bit--; + if(bit < 0) { + q++; + bit = 7; + } + } + p++; + } + for(yy = 0; yy < size; yy++) { + png_write_row(png_ptr, row); + } + } + /* bottom margin */ + memset(row, 0xff, (size_t)((realwidth + 7) / 8)); + for(y = 0; y < margin * size; y++) { + png_write_row(png_ptr, row); + } + } else { + /* top margin */ + fillRow(row, realwidth, bg_color); + for(y = 0; y < margin * size; y++) { + png_write_row(png_ptr, row); + } + + /* data */ + p = qrcode->data; + for(y = 0; y < qrcode->width; y++) { + fillRow(row, realwidth, bg_color); + for(x = 0; x < qrcode->width; x++) { + for(xx = 0; xx < size; xx++) { + if(*p & 1) { + memcpy(&row[((margin + x) * size + xx) * 4], fg_color, 4); + } + } + p++; + } + for(yy = 0; yy < size; yy++) { + png_write_row(png_ptr, row); + } + } + /* bottom margin */ + fillRow(row, realwidth, bg_color); + for(y = 0; y < margin * size; y++) { + png_write_row(png_ptr, row); + } + } + + png_write_end(png_ptr, info_ptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + + fclose(fp); + free(row); + free(palette); + + return 0; +#else + fputs("PNG output is disabled at compile time. No output generated.\n", stderr); + return 0; +#endif +} + +static int writeEPS(const QRcode *qrcode, const char *outfile) +{ + FILE *fp; + unsigned char *row, *p; + int x, y, yy; + int realwidth; + + fp = openFile(outfile); + + realwidth = (qrcode->width + margin * 2) * size; + /* EPS file header */ + fprintf(fp, "%%!PS-Adobe-2.0 EPSF-1.2\n" + "%%%%BoundingBox: 0 0 %d %d\n" + "%%%%Pages: 1 1\n" + "%%%%EndComments\n", realwidth, realwidth); + /* draw point */ + fprintf(fp, "/p { " + "moveto " + "0 1 rlineto " + "1 0 rlineto " + "0 -1 rlineto " + "fill " + "} bind def\n"); + /* set color */ + fprintf(fp, "gsave\n"); + fprintf(fp, "%f %f %f setrgbcolor\n", + (float)bg_color[0] / 255, + (float)bg_color[1] / 255, + (float)bg_color[2] / 255); + fprintf(fp, "%d %d scale\n", realwidth, realwidth); + fprintf(fp, "0 0 p\ngrestore\n"); + fprintf(fp, "%f %f %f setrgbcolor\n", + (float)fg_color[0] / 255, + (float)fg_color[1] / 255, + (float)fg_color[2] / 255); + fprintf(fp, "%d %d scale\n", size, size); + + /* data */ + p = qrcode->data; + for(y = 0; y < qrcode->width; y++) { + row = (p+(y*qrcode->width)); + yy = (margin + qrcode->width - y - 1); + + for(x = 0; x < qrcode->width; x++) { + if(*(row+x)&0x1) { + fprintf(fp, "%d %d p ", margin + x, yy); + } + } + } + + fprintf(fp, "\n%%%%EOF\n"); + fclose(fp); + + return 0; +} + +static void writeSVG_drawModules(FILE *fp, int x, int y, int width, const char* col, float opacity) +{ + if(svg_path) { + fprintf(fp, "M%d,%dh%d", x, y, width); + } else { + if(fg_color[3] != 255) { + fprintf(fp, "\t\t\t\n", + x, y, width, col, opacity ); + } else { + fprintf(fp, "\t\t\t\n", + x, y, width, col ); + } + } +} + +static int writeSVG(const QRcode *qrcode, const char *outfile) +{ + FILE *fp; + unsigned char *row, *p; + int x, y, x0, pen; + int symwidth, realwidth; + float scale; + char fg[7], bg[7]; + float fg_opacity; + float bg_opacity; + + fp = openFile(outfile); + + scale = dpi * INCHES_PER_METER / 100.0; + + symwidth = qrcode->width + margin * 2; + realwidth = symwidth * size; + + snprintf(fg, 7, "%02x%02x%02x", fg_color[0], fg_color[1], fg_color[2]); + snprintf(bg, 7, "%02x%02x%02x", bg_color[0], bg_color[1], bg_color[2]); + fg_opacity = (float)fg_color[3] / 255; + bg_opacity = (float)bg_color[3] / 255; + + /* XML declaration */ + if (!inline_svg) + fputs( "\n", fp ); + + /* DTD + No document type specified because "while a DTD is provided in [the SVG] + specification, the use of DTDs for validating XML documents is known to + be problematic. In particular, DTDs do not handle namespaces gracefully. + It is *not* recommended that a DOCTYPE declaration be included in SVG + documents." + http://www.w3.org/TR/2003/REC-SVG11-20030114/intro.html#Namespace + */ + + /* Vanity remark */ + fprintf(fp, "\n", QRcode_APIVersionString()); + + /* SVG code start */ + fprintf(fp, + "\n", + realwidth / scale, realwidth / scale, symwidth, symwidth + ); + + /* Make named group */ + fputs("\t\n", fp); + + /* Make solid background */ + if(bg_color[3] != 255) { + fprintf(fp, "\t\t\n", symwidth, symwidth, bg, bg_opacity); + } else { + fprintf(fp, "\t\t\n", symwidth, symwidth, bg); + } + + if(svg_path) { + if(fg_color[3] != 255) { + fprintf(fp, "\t\t\n", margin, margin); + } + + /* Write data */ + p = qrcode->data; + for(y = 0; y < qrcode->width; y++) { + row = (p+(y*qrcode->width)); + + if( !rle ) { + /* no RLE */ + for(x = 0; x < qrcode->width; x++) { + if(*(row+x)&0x1) { + writeSVG_drawModules(fp, x, y, 1, fg, fg_opacity); + } + } + } else { + /* simple RLE */ + pen = 0; + x0 = 0; + for(x = 0; x < qrcode->width; x++) { + if( !pen ) { + pen = *(row+x)&0x1; + x0 = x; + } else if(!(*(row+x)&0x1)) { + writeSVG_drawModules(fp, x0, y, x-x0, fg, fg_opacity); + pen = 0; + } + } + if( pen ) { + writeSVG_drawModules(fp, x0, y, qrcode->width - x0, fg, fg_opacity); + } + } + } + + if(svg_path) { + fputs("\"/>\n", fp); + } else { + /* Close QR data viewbox */ + fputs("\t\t\n", fp); + } + + /* Close group */ + fputs("\t\n", fp); + + /* Close SVG code */ + fputs("\n", fp); + fclose(fp); + + return 0; +} + +static int writeXPM(const QRcode *qrcode, const char *outfile) +{ + FILE *fp; + int x, xx, y, yy, realwidth, realmargin; + char *row; + char fg[7], bg[7]; + unsigned char *p; + + fp = openFile(outfile); + + realwidth = (qrcode->width + margin * 2) * size; + realmargin = margin * size; + + row = malloc((size_t)realwidth + 1); + if (!row ) { + fprintf(stderr, "Failed to allocate memory.\n"); + exit(EXIT_FAILURE); + } + + snprintf(fg, 7, "%02x%02x%02x", fg_color[0], fg_color[1], fg_color[2]); + snprintf(bg, 7, "%02x%02x%02x", bg_color[0], bg_color[1], bg_color[2]); + + fputs("/* XPM */\n", fp); + fputs("static const char *const qrcode_xpm[] = {\n", fp); + fputs("/* width height ncolors chars_per_pixel */\n", fp); + fprintf(fp, "\"%d %d 2 1\",\n", realwidth, realwidth); + + fputs("/* colors */\n", fp); + fprintf(fp, "\"F c #%s\",\n", fg); + fprintf(fp, "\"B c #%s\",\n", bg); + + fputs("/* pixels */\n", fp); + memset(row, 'B', (size_t)realwidth); + row[realwidth] = '\0'; + + for (y = 0; y < realmargin; y++) { + fprintf(fp, "\"%s\",\n", row); + } + + p = qrcode->data; + for (y = 0; y < qrcode->width; y++) { + for (yy = 0; yy < size; yy++) { + fputs("\"", fp); + + for (x = 0; x < margin; x++) { + for (xx = 0; xx < size; xx++) { + fputs("B", fp); + } + } + + for (x = 0; x < qrcode->width; x++) { + for (xx = 0; xx < size; xx++) { + if (p[(y * qrcode->width) + x] & 0x1) { + fputs("F", fp); + } else { + fputs("B", fp); + } + } + } + + for (x = 0; x < margin; x++) { + for (xx = 0; xx < size; xx++) { + fputs("B", fp); + } + } + + fputs("\",\n", fp); + } + } + + for (y = 0; y < realmargin; y++) { + fprintf(fp, "\"%s\"%s\n", row, y < (size - 1) ? "," : "};"); + } + + free(row); + fclose(fp); + + return 0; +} + +static void writeANSI_margin(FILE* fp, int realwidth, + char* buffer, const char* white, int white_s ) +{ + int y; + + strncpy(buffer, white, (size_t)white_s); + memset(buffer + white_s, ' ', (size_t)realwidth * 2); + strcpy(buffer + white_s + realwidth * 2, "\033[0m\n"); // reset to default colors + for(y = 0; y < margin; y++ ){ + fputs(buffer, fp); + } +} + +static int writeANSI(const QRcode *qrcode, const char *outfile) +{ + FILE *fp; + unsigned char *row, *p; + int x, y; + int realwidth; + int last; + + const char *white, *black; + char *buffer; + int white_s, black_s, buffer_s; + + if(image_type == ANSI256_TYPE){ + /* codes for 256 color compatible terminals */ + white = "\033[48;5;231m"; + white_s = 11; + black = "\033[48;5;16m"; + black_s = 10; + } else { + white = "\033[47m"; + white_s = 5; + black = "\033[40m"; + black_s = 5; + } + + size = 1; + + fp = openFile(outfile); + + realwidth = (qrcode->width + margin * 2) * size; + buffer_s = (realwidth * white_s) * 2; + buffer = (char *)malloc((size_t)buffer_s); + if(buffer == NULL) { + fprintf(stderr, "Failed to allocate memory.\n"); + exit(EXIT_FAILURE); + } + + /* top margin */ + writeANSI_margin(fp, realwidth, buffer, white, white_s); + + /* data */ + p = qrcode->data; + for(y = 0; y < qrcode->width; y++) { + row = (p+(y*qrcode->width)); + + memset(buffer, 0, (size_t)buffer_s); + strncpy(buffer, white, (size_t)white_s); + for(x = 0; x < margin; x++ ){ + strncat(buffer, " ", 2); + } + last = 0; + + for(x = 0; x < qrcode->width; x++) { + if(*(row+x)&0x1) { + if( last != 1 ){ + strncat(buffer, black, (size_t)black_s); + last = 1; + } + } else if( last != 0 ){ + strncat(buffer, white, (size_t)white_s); + last = 0; + } + strncat(buffer, " ", 2); + } + + if( last != 0 ){ + strncat(buffer, white, (size_t)white_s); + } + for(x = 0; x < margin; x++ ){ + strncat(buffer, " ", 2); + } + strncat(buffer, "\033[0m\n", 5); + fputs(buffer, fp); + } + + /* bottom margin */ + writeANSI_margin(fp, realwidth, buffer, white, white_s); + + fclose(fp); + free(buffer); + + return 0; +} + +static void writeUTF8_margin(FILE* fp, int realwidth, const char* white, + const char *reset, const char* full) +{ + int x, y; + + for (y = 0; y < margin/2; y++) { + fputs(white, fp); + for (x = 0; x < realwidth; x++) + fputs(full, fp); + fputs(reset, fp); + fputc('\n', fp); + } +} + +static int writeUTF8(const QRcode *qrcode, const char *outfile, int use_ansi, int invert) +{ + FILE *fp; + int x, y; + int realwidth; + const char *white, *reset; + const char *empty, *lowhalf, *uphalf, *full; + + empty = " "; + lowhalf = "\342\226\204"; + uphalf = "\342\226\200"; + full = "\342\226\210"; + + if (invert) { + const char *tmp; + + tmp = empty; + empty = full; + full = tmp; + + tmp = lowhalf; + lowhalf = uphalf; + uphalf = tmp; + } + + if (use_ansi){ + if (use_ansi == 2) { + white = "\033[38;5;231m\033[48;5;16m"; + } else { + white = "\033[40;37;1m"; + } + reset = "\033[0m"; + } else { + white = ""; + reset = ""; + } + + fp = openFile(outfile); + + realwidth = (qrcode->width + margin * 2); + + /* top margin */ + writeUTF8_margin(fp, realwidth, white, reset, full); + + /* data */ + for(y = 0; y < qrcode->width; y += 2) { + unsigned char *row1, *row2; + row1 = qrcode->data + y*qrcode->width; + row2 = row1 + qrcode->width; + + fputs(white, fp); + + for (x = 0; x < margin; x++) { + fputs(full, fp); + } + + for (x = 0; x < qrcode->width; x++) { + if(row1[x] & 1) { + if(y < qrcode->width - 1 && row2[x] & 1) { + fputs(empty, fp); + } else { + fputs(lowhalf, fp); + } + } else if(y < qrcode->width - 1 && row2[x] & 1) { + fputs(uphalf, fp); + } else { + fputs(full, fp); + } + } + + for (x = 0; x < margin; x++) + fputs(full, fp); + + fputs(reset, fp); + fputc('\n', fp); + } + + /* bottom margin */ + writeUTF8_margin(fp, realwidth, white, reset, full); + + fclose(fp); + + return 0; +} + +static void writeASCII_margin(FILE* fp, int realwidth, char* buffer, int invert) +{ + int y, h; + + h = margin; + + memset(buffer, (invert?'#':' '), (size_t)realwidth); + buffer[realwidth] = '\n'; + buffer[realwidth + 1] = '\0'; + for(y = 0; y < h; y++ ){ + fputs(buffer, fp); + } +} + +static int writeASCII(const QRcode *qrcode, const char *outfile, int invert) +{ + FILE *fp; + unsigned char *row; + int x, y; + int realwidth; + char *buffer, *p; + int buffer_s; + char black = '#'; + char white = ' '; + + if(invert) { + black = ' '; + white = '#'; + } + + size = 1; + + fp = openFile(outfile); + + realwidth = (qrcode->width + margin * 2) * 2; + buffer_s = realwidth + 2; + buffer = (char *)malloc((size_t)buffer_s); + if(buffer == NULL) { + fprintf(stderr, "Failed to allocate memory.\n"); + exit(EXIT_FAILURE); + } + + /* top margin */ + writeASCII_margin(fp, realwidth, buffer, invert); + + /* data */ + for(y = 0; y < qrcode->width; y++) { + row = qrcode->data+(y*qrcode->width); + p = buffer; + + memset(p, white, (size_t)margin * 2); + p += margin * 2; + + for(x = 0; x < qrcode->width; x++) { + if(row[x]&0x1) { + *p++ = black; + *p++ = black; + } else { + *p++ = white; + *p++ = white; + } + } + + memset(p, white, (size_t)margin * 2); + p += margin * 2; + *p++ = '\n'; + *p++ = '\0'; + fputs( buffer, fp ); + } + + /* bottom margin */ + writeASCII_margin(fp, realwidth, buffer, invert); + + fclose(fp); + free(buffer); + + return 0; +} + +static QRcode *encode(const unsigned char *intext, int length) +{ + QRcode *code; + + if(micro) { + if(eightbit) { + code = QRcode_encodeDataMQR(length, intext, version, level); + } else { + code = QRcode_encodeStringMQR((char *)intext, version, level, hint, casesensitive); + } + } else if(eightbit) { + code = QRcode_encodeData(length, intext, version, level); + } else { + code = QRcode_encodeString((char *)intext, version, level, hint, casesensitive); + } + + return code; +} + +static void qrencode(const unsigned char *intext, int length, const char *outfile) +{ + QRcode *qrcode; + + qrcode = encode(intext, length); + if(qrcode == NULL) { + if(errno == ERANGE) { + fprintf(stderr, "Failed to encode the input data: Input data too large\n"); + } else { + perror("Failed to encode the input data"); + } + exit(EXIT_FAILURE); + } + if(strict_versioning && version > 0 && qrcode->version != version) { + fprintf(stderr, "Failed to encode the input data: Input data too large\n"); + exit(EXIT_FAILURE); + } + + if(verbose) { + fprintf(stderr, "File: %s, Version: %d\n", (outfile!=NULL)?outfile:"(stdout)", qrcode->version); + } + + switch(image_type) { + case PNG_TYPE: + case PNG32_TYPE: + writePNG(qrcode, outfile, image_type); + break; + case EPS_TYPE: + writeEPS(qrcode, outfile); + break; + case SVG_TYPE: + writeSVG(qrcode, outfile); + break; + case XPM_TYPE: + writeXPM(qrcode, outfile); + break; + case ANSI_TYPE: + case ANSI256_TYPE: + writeANSI(qrcode, outfile); + break; + case ASCIIi_TYPE: + writeASCII(qrcode, outfile, 1); + break; + case ASCII_TYPE: + writeASCII(qrcode, outfile, 0); + break; + case UTF8_TYPE: + writeUTF8(qrcode, outfile, 0, 0); + break; + case ANSIUTF8_TYPE: + writeUTF8(qrcode, outfile, 1, 0); + break; + case ANSI256UTF8_TYPE: + writeUTF8(qrcode, outfile, 2, 0); + break; + case UTF8i_TYPE: + writeUTF8(qrcode, outfile, 0, 1); + break; + case ANSIUTF8i_TYPE: + writeUTF8(qrcode, outfile, 1, 1); + break; + default: + fprintf(stderr, "Unknown image type.\n"); + exit(EXIT_FAILURE); + } + + QRcode_free(qrcode); +} + +static QRcode_List *encodeStructured(const unsigned char *intext, int length) +{ + QRcode_List *list; + + if(eightbit) { + list = QRcode_encodeDataStructured(length, intext, version, level); + } else { + list = QRcode_encodeStringStructured((char *)intext, version, level, hint, casesensitive); + } + + return list; +} + +static void qrencodeStructured(const unsigned char *intext, int length, const char *outfile) +{ + QRcode_List *qrlist, *p; + char filename[FILENAME_MAX]; + char *base, *q, *suffix = NULL; + const char *type_suffix; + int i = 1; + size_t suffix_size; + + switch(image_type) { + case PNG_TYPE: + type_suffix = ".png"; + break; + case EPS_TYPE: + type_suffix = ".eps"; + break; + case SVG_TYPE: + type_suffix = ".svg"; + break; + case XPM_TYPE: + type_suffix = ".xpm"; + break; + case ANSI_TYPE: + case ANSI256_TYPE: + case ASCII_TYPE: + case UTF8_TYPE: + case ANSIUTF8_TYPE: + case UTF8i_TYPE: + case ANSIUTF8i_TYPE: + type_suffix = ".txt"; + break; + default: + fprintf(stderr, "Unknown image type.\n"); + exit(EXIT_FAILURE); + } + + if(outfile == NULL) { + fprintf(stderr, "An output filename must be specified to store the structured images.\n"); + exit(EXIT_FAILURE); + } + base = strdup(outfile); + if(base == NULL) { + fprintf(stderr, "Failed to allocate memory.\n"); + exit(EXIT_FAILURE); + } + suffix_size = strlen(type_suffix); + if(strlen(base) > suffix_size) { + q = base + strlen(base) - suffix_size; + if(strcasecmp(type_suffix, q) == 0) { + suffix = strdup(q); + *q = '\0'; + } + } + + qrlist = encodeStructured(intext, length); + if(qrlist == NULL) { + if(errno == ERANGE) { + fprintf(stderr, "Failed to encode the input data: Input data too large\n"); + } else { + perror("Failed to encode the input data"); + } + exit(EXIT_FAILURE); + } + + for(p = qrlist; p != NULL; p = p->next) { + if(p->code == NULL) { + fprintf(stderr, "Failed to encode the input data.\n"); + exit(EXIT_FAILURE); + } + if(suffix) { + snprintf(filename, FILENAME_MAX, "%s-%02d%s", base, i, suffix); + } else { + snprintf(filename, FILENAME_MAX, "%s-%02d", base, i); + } + + if(verbose) { + fprintf(stderr, "File: %s, Version: %d\n", filename, p->code->version); + } + + switch(image_type) { + case PNG_TYPE: + case PNG32_TYPE: + writePNG(p->code, filename, image_type); + break; + case EPS_TYPE: + writeEPS(p->code, filename); + break; + case SVG_TYPE: + writeSVG(p->code, filename); + break; + case XPM_TYPE: + writeXPM(p->code, filename); + break; + case ANSI_TYPE: + case ANSI256_TYPE: + writeANSI(p->code, filename); + break; + case ASCIIi_TYPE: + writeASCII(p->code, filename, 1); + break; + case ASCII_TYPE: + writeASCII(p->code, filename, 0); + break; + case UTF8_TYPE: + writeUTF8(p->code, filename, 0, 0); + break; + case ANSIUTF8_TYPE: + writeUTF8(p->code, filename, 0, 0); + break; + case ANSI256UTF8_TYPE: + writeUTF8(p->code, filename, 0, 0); + break; + case UTF8i_TYPE: + writeUTF8(p->code, filename, 0, 1); + break; + case ANSIUTF8i_TYPE: + writeUTF8(p->code, filename, 0, 1); + break; + + default: + fprintf(stderr, "Unknown image type.\n"); + exit(EXIT_FAILURE); + } + i++; + } + + free(base); + if(suffix) { + free(suffix); + } + + QRcode_List_free(qrlist); +} + +int main(int argc, char **argv) +{ + int opt, lindex = -1; + char *outfile = NULL, *infile = NULL; + unsigned char *intext = NULL; + int length = 0; + FILE *fp; + + while((opt = getopt_long(argc, argv, optstring, options, &lindex)) != -1) { + switch(opt) { + case 'h': + if(lindex == 0) { + usage(1, 1, EXIT_SUCCESS); + } else { + usage(1, 0, EXIT_SUCCESS); + } + exit(EXIT_SUCCESS); + case 'o': + outfile = optarg; + break; + case 'r': + infile = optarg; + break; + case 's': + size = atoi(optarg); + if(size <= 0) { + fprintf(stderr, "Invalid size: %d\n", size); + exit(EXIT_FAILURE); + } + break; + case 'v': + version = atoi(optarg); + if(version < 0) { + fprintf(stderr, "Invalid version: %d\n", version); + exit(EXIT_FAILURE); + } + break; + case 'l': + switch(*optarg) { + case 'l': + case 'L': + level = QR_ECLEVEL_L; + break; + case 'm': + case 'M': + level = QR_ECLEVEL_M; + break; + case 'q': + case 'Q': + level = QR_ECLEVEL_Q; + break; + case 'h': + case 'H': + level = QR_ECLEVEL_H; + break; + default: + fprintf(stderr, "Invalid level: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'm': + margin = atoi(optarg); + if(margin < 0) { + fprintf(stderr, "Invalid margin: %d\n", margin); + exit(EXIT_FAILURE); + } + break; + case 'd': + dpi = atoi(optarg); + if( dpi < 0 ) { + fprintf(stderr, "Invalid DPI: %d\n", dpi); + exit(EXIT_FAILURE); + } + break; + case 't': + if(strcasecmp(optarg, "png32") == 0) { + image_type = PNG32_TYPE; + } else if(strcasecmp(optarg, "png") == 0) { + image_type = PNG_TYPE; + } else if(strcasecmp(optarg, "eps") == 0) { + image_type = EPS_TYPE; + } else if(strcasecmp(optarg, "svg") == 0) { + image_type = SVG_TYPE; + } else if(strcasecmp(optarg, "xpm") == 0) { + image_type = XPM_TYPE; + } else if(strcasecmp(optarg, "ansi") == 0) { + image_type = ANSI_TYPE; + } else if(strcasecmp(optarg, "ansi256") == 0) { + image_type = ANSI256_TYPE; + } else if(strcasecmp(optarg, "asciii") == 0) { + image_type = ASCIIi_TYPE; + } else if(strcasecmp(optarg, "ascii") == 0) { + image_type = ASCII_TYPE; + } else if(strcasecmp(optarg, "utf8") == 0) { + image_type = UTF8_TYPE; + } else if(strcasecmp(optarg, "ansiutf8") == 0) { + image_type = ANSIUTF8_TYPE; + } else if(strcasecmp(optarg, "ansi256utf8") == 0) { + image_type = ANSI256UTF8_TYPE; + } else if(strcasecmp(optarg, "utf8i") == 0) { + image_type = UTF8i_TYPE; + } else if(strcasecmp(optarg, "ansiutf8i") == 0) { + image_type = ANSIUTF8i_TYPE; + } else { + fprintf(stderr, "Invalid image type: %s\n", optarg); + exit(EXIT_FAILURE); + } + break; + case 'S': + structured = 1; + break; + case 'k': + hint = QR_MODE_KANJI; + break; + case 'c': + casesensitive = 1; + break; + case 'i': + casesensitive = 0; + break; + case '8': + eightbit = 1; + break; + case 'M': + micro = 1; + break; + case 'f': + if(color_set(fg_color, optarg)) { + fprintf(stderr, "Invalid foreground color value.\n"); + exit(EXIT_FAILURE); + } + break; + case 'b': + if(color_set(bg_color, optarg)) { + fprintf(stderr, "Invalid background color value.\n"); + exit(EXIT_FAILURE); + } + break; + case 'V': + usage(0, 0, EXIT_SUCCESS); + exit(EXIT_SUCCESS); + case 0: + break; + default: + fprintf(stderr, "Try \"qrencode --help\" for more information.\n"); + exit(EXIT_FAILURE); + } + } + + if(argc == 1) { + usage(1, 0, EXIT_FAILURE); + exit(EXIT_FAILURE); + } + + if(outfile == NULL && image_type == PNG_TYPE) { + fprintf(stderr, "No output filename is given.\n"); + exit(EXIT_FAILURE); + } + + if(optind < argc) { + intext = (unsigned char *)argv[optind]; + length = strlen((char *)intext); + } + if(intext == NULL) { + fp = infile == NULL ? stdin : fopen(infile,"r"); + if(fp == 0) { + fprintf(stderr, "Cannot read input file %s.\n", infile); + exit(EXIT_FAILURE); + } + intext = readFile(fp,&length); + + } + + if(micro && version > MQRSPEC_VERSION_MAX) { + fprintf(stderr, "Version number should be less or equal to %d.\n", MQRSPEC_VERSION_MAX); + exit(EXIT_FAILURE); + } else if(!micro && version > QRSPEC_VERSION_MAX) { + fprintf(stderr, "Version number should be less or equal to %d.\n", QRSPEC_VERSION_MAX); + exit(EXIT_FAILURE); + } + + if(margin < 0) { + if(micro) { + margin = 2; + } else { + margin = 4; + } + } + + if(micro) { + if(structured) { + fprintf(stderr, "Micro QR Code does not support structured symbols.\n"); + exit(EXIT_FAILURE); + } + } + + if(structured) { + if(version == 0) { + fprintf(stderr, "Version number must be specified to encode structured symbols.\n"); + exit(EXIT_FAILURE); + } + qrencodeStructured(intext, length, outfile); + } else { + qrencode(intext, length, outfile); + } + + return 0; +} diff --git a/src/otsur/qrencode/qrencode.c b/src/otsur/qrencode/qrencode.c new file mode 100644 index 0000000000..2cb9ca86e2 --- /dev/null +++ b/src/otsur/qrencode/qrencode.c @@ -0,0 +1,938 @@ +/* + * qrencode - QR Code encoder + * + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include + +#include "qrencode.h" +#include "qrspec.h" +#include "mqrspec.h" +#include "bitstream.h" +#include "qrinput.h" +#include "rsecc.h" +#include "split.h" +#include "mask.h" +#include "mmask.h" + +/****************************************************************************** + * Raw code + *****************************************************************************/ + +typedef struct { + int dataLength; + int eccLength; + unsigned char *data; + unsigned char *ecc; +} RSblock; + +typedef struct { + int version; + int dataLength; + int eccLength; + unsigned char *datacode; + unsigned char *ecccode; + int b1; + int blocks; + RSblock *rsblock; + int count; +} QRRawCode; + +static void RSblock_initBlock(RSblock *block, int dl, unsigned char *data, int el, unsigned char *ecc) +{ + block->dataLength = dl; + block->data = data; + block->eccLength = el; + block->ecc = ecc; + + RSECC_encode((size_t)dl, (size_t)el, data, ecc); +} + +static int RSblock_init(RSblock *blocks, int spec[5], unsigned char *data, unsigned char *ecc) +{ + int i; + RSblock *block; + unsigned char *dp, *ep; + int el, dl; + + dl = QRspec_rsDataCodes1(spec); + el = QRspec_rsEccCodes1(spec); + + block = blocks; + dp = data; + ep = ecc; + for(i = 0; i < QRspec_rsBlockNum1(spec); i++) { + RSblock_initBlock(block, dl, dp, el, ep); + dp += dl; + ep += el; + block++; + } + + if(QRspec_rsBlockNum2(spec) == 0) return 0; + + dl = QRspec_rsDataCodes2(spec); + el = QRspec_rsEccCodes2(spec); + for(i = 0; i < QRspec_rsBlockNum2(spec); i++) { + RSblock_initBlock(block, dl, dp, el, ep); + dp += dl; + ep += el; + block++; + } + + return 0; +} + +STATIC_IN_RELEASE void QRraw_free(QRRawCode *raw); +STATIC_IN_RELEASE QRRawCode *QRraw_new(QRinput *input) +{ + QRRawCode *raw; + int spec[5], ret; + + raw = (QRRawCode *)malloc(sizeof(QRRawCode)); + if(raw == NULL) return NULL; + + raw->datacode = QRinput_getByteStream(input); + if(raw->datacode == NULL) { + free(raw); + return NULL; + } + + QRspec_getEccSpec(input->version, input->level, spec); + + raw->version = input->version; + raw->b1 = QRspec_rsBlockNum1(spec); + raw->dataLength = QRspec_rsDataLength(spec); + raw->eccLength = QRspec_rsEccLength(spec); + raw->ecccode = (unsigned char *)malloc((size_t)raw->eccLength); + if(raw->ecccode == NULL) { + free(raw->datacode); + free(raw); + return NULL; + } + + raw->blocks = QRspec_rsBlockNum(spec); + raw->rsblock = (RSblock *)calloc((size_t)(raw->blocks), sizeof(RSblock)); + if(raw->rsblock == NULL) { + QRraw_free(raw); + return NULL; + } + ret = RSblock_init(raw->rsblock, spec, raw->datacode, raw->ecccode); + if(ret < 0) { + QRraw_free(raw); + return NULL; + } + + raw->count = 0; + + return raw; +} + +/** + * Return a code (byte). + * This function can be called iteratively. + * @param raw raw code. + * @return code + */ +STATIC_IN_RELEASE unsigned char QRraw_getCode(QRRawCode *raw) +{ + int col, row; + unsigned char ret; + + if(raw->count < raw->dataLength) { + row = raw->count % raw->blocks; + col = raw->count / raw->blocks; + if(col >= raw->rsblock[0].dataLength) { + row += raw->b1; + } + ret = raw->rsblock[row].data[col]; + } else if(raw->count < raw->dataLength + raw->eccLength) { + row = (raw->count - raw->dataLength) % raw->blocks; + col = (raw->count - raw->dataLength) / raw->blocks; + ret = raw->rsblock[row].ecc[col]; + } else { + return 0; + } + raw->count++; + return ret; +} + +STATIC_IN_RELEASE void QRraw_free(QRRawCode *raw) +{ + if(raw != NULL) { + free(raw->datacode); + free(raw->ecccode); + free(raw->rsblock); + free(raw); + } +} + +/****************************************************************************** + * Raw code for Micro QR Code + *****************************************************************************/ + +typedef struct { + int version; + int dataLength; + int eccLength; + unsigned char *datacode; + unsigned char *ecccode; + RSblock *rsblock; + int oddbits; + int count; +} MQRRawCode; + +STATIC_IN_RELEASE void MQRraw_free(MQRRawCode *raw); +STATIC_IN_RELEASE MQRRawCode *MQRraw_new(QRinput *input) +{ + MQRRawCode *raw; + + raw = (MQRRawCode *)malloc(sizeof(MQRRawCode)); + if(raw == NULL) return NULL; + + raw->version = input->version; + raw->dataLength = MQRspec_getDataLength(input->version, input->level); + raw->eccLength = MQRspec_getECCLength(input->version, input->level); + raw->oddbits = raw->dataLength * 8 - MQRspec_getDataLengthBit(input->version, input->level); + raw->datacode = QRinput_getByteStream(input); + if(raw->datacode == NULL) { + free(raw); + return NULL; + } + raw->ecccode = (unsigned char *)malloc((size_t)raw->eccLength); + if(raw->ecccode == NULL) { + free(raw->datacode); + free(raw); + return NULL; + } + + raw->rsblock = (RSblock *)calloc(1, sizeof(RSblock)); + if(raw->rsblock == NULL) { + MQRraw_free(raw); + return NULL; + } + + RSblock_initBlock(raw->rsblock, raw->dataLength, raw->datacode, raw->eccLength, raw->ecccode); + + raw->count = 0; + + return raw; +} + +/** + * Return a code (byte). + * This function can be called iteratively. + * @param raw raw code. + * @return code + */ +STATIC_IN_RELEASE unsigned char MQRraw_getCode(MQRRawCode *raw) +{ + unsigned char ret; + + if(raw->count < raw->dataLength) { + ret = raw->datacode[raw->count]; + } else if(raw->count < raw->dataLength + raw->eccLength) { + ret = raw->ecccode[raw->count - raw->dataLength]; + } else { + return 0; + } + raw->count++; + return ret; +} + +STATIC_IN_RELEASE void MQRraw_free(MQRRawCode *raw) +{ + if(raw != NULL) { + free(raw->datacode); + free(raw->ecccode); + free(raw->rsblock); + free(raw); + } +} + + +/****************************************************************************** + * Frame filling + *****************************************************************************/ + +typedef struct { + int width; + unsigned char *frame; + int x, y; + int dir; + int bit; + int mqr; +} FrameFiller; + +static void FrameFiller_set(FrameFiller *filler, int width, unsigned char *frame, int mqr) +{ + filler->width = width; + filler->frame = frame; + filler->x = width - 1; + filler->y = width - 1; + filler->dir = -1; + filler->bit = -1; + filler->mqr = mqr; +} + +static unsigned char *FrameFiller_next(FrameFiller *filler) +{ + unsigned char *p; + int x, y, w; + + if(filler->bit == -1) { + filler->bit = 0; + return filler->frame + filler->y * filler->width + filler->x; + } + + x = filler->x; + y = filler->y; + p = filler->frame; + w = filler->width; + + if(filler->bit == 0) { + x--; + filler->bit++; + } else { + x++; + y += filler->dir; + filler->bit--; + } + + if(filler->dir < 0) { + if(y < 0) { + y = 0; + x -= 2; + filler->dir = 1; + if(!filler->mqr && x == 6) { + x--; + y = 9; + } + } + } else if(y == w) { + y = w - 1; + x -= 2; + filler->dir = -1; + if(!filler->mqr && x == 6) { + x--; + y -= 8; + } + } + if(x < 0 || y < 0) return NULL; + + filler->x = x; + filler->y = y; + + if(p[y * w + x] & 0x80) { + // This tail recursion could be optimized. + return FrameFiller_next(filler); + } + return &p[y * w + x]; +} + +#ifdef WITH_TESTS +unsigned char *FrameFiller_test(int version) +{ + int width; + unsigned char *frame, *p; + int i, length; + FrameFiller filler; + + width = QRspec_getWidth(version); + frame = QRspec_newFrame(version); + if(frame == NULL) return NULL; + FrameFiller_set(&filler, width, frame, 0); + length = QRspec_getDataLength(version, QR_ECLEVEL_L) * 8 + + QRspec_getECCLength(version, QR_ECLEVEL_L) * 8 + + QRspec_getRemainder(version); + for(i = 0; i < length; i++) { + p = FrameFiller_next(&filler); + if(p == NULL) { + free(frame); + return NULL; + } + *p = (unsigned char)(i & 0x7f) | 0x80; + } + return frame; +} + +unsigned char *FrameFiller_testMQR(int version) +{ + int width; + unsigned char *frame, *p; + int i, length; + FrameFiller filler; + + width = MQRspec_getWidth(version); + frame = MQRspec_newFrame(version); + if(frame == NULL) return NULL; + FrameFiller_set(&filler, width, frame, 1); + length = MQRspec_getDataLengthBit(version, QR_ECLEVEL_L) + + MQRspec_getECCLength(version, QR_ECLEVEL_L) * 8; + for(i = 0; i < length; i++) { + p = FrameFiller_next(&filler); + if(p == NULL) { + fprintf(stderr, "Frame filler run over the frame!\n"); + return frame; + } + *p = (unsigned char)(i & 0x7f) | 0x80; + } + return frame; +} +#endif + + +/****************************************************************************** + * QR-code encoding + *****************************************************************************/ + +STATIC_IN_RELEASE QRcode *QRcode_new(int version, int width, unsigned char *data) +{ + QRcode *qrcode; + + qrcode = (QRcode *)malloc(sizeof(QRcode)); + if(qrcode == NULL) return NULL; + + qrcode->version = version; + qrcode->width = width; + qrcode->data = data; + + return qrcode; +} + +void QRcode_free(QRcode *qrcode) +{ + if(qrcode != NULL) { + free(qrcode->data); + free(qrcode); + } +} + +STATIC_IN_RELEASE QRcode *QRcode_encodeMask(QRinput *input, int mask) +{ + int width, version; + QRRawCode *raw; + unsigned char *frame, *masked, *p, code, bit; + int i, j; + QRcode *qrcode = NULL; + FrameFiller filler; + + if(input->mqr) { + errno = EINVAL; + return NULL; + } + if(input->version < 0 || input->version > QRSPEC_VERSION_MAX) { + errno = EINVAL; + return NULL; + } + if(!(input->level >= QR_ECLEVEL_L && input->level <= QR_ECLEVEL_H)) { + errno = EINVAL; + return NULL; + } + + raw = QRraw_new(input); + if(raw == NULL) return NULL; + + version = raw->version; + width = QRspec_getWidth(version); + frame = QRspec_newFrame(version); + if(frame == NULL) { + QRraw_free(raw); + return NULL; + } + FrameFiller_set(&filler, width, frame, 0); + + /* interleaved data and ecc codes */ + for(i = 0; i < raw->dataLength; i++) { + code = QRraw_getCode(raw); + bit = 0x80; + for(j = 0; j < 8; j++) { + p = FrameFiller_next(&filler); + if(p == NULL) goto EXIT; + *p = ((bit & code) != 0); + bit = bit >> 1; + } + } + for(i = 0; i < raw->eccLength; i++) { + code = QRraw_getCode(raw); + bit = 0x80; + for(j = 0; j < 8; j++) { + p = FrameFiller_next(&filler); + if(p == NULL) goto EXIT; + *p = 0x02 | ((bit & code) != 0); + bit = bit >> 1; + } + } + QRraw_free(raw); + raw = NULL; + /* remainder bits */ + j = QRspec_getRemainder(version); + for(i = 0; i < j; i++) { + p = FrameFiller_next(&filler); + if(p == NULL) goto EXIT; + *p = 0x02; + } + + /* masking */ + if(mask == -2) { // just for debug purpose + masked = (unsigned char *)malloc((size_t)(width * width)); + memcpy(masked, frame, (size_t)(width * width)); + } else if(mask < 0) { + masked = Mask_mask(width, frame, input->level); + } else { + masked = Mask_makeMask(width, frame, mask, input->level); + } + if(masked == NULL) { + goto EXIT; + } + qrcode = QRcode_new(version, width, masked); + if(qrcode == NULL) { + free(masked); + } + +EXIT: + QRraw_free(raw); + free(frame); + return qrcode; +} + +STATIC_IN_RELEASE QRcode *QRcode_encodeMaskMQR(QRinput *input, int mask) +{ + int width, version; + MQRRawCode *raw; + unsigned char *frame, *masked, *p, code, bit; + int i, j, length; + QRcode *qrcode = NULL; + FrameFiller filler; + + if(!input->mqr) { + errno = EINVAL; + return NULL; + } + if(input->version <= 0 || input->version > MQRSPEC_VERSION_MAX) { + errno = EINVAL; + return NULL; + } + if(!(input->level >= QR_ECLEVEL_L && input->level <= QR_ECLEVEL_Q)) { + errno = EINVAL; + return NULL; + } + + raw = MQRraw_new(input); + if(raw == NULL) return NULL; + + version = raw->version; + width = MQRspec_getWidth(version); + frame = MQRspec_newFrame(version); + if(frame == NULL) { + MQRraw_free(raw); + return NULL; + } + FrameFiller_set(&filler, width, frame, 1); + + /* interleaved data and ecc codes */ + for(i = 0; i < raw->dataLength; i++) { + code = MQRraw_getCode(raw); + bit = 0x80; + if(raw->oddbits && i == raw->dataLength - 1) { + length = raw->oddbits; + } else { + length = 8; + } + for(j = 0; j < length; j++) { + p = FrameFiller_next(&filler); + if(p == NULL) goto EXIT; + *p = ((bit & code) != 0); + bit = bit >> 1; + } + } + for(i = 0; i < raw->eccLength; i++) { + code = MQRraw_getCode(raw); + bit = 0x80; + length = 8; + for(j = 0; j < length; j++) { + p = FrameFiller_next(&filler); + if(p == NULL) goto EXIT; + *p = 0x02 | ((bit & code) != 0); + bit = bit >> 1; + } + } + MQRraw_free(raw); + raw = NULL; + + /* masking */ + if(mask == -2) { // just for debug purpose + masked = (unsigned char *)malloc((size_t)(width * width)); + memcpy(masked, frame, (size_t)(width * width)); + } else if(mask < 0) { + masked = MMask_mask(version, frame, input->level); + } else { + masked = MMask_makeMask(version, frame, mask, input->level); + } + if(masked == NULL) { + goto EXIT; + } + + qrcode = QRcode_new(version, width, masked); + if(qrcode == NULL) { + free(masked); + } + +EXIT: + MQRraw_free(raw); + free(frame); + return qrcode; +} + +QRcode *QRcode_encodeInput(QRinput *input) +{ + if(input->mqr) { + return QRcode_encodeMaskMQR(input, -1); + } else { + return QRcode_encodeMask(input, -1); + } +} + +static QRcode *QRcode_encodeStringReal(const char *string, int version, QRecLevel level, int mqr, QRencodeMode hint, int casesensitive) +{ + QRinput *input; + QRcode *code; + int ret; + + if(string == NULL) { + errno = EINVAL; + return NULL; + } + if(hint != QR_MODE_8 && hint != QR_MODE_KANJI) { + errno = EINVAL; + return NULL; + } + + if(mqr) { + input = QRinput_newMQR(version, level); + } else { + input = QRinput_new2(version, level); + } + if(input == NULL) return NULL; + + ret = Split_splitStringToQRinput(string, input, hint, casesensitive); + if(ret < 0) { + QRinput_free(input); + return NULL; + } + code = QRcode_encodeInput(input); + QRinput_free(input); + + return code; +} + +QRcode *QRcode_encodeString(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive) +{ + return QRcode_encodeStringReal(string, version, level, 0, hint, casesensitive); +} + +QRcode *QRcode_encodeStringMQR(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive) +{ + int i; + + if(version == 0) { + version = 1; + } + for(i = version; i <= MQRSPEC_VERSION_MAX ; i++) { + QRcode *code = QRcode_encodeStringReal(string, i, level, 1, hint, casesensitive); + if(code != NULL) return code; + } + + return NULL; +} + +static QRcode *QRcode_encodeDataReal(const unsigned char *data, int length, int version, QRecLevel level, int mqr) +{ + QRinput *input; + QRcode *code; + int ret; + + if(data == NULL || length == 0) { + errno = EINVAL; + return NULL; + } + + if(mqr) { + input = QRinput_newMQR(version, level); + } else { + input = QRinput_new2(version, level); + } + if(input == NULL) return NULL; + + ret = QRinput_append(input, QR_MODE_8, length, data); + if(ret < 0) { + QRinput_free(input); + return NULL; + } + code = QRcode_encodeInput(input); + QRinput_free(input); + + return code; +} + +QRcode *QRcode_encodeData(int size, const unsigned char *data, int version, QRecLevel level) +{ + return QRcode_encodeDataReal(data, size, version, level, 0); +} + +QRcode *QRcode_encodeString8bit(const char *string, int version, QRecLevel level) +{ + if(string == NULL) { + errno = EINVAL; + return NULL; + } + return QRcode_encodeDataReal((unsigned char *)string, (int)strlen(string), version, level, 0); +} + +QRcode *QRcode_encodeDataMQR(int size, const unsigned char *data, int version, QRecLevel level) +{ + int i; + + if(version == 0) { + version = 1; + } + for(i = version; i <= MQRSPEC_VERSION_MAX; i++) { + QRcode *code = QRcode_encodeDataReal(data, size, i, level, 1); + if(code != NULL) return code; + } + + return NULL; +} + +QRcode *QRcode_encodeString8bitMQR(const char *string, int version, QRecLevel level) +{ + int i; + + if(string == NULL) { + errno = EINVAL; + return NULL; + } + if(version == 0) { + version = 1; + } + for(i = version; i <= MQRSPEC_VERSION_MAX; i++) { + QRcode *code = QRcode_encodeDataReal((unsigned char *)string, (int)strlen(string), i, level, 1); + if(code != NULL) return code; + } + + return NULL; +} + + +/****************************************************************************** + * Structured QR-code encoding + *****************************************************************************/ + +static QRcode_List *QRcode_List_newEntry(void) +{ + QRcode_List *entry; + + entry = (QRcode_List *)malloc(sizeof(QRcode_List)); + if(entry == NULL) return NULL; + + entry->next = NULL; + entry->code = NULL; + + return entry; +} + +static void QRcode_List_freeEntry(QRcode_List *entry) +{ + if(entry != NULL) { + QRcode_free(entry->code); + free(entry); + } +} + +void QRcode_List_free(QRcode_List *qrlist) +{ + QRcode_List *list = qrlist, *next; + + while(list != NULL) { + next = list->next; + QRcode_List_freeEntry(list); + list = next; + } +} + +int QRcode_List_size(QRcode_List *qrlist) +{ + QRcode_List *list = qrlist; + int size = 0; + + while(list != NULL) { + size++; + list = list->next; + } + + return size; +} + +#if 0 +static unsigned char QRcode_parity(const char *str, int size) +{ + unsigned char parity = 0; + int i; + + for(i = 0; i < size; i++) { + parity ^= str[i]; + } + + return parity; +} +#endif + +QRcode_List *QRcode_encodeInputStructured(QRinput_Struct *s) +{ + QRcode_List *head = NULL; + QRcode_List *tail = NULL; + QRcode_List *entry; + QRinput_InputList *list = s->head; + + while(list != NULL) { + if(head == NULL) { + entry = QRcode_List_newEntry(); + if(entry == NULL) goto ABORT; + head = entry; + tail = head; + } else { + entry = QRcode_List_newEntry(); + if(entry == NULL) goto ABORT; + tail->next = entry; + tail = tail->next; + } + tail->code = QRcode_encodeInput(list->input); + if(tail->code == NULL) { + goto ABORT; + } + list = list->next; + } + + return head; +ABORT: + QRcode_List_free(head); + return NULL; +} + +static QRcode_List *QRcode_encodeInputToStructured(QRinput *input) +{ + QRinput_Struct *s; + QRcode_List *codes; + + s = QRinput_splitQRinputToStruct(input); + if(s == NULL) return NULL; + + codes = QRcode_encodeInputStructured(s); + QRinput_Struct_free(s); + + return codes; +} + +static QRcode_List *QRcode_encodeDataStructuredReal( + int size, const unsigned char *data, + int version, QRecLevel level, + int eightbit, QRencodeMode hint, int casesensitive) +{ + QRinput *input; + QRcode_List *codes; + int ret; + + if(version <= 0) { + errno = EINVAL; + return NULL; + } + if(!eightbit && (hint != QR_MODE_8 && hint != QR_MODE_KANJI)) { + errno = EINVAL; + return NULL; + } + + input = QRinput_new2(version, level); + if(input == NULL) return NULL; + + if(eightbit) { + ret = QRinput_append(input, QR_MODE_8, size, data); + } else { + ret = Split_splitStringToQRinput((char *)data, input, hint, casesensitive); + } + if(ret < 0) { + QRinput_free(input); + return NULL; + } + codes = QRcode_encodeInputToStructured(input); + QRinput_free(input); + + return codes; +} + +QRcode_List *QRcode_encodeDataStructured(int size, const unsigned char *data, int version, QRecLevel level) { + return QRcode_encodeDataStructuredReal(size, data, version, level, 1, QR_MODE_NUL, 0); +} + +QRcode_List *QRcode_encodeString8bitStructured(const char *string, int version, QRecLevel level) { + if(string == NULL) { + errno = EINVAL; + return NULL; + } + return QRcode_encodeDataStructured((int)strlen(string), (unsigned char *)string, version, level); +} + +QRcode_List *QRcode_encodeStringStructured(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive) +{ + if(string == NULL) { + errno = EINVAL; + return NULL; + } + return QRcode_encodeDataStructuredReal((int)strlen(string), (unsigned char *)string, version, level, 0, hint, casesensitive); +} + +/****************************************************************************** + * System utilities + *****************************************************************************/ + +void QRcode_APIVersion(int *major_version, int *minor_version, int *micro_version) +{ + if(major_version != NULL) { + *major_version = MAJOR_VERSION; + } + if(minor_version != NULL) { + *minor_version = MINOR_VERSION; + } + if(micro_version != NULL) { + *micro_version = MICRO_VERSION; + } +} + +char *QRcode_APIVersionString(void) +{ + return VERSION; +} + +void QRcode_clearCache(void) +{ + return; +} diff --git a/src/otsur/qrencode/qrencode.h b/src/otsur/qrencode/qrencode.h new file mode 100644 index 0000000000..1a934cc12c --- /dev/null +++ b/src/otsur/qrencode/qrencode.h @@ -0,0 +1,568 @@ +/** + * qrencode - QR Code encoder + * + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** \mainpage + * Libqrencode is a library for encoding data in a QR Code symbol, a kind of 2D + * symbology. + * + * \section encoding Encoding + * + * There are two methods to encode data: encoding a string/data or + * encoding a structured data. + * + * \subsection encoding-string Encoding a string/data + * You can encode a string by calling QRcode_encodeString(). + * The given string is parsed automatically and encoded. If you want to encode + * data that can be represented as a C string style (NUL terminated), you can + * simply use this way. + * + * If the input data contains Kanji (Shift-JIS) characters and you want to + * encode them as Kanji in QR Code, you should give QR_MODE_KANJI as a hint. + * Otherwise, all of non-alphanumeric characters are encoded as 8-bit data. + * If you want to encode a whole string in 8-bit mode, you can use + * QRcode_encodeString8bit() instead. + * + * Please note that a C string can not contain NUL characters. If your data + * contains NUL, you must use QRcode_encodeData(). + * + * \subsection encoding-input Encoding a structured data + * You can construct a structured input data manually. If the structure of the + * input data is known, you can use this method. + * At first, create a ::QRinput object by QRinput_new(). Then add input data + * to the QRinput object by QRinput_append(). Finally call QRcode_encodeInput() + * to encode the QRinput data. + * You can reuse the QRinput object again to encode it in other symbols with + * different parameters. + * + * \section result Result + * The encoded symbol is generated as a ::QRcode object. It will contain its + * version number, the width of the symbol, and an array represents the symbol. + * See ::QRcode for the details. You can free the object by QRcode_free(). + * + * Please note that the version of the result may be larger than specified. + * In such cases, the input data would be too large to be encoded in a + * symbol of the specified version. + * + * \section structured Structured append + * Libqrencode can generate "Structured-appended" symbols that enables to split + * a large data set into mulitple QR codes. A QR code reader concatenates + * multiple QR code symbols into a string. + * Just like QRcode_encodeString(), you can use QRcode_encodeStringStructured() + * to generate structured-appended symbols. This functions returns an instance + * of ::QRcode_List. The returned list is a singly-linked list of QRcode: you + * can retrieve each QR code in this way: + * + * \code + * QRcode_List *qrcodes; + * QRcode_List *entry; + * QRcode *qrcode; + * + * qrcodes = QRcode_encodeStringStructured(...); + * entry = qrcodes; + * while(entry != NULL) { + * qrcode = entry->code; + * // do something + * entry = entry->next; + * } + * QRcode_List_free(entry); + * \endcode + * + * Instead of using auto-parsing functions, you can construct your own + * structured input. At first, instantiate an object of ::QRinput_Struct + * by calling QRinput_Struct_new(). This object can hold multiple ::QRinput, + * and one QR code is generated for a ::QRinput. + * QRinput_Struct_appendInput() appends a ::QRinput to a ::QRinput_Struct + * object. In order to generate structured-appended symbols, it is required to + * embed headers to each symbol. You can use + * QRinput_Struct_insertStructuredAppendHeaders() to insert appropriate + * headers to each symbol. You should call this function just once before + * encoding symbols. + */ + +#ifndef QRENCODE_H +#define QRENCODE_H + +#if defined(__cplusplus) +extern "C" { +#endif + +/** + * Encoding mode. + */ +typedef enum { + QR_MODE_NUL = -1, ///< Terminator (NUL character). Internal use only + QR_MODE_NUM = 0, ///< Numeric mode + QR_MODE_AN, ///< Alphabet-numeric mode + QR_MODE_8, ///< 8-bit data mode + QR_MODE_KANJI, ///< Kanji (shift-jis) mode + QR_MODE_STRUCTURE, ///< Internal use only + QR_MODE_ECI, ///< ECI mode + QR_MODE_FNC1FIRST, ///< FNC1, first position + QR_MODE_FNC1SECOND, ///< FNC1, second position +} QRencodeMode; + +/** + * Level of error correction. + */ +typedef enum { + QR_ECLEVEL_L = 0, ///< lowest + QR_ECLEVEL_M, + QR_ECLEVEL_Q, + QR_ECLEVEL_H ///< highest +} QRecLevel; + +/** + * Maximum version (size) of QR-code symbol. + */ +#define QRSPEC_VERSION_MAX 40 + +/** + * Maximum version (size) of QR-code symbol. + */ +#define MQRSPEC_VERSION_MAX 4 + + +/****************************************************************************** + * Input data (qrinput.c) + *****************************************************************************/ + +/** + * Singly linked list to contain input strings. An instance of this class + * contains its version and error correction level too. It is required to + * set them by QRinput_setVersion() and QRinput_setErrorCorrectionLevel(), + * or use QRinput_new2() to instantiate an object. + */ +typedef struct _QRinput QRinput; + +/** + * Instantiate an input data object. The version is set to 0 (auto-select) + * and the error correction level is set to QR_ECLEVEL_L. + * @return an input object (initialized). On error, NULL is returned and errno + * is set to indicate the error. + * @throw ENOMEM unable to allocate memory. + */ +extern QRinput *QRinput_new(void); + +/** + * Instantiate an input data object. + * @param version version number. + * @param level Error correction level. + * @return an input object (initialized). On error, NULL is returned and errno + * is set to indicate the error. + * @throw ENOMEM unable to allocate memory for input objects. + * @throw EINVAL invalid arguments. + */ +extern QRinput *QRinput_new2(int version, QRecLevel level); + +/** + * Instantiate an input data object. Object's Micro QR Code flag is set. + * Unlike with full-sized QR Code, version number must be specified (>0). + * @param version version number (1--4). + * @param level Error correction level. + * @return an input object (initialized). On error, NULL is returned and errno + * is set to indicate the error. + * @throw ENOMEM unable to allocate memory for input objects. + * @throw EINVAL invalid arguments. + */ +extern QRinput *QRinput_newMQR(int version, QRecLevel level); + +/** + * Append data to an input object. + * The data is copied and appended to the input object. + * @param input input object. + * @param mode encoding mode. + * @param size size of data (byte). + * @param data a pointer to the memory area of the input data. + * @retval 0 success. + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + * @throw EINVAL input data is invalid. + * + */ +extern int QRinput_append(QRinput *input, QRencodeMode mode, int size, const unsigned char *data); + +/** + * Append ECI header. + * @param input input object. + * @param ecinum ECI indicator number (0 - 999999) + * @retval 0 success. + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + * @throw EINVAL input data is invalid. + * + */ +extern int QRinput_appendECIheader(QRinput *input, unsigned int ecinum); + +/** + * Get current version. + * @param input input object. + * @return current version. + */ +extern int QRinput_getVersion(QRinput *input); + +/** + * Set version of the QR code that is to be encoded. + * This function cannot be applied to Micro QR Code. + * @param input input object. + * @param version version number (0 = auto) + * @retval 0 success. + * @retval -1 invalid argument. + */ +extern int QRinput_setVersion(QRinput *input, int version); + +/** + * Get current error correction level. + * @param input input object. + * @return Current error correcntion level. + */ +extern QRecLevel QRinput_getErrorCorrectionLevel(QRinput *input); + +/** + * Set error correction level of the QR code that is to be encoded. + * This function cannot be applied to Micro QR Code. + * @param input input object. + * @param level Error correction level. + * @retval 0 success. + * @retval -1 invalid argument. + */ +extern int QRinput_setErrorCorrectionLevel(QRinput *input, QRecLevel level); + +/** + * Set version and error correction level of the QR code at once. + * This function is recommened for Micro QR Code. + * @param input input object. + * @param version version number (0 = auto) + * @param level Error correction level. + * @retval 0 success. + * @retval -1 invalid argument. + */ +extern int QRinput_setVersionAndErrorCorrectionLevel(QRinput *input, int version, QRecLevel level); + +/** + * Free the input object. + * All of data chunks in the input object are freed too. + * @param input input object. + */ +extern void QRinput_free(QRinput *input); + +/** + * Validate the input data. + * @param mode encoding mode. + * @param size size of data (byte). + * @param data a pointer to the memory area of the input data. + * @retval 0 success. + * @retval -1 invalid arguments. + */ +extern int QRinput_check(QRencodeMode mode, int size, const unsigned char *data); + +/** + * Set of QRinput for structured symbols. + */ +typedef struct _QRinput_Struct QRinput_Struct; + +/** + * Instantiate a set of input data object. + * @return an instance of QRinput_Struct. On error, NULL is returned and errno + * is set to indicate the error. + * @throw ENOMEM unable to allocate memory. + */ +extern QRinput_Struct *QRinput_Struct_new(void); + +/** + * Set parity of structured symbols. + * @param s structured input object. + * @param parity parity of s. + */ +extern void QRinput_Struct_setParity(QRinput_Struct *s, unsigned char parity); + +/** + * Append a QRinput object to the set. QRinput created by QRinput_newMQR() + * will be rejected. + * @warning never append the same QRinput object twice or more. + * @param s structured input object. + * @param input an input object. + * @retval >0 number of input objects in the structure. + * @retval -1 an error occurred. See Exceptions for the details. + * @throw ENOMEM unable to allocate memory. + * @throw EINVAL invalid arguments. + */ +extern int QRinput_Struct_appendInput(QRinput_Struct *s, QRinput *input); + +/** + * Free all of QRinput in the set. + * @param s a structured input object. + */ +extern void QRinput_Struct_free(QRinput_Struct *s); + +/** + * Split a QRinput to QRinput_Struct. It calculates a parity, set it, then + * insert structured-append headers. QRinput created by QRinput_newMQR() will + * be rejected. + * @param input input object. Version number and error correction level must be + * set. + * @return a set of input data. On error, NULL is returned, and errno is set + * to indicate the error. See Exceptions for the details. + * @throw ERANGE input data is too large. + * @throw EINVAL invalid input data. + * @throw ENOMEM unable to allocate memory. + */ +extern QRinput_Struct *QRinput_splitQRinputToStruct(QRinput *input); + +/** + * Insert structured-append headers to the input structure. It calculates + * a parity and set it if the parity is not set yet. + * @param s input structure + * @retval 0 success. + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw EINVAL invalid input object. + * @throw ENOMEM unable to allocate memory. + */ +extern int QRinput_Struct_insertStructuredAppendHeaders(QRinput_Struct *s); + +/** + * Set FNC1-1st position flag. + */ +extern int QRinput_setFNC1First(QRinput *input); + +/** + * Set FNC1-2nd position flag and application identifier. + */ +extern int QRinput_setFNC1Second(QRinput *input, unsigned char appid); + +/****************************************************************************** + * QRcode output (qrencode.c) + *****************************************************************************/ + +/** + * QRcode class. + * Symbol data is represented as an array contains width*width uchars. + * Each uchar represents a module (dot). If the less significant bit of + * the uchar is 1, the corresponding module is black. The other bits are + * meaningless for usual applications, but here its specification is described. + * + * @verbatim + MSB 76543210 LSB + |||||||`- 1=black/0=white + ||||||`-- 1=ecc/0=data code area + |||||`--- format information + ||||`---- version information + |||`----- timing pattern + ||`------ alignment pattern + |`------- finder pattern and separator + `-------- non-data modules (format, timing, etc.) + @endverbatim + */ +typedef struct { + int version; ///< version of the symbol + int width; ///< width of the symbol + unsigned char *data; ///< symbol data +} QRcode; + +/** + * Singly-linked list of QRcode. Used to represent a structured symbols. + * A list is terminated with NULL. + */ +typedef struct _QRcode_List { + QRcode *code; + struct _QRcode_List *next; +} QRcode_List; + +/** + * Create a symbol from the input data. + * @warning This function is THREAD UNSAFE when pthread is disabled. + * @param input input data. + * @return an instance of QRcode class. The version of the result QRcode may + * be larger than the designated version. On error, NULL is returned, + * and errno is set to indicate the error. See Exceptions for the + * details. + * @throw EINVAL invalid input object. + * @throw ENOMEM unable to allocate memory for input objects. + */ +extern QRcode *QRcode_encodeInput(QRinput *input); + +/** + * Create a symbol from the string. The library automatically parses the input + * string and encodes in a QR Code symbol. + * @warning This function is THREAD UNSAFE when pthread is disabled. + * @param string input string. It must be NUL terminated. + * @param version version of the symbol. If 0, the library chooses the minimum + * version for the given input data. + * @param level error correction level. + * @param hint tell the library how Japanese Kanji characters should be + * encoded. If QR_MODE_KANJI is given, the library assumes that the + * given string contains Shift-JIS characters and encodes them in + * Kanji-mode. If QR_MODE_8 is given, all of non-alphanumerical + * characters will be encoded as is. If you want to embed UTF-8 + * string, choose this. Other mode will cause EINVAL error. + * @param casesensitive case-sensitive(1) or not(0). + * @return an instance of QRcode class. The version of the result QRcode may + * be larger than the designated version. On error, NULL is returned, + * and errno is set to indicate the error. See Exceptions for the + * details. + * @throw EINVAL invalid input object. + * @throw ENOMEM unable to allocate memory for input objects. + * @throw ERANGE input data is too large. + */ +extern QRcode *QRcode_encodeString(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive); + +/** + * Same to QRcode_encodeString(), but encode whole data in 8-bit mode. + * @warning This function is THREAD UNSAFE when pthread is disabled. + */ +extern QRcode *QRcode_encodeString8bit(const char *string, int version, QRecLevel level); + +/** + * Micro QR Code version of QRcode_encodeString(). + * @warning This function is THREAD UNSAFE when pthread is disabled. + */ +extern QRcode *QRcode_encodeStringMQR(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive); + +/** + * Micro QR Code version of QRcode_encodeString8bit(). + * @warning This function is THREAD UNSAFE when pthread is disabled. + */ +extern QRcode *QRcode_encodeString8bitMQR(const char *string, int version, QRecLevel level); + +/** + * Encode byte stream (may include '\0') in 8-bit mode. + * @warning This function is THREAD UNSAFE when pthread is disabled. + * @param size size of the input data. + * @param data input data. + * @param version version of the symbol. If 0, the library chooses the minimum + * version for the given input data. + * @param level error correction level. + * @throw EINVAL invalid input object. + * @throw ENOMEM unable to allocate memory for input objects. + * @throw ERANGE input data is too large. + */ +extern QRcode *QRcode_encodeData(int size, const unsigned char *data, int version, QRecLevel level); + +/** + * Micro QR Code version of QRcode_encodeData(). + * @warning This function is THREAD UNSAFE when pthread is disabled. + */ +extern QRcode *QRcode_encodeDataMQR(int size, const unsigned char *data, int version, QRecLevel level); + +/** + * Free the instance of QRcode class. + * @param qrcode an instance of QRcode class. + */ +extern void QRcode_free(QRcode *qrcode); + +/** + * Create structured symbols from the input data. + * @warning This function is THREAD UNSAFE when pthread is disabled. + * @param s input data, structured. + * @return a singly-linked list of QRcode. + */ +extern QRcode_List *QRcode_encodeInputStructured(QRinput_Struct *s); + +/** + * Create structured symbols from the string. The library automatically parses + * the input string and encodes in a QR Code symbol. + * @warning This function is THREAD UNSAFE when pthread is disabled. + * @param string input string. It must be NUL terminated. + * @param version version of the symbol. + * @param level error correction level. + * @param hint tell the library how Japanese Kanji characters should be + * encoded. If QR_MODE_KANJI is given, the library assumes that the + * given string contains Shift-JIS characters and encodes them in + * Kanji-mode. If QR_MODE_8 is given, all of non-alphanumerical + * characters will be encoded as is. If you want to embed UTF-8 + * string, choose this. Other mode will cause EINVAL error. + * @param casesensitive case-sensitive(1) or not(0). + * @return a singly-linked list of QRcode. On error, NULL is returned, and + * errno is set to indicate the error. See Exceptions for the details. + * @throw EINVAL invalid input object. + * @throw ENOMEM unable to allocate memory for input objects. + */ +extern QRcode_List *QRcode_encodeStringStructured(const char *string, int version, QRecLevel level, QRencodeMode hint, int casesensitive); + +/** + * Same to QRcode_encodeStringStructured(), but encode whole data in 8-bit mode. + * @warning This function is THREAD UNSAFE when pthread is disabled. + */ +extern QRcode_List *QRcode_encodeString8bitStructured(const char *string, int version, QRecLevel level); + +/** + * Create structured symbols from byte stream (may include '\0'). Wholde data + * are encoded in 8-bit mode. + * @warning This function is THREAD UNSAFE when pthread is disabled. + * @param size size of the input data. + * @param data input dat. + * @param version version of the symbol. + * @param level error correction level. + * @return a singly-linked list of QRcode. On error, NULL is returned, and + * errno is set to indicate the error. See Exceptions for the details. + * @throw EINVAL invalid input object. + * @throw ENOMEM unable to allocate memory for input objects. + */ +extern QRcode_List *QRcode_encodeDataStructured(int size, const unsigned char *data, int version, QRecLevel level); + +/** + * Return the number of symbols included in a QRcode_List. + * @param qrlist a head entry of a QRcode_List. + * @return number of symbols in the list. + */ +extern int QRcode_List_size(QRcode_List *qrlist); + +/** + * Free the QRcode_List. + * @param qrlist a head entry of a QRcode_List. + */ +extern void QRcode_List_free(QRcode_List *qrlist); + + +/****************************************************************************** + * System utilities + *****************************************************************************/ + +/** + * Return a string that identifies the library version. + * @param major_version major version number + * @param minor_version minor version number + * @param micro_version micro version number + */ +extern void QRcode_APIVersion(int *major_version, int *minor_version, int *micro_version); + +/** + * Return a string that identifies the library version. + * @return a string identifies the library version. The string is held by the + * library. Do NOT free it. + */ +extern char *QRcode_APIVersionString(void); + +/** + * @deprecated + */ +#ifndef _MSC_VER +extern void QRcode_clearCache(void) __attribute__ ((deprecated)); +#else +extern void QRcode_clearCache(void); +#endif + +#if defined(__cplusplus) +} +#endif + +#endif /* QRENCODE_H */ diff --git a/src/otsur/qrencode/qrencode_inner.h b/src/otsur/qrencode/qrencode_inner.h new file mode 100644 index 0000000000..2ea54f59d0 --- /dev/null +++ b/src/otsur/qrencode/qrencode_inner.h @@ -0,0 +1,88 @@ +/** + * qrencode - QR Code encoder + * + * Header for test use + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef QRENCODE_INNER_H +#define QRENCODE_INNER_H + +/** + * This header file includes definitions for test use. + */ + +/****************************************************************************** + * Raw code + *****************************************************************************/ + +typedef struct { + int dataLength; + int eccLength; + unsigned char *data; + unsigned char *ecc; +} RSblock; + +typedef struct { + int version; + int dataLength; + int eccLength; + unsigned char *datacode; + unsigned char *ecccode; + int b1; + int blocks; + RSblock *rsblock; + int count; +} QRRawCode; + +extern QRRawCode *QRraw_new(QRinput *input); +extern unsigned char QRraw_getCode(QRRawCode *raw); +extern void QRraw_free(QRRawCode *raw); + +/****************************************************************************** + * Raw code for Micro QR Code + *****************************************************************************/ + +typedef struct { + int version; + int dataLength; + int eccLength; + unsigned char *datacode; + unsigned char *ecccode; + RSblock *rsblock; + int oddbits; + int count; +} MQRRawCode; + +extern MQRRawCode *MQRraw_new(QRinput *input); +extern unsigned char MQRraw_getCode(MQRRawCode *raw); +extern void MQRraw_free(MQRRawCode *raw); + +/****************************************************************************** + * Frame filling + *****************************************************************************/ +extern unsigned char *FrameFiller_test(int version); +extern unsigned char *FrameFiller_testMQR(int version); + +/****************************************************************************** + * QR-code encoding + *****************************************************************************/ +extern QRcode *QRcode_encodeMask(QRinput *input, int mask); +extern QRcode *QRcode_encodeMaskMQR(QRinput *input, int mask); +extern QRcode *QRcode_new(int version, int width, unsigned char *data); + +#endif /* QRENCODE_INNER_H */ diff --git a/src/otsur/qrencode/qrinput.c b/src/otsur/qrencode/qrinput.c new file mode 100644 index 0000000000..34bedc2a0c --- /dev/null +++ b/src/otsur/qrencode/qrinput.c @@ -0,0 +1,1639 @@ +/* + * qrencode - QR Code encoder + * + * Input data chunk class + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include + +#include "qrencode.h" +#include "qrspec.h" +#include "mqrspec.h" +#include "bitstream.h" +#include "qrinput.h" + +/****************************************************************************** + * Utilities + *****************************************************************************/ +int QRinput_isSplittableMode(QRencodeMode mode) +{ + return (mode >= QR_MODE_NUM && mode <= QR_MODE_KANJI); +} + +/****************************************************************************** + * Entry of input data + *****************************************************************************/ + +static QRinput_List *QRinput_List_newEntry(QRencodeMode mode, int size, const unsigned char *data) +{ + QRinput_List *entry; + + if(QRinput_check(mode, size, data)) { + errno = EINVAL; + return NULL; + } + + entry = (QRinput_List *)malloc(sizeof(QRinput_List)); + if(entry == NULL) return NULL; + + entry->mode = mode; + entry->size = size; + entry->data = NULL; + if(size > 0) { + entry->data = (unsigned char *)malloc((size_t)size); + if(entry->data == NULL) { + free(entry); + return NULL; + } + memcpy(entry->data, data, (size_t)size); + } + entry->bstream = NULL; + entry->next = NULL; + + return entry; +} + +static void QRinput_List_freeEntry(QRinput_List *entry) +{ + if(entry != NULL) { + free(entry->data); + BitStream_free(entry->bstream); + free(entry); + } +} + +static QRinput_List *QRinput_List_dup(QRinput_List *entry) +{ + QRinput_List *n; + + n = (QRinput_List *)malloc(sizeof(QRinput_List)); + if(n == NULL) return NULL; + + n->mode = entry->mode; + n->size = entry->size; + n->data = (unsigned char *)malloc((size_t)n->size); + if(n->data == NULL) { + free(n); + return NULL; + } + memcpy(n->data, entry->data, (size_t)entry->size); + n->bstream = NULL; + n->next = NULL; + + return n; +} + +/****************************************************************************** + * Input Data + *****************************************************************************/ + +QRinput *QRinput_new(void) +{ + return QRinput_new2(0, QR_ECLEVEL_L); +} + +QRinput *QRinput_new2(int version, QRecLevel level) +{ + QRinput *input; + + if(version < 0 || version > QRSPEC_VERSION_MAX || level < 0 || level > QR_ECLEVEL_H) { + errno = EINVAL; + return NULL; + } + + input = (QRinput *)malloc(sizeof(QRinput)); + if(input == NULL) return NULL; + + input->head = NULL; + input->tail = NULL; + input->version = version; + input->level = level; + input->mqr = 0; + input->fnc1 = 0; + + return input; +} + +QRinput *QRinput_newMQR(int version, QRecLevel level) +{ + QRinput *input; + + if(version <= 0 || version > MQRSPEC_VERSION_MAX) goto INVALID; + if((MQRspec_getECCLength(version, level) == 0)) goto INVALID; + + input = QRinput_new2(version, level); + if(input == NULL) return NULL; + + input->mqr = 1; + + return input; + +INVALID: + errno = EINVAL; + return NULL; +} + +int QRinput_getVersion(QRinput *input) +{ + return input->version; +} + +int QRinput_setVersion(QRinput *input, int version) +{ + if(input->mqr || version < 0 || version > QRSPEC_VERSION_MAX) { + errno = EINVAL; + return -1; + } + + input->version = version; + + return 0; +} + +QRecLevel QRinput_getErrorCorrectionLevel(QRinput *input) +{ + return input->level; +} + +int QRinput_setErrorCorrectionLevel(QRinput *input, QRecLevel level) +{ + if(input->mqr || level > QR_ECLEVEL_H) { + errno = EINVAL; + return -1; + } + + input->level = level; + + return 0; +} + +int QRinput_setVersionAndErrorCorrectionLevel(QRinput *input, int version, QRecLevel level) +{ + if(input->mqr) { + if(version <= 0 || version > MQRSPEC_VERSION_MAX) goto INVALID; + if((MQRspec_getECCLength(version, level) == 0)) goto INVALID; + } else { + if(version < 0 || version > QRSPEC_VERSION_MAX) goto INVALID; + if(level > QR_ECLEVEL_H) goto INVALID; + } + + input->version = version; + input->level = level; + + return 0; + +INVALID: + errno = EINVAL; + return -1; +} + +static void QRinput_appendEntry(QRinput *input, QRinput_List *entry) +{ + if(input->tail == NULL) { + input->head = entry; + input->tail = entry; + } else { + input->tail->next = entry; + input->tail = entry; + } + entry->next = NULL; +} + +int QRinput_append(QRinput *input, QRencodeMode mode, int size, const unsigned char *data) +{ + QRinput_List *entry; + + entry = QRinput_List_newEntry(mode, size, data); + if(entry == NULL) { + return -1; + } + + QRinput_appendEntry(input, entry); + + return 0; +} + +/** + * Insert a structured-append header to the head of the input data. + * @param input input data. + * @param size number of structured symbols. + * @param number index number of the symbol. (1 <= number <= size) + * @param parity parity among input data. (NOTE: each symbol of a set of structured symbols has the same parity data) + * @retval 0 success. + * @retval -1 error occurred and errno is set to indeicate the error. See Execptions for the details. + * @throw EINVAL invalid parameter. + * @throw ENOMEM unable to allocate memory. + */ +STATIC_IN_RELEASE int QRinput_insertStructuredAppendHeader(QRinput *input, int size, int number, unsigned char parity) +{ + QRinput_List *entry; + unsigned char buf[3]; + + if(size > MAX_STRUCTURED_SYMBOLS) { + errno = EINVAL; + return -1; + } + if(number <= 0 || number > size) { + errno = EINVAL; + return -1; + } + + buf[0] = (unsigned char)size; + buf[1] = (unsigned char)number; + buf[2] = parity; + entry = QRinput_List_newEntry(QR_MODE_STRUCTURE, 3, buf); + if(entry == NULL) { + return -1; + } + + entry->next = input->head; + input->head = entry; + + return 0; +} + +int QRinput_appendECIheader(QRinput *input, unsigned int ecinum) +{ + unsigned char data[4]; + + if(ecinum > 999999) { + errno = EINVAL; + return -1; + } + + /* We manually create byte array of ecinum because + (unsigned char *)&ecinum may cause bus error on some architectures, */ + data[0] = ecinum & 0xff; + data[1] = (ecinum >> 8) & 0xff; + data[2] = (ecinum >> 16) & 0xff; + data[3] = (ecinum >> 24) & 0xff; + return QRinput_append(input, QR_MODE_ECI, 4, data); +} + +void QRinput_free(QRinput *input) +{ + QRinput_List *list, *next; + + if(input != NULL) { + list = input->head; + while(list != NULL) { + next = list->next; + QRinput_List_freeEntry(list); + list = next; + } + free(input); + } +} + +static unsigned char QRinput_calcParity(QRinput *input) +{ + unsigned char parity = 0; + QRinput_List *list; + int i; + + list = input->head; + while(list != NULL) { + if(list->mode != QR_MODE_STRUCTURE) { + for(i = list->size-1; i >= 0; i--) { + parity ^= list->data[i]; + } + } + list = list->next; + } + + return parity; +} + +QRinput *QRinput_dup(QRinput *input) +{ + QRinput *n; + QRinput_List *list, *e; + + if(input->mqr) { + n = QRinput_newMQR(input->version, input->level); + } else { + n = QRinput_new2(input->version, input->level); + } + if(n == NULL) return NULL; + + list = input->head; + while(list != NULL) { + e = QRinput_List_dup(list); + if(e == NULL) { + QRinput_free(n); + return NULL; + } + QRinput_appendEntry(n, e); + list = list->next; + } + + return n; +} + +/****************************************************************************** + * Numeric data + *****************************************************************************/ + +/** + * Check the input data. + * @param size + * @param data + * @return result + */ +static int QRinput_checkModeNum(int size, const char *data) +{ + int i; + + for(i = 0; i < size; i++) { + if(data[i] < '0' || data[i] > '9') + return -1; + } + + return 0; +} + +/** + * Estimate the length of the encoded bit stream of numeric data. + * @param size + * @return number of bits + */ +int QRinput_estimateBitsModeNum(int size) +{ + int w; + int bits; + + w = size / 3; + bits = w * 10; + switch(size - w * 3) { + case 1: + bits += 4; + break; + case 2: + bits += 7; + break; + default: + break; + } + + return bits; +} + +/** + * Convert the number data and append to a bit stream. + * @param entry + * @param mqr + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + */ +static int QRinput_encodeModeNum(QRinput_List *entry, BitStream *bstream, int version, int mqr) +{ + int words, i, ret; + unsigned int val; + + if(mqr) { + if(version > 1) { + ret = BitStream_appendNum(bstream, (size_t)(version - 1), MQRSPEC_MODEID_NUM); + if(ret < 0) return -1; + } + ret = BitStream_appendNum(bstream, (size_t)MQRspec_lengthIndicator(QR_MODE_NUM, version), (unsigned int)entry->size); + if(ret < 0) return -1; + } else { + ret = BitStream_appendNum(bstream, 4, QRSPEC_MODEID_NUM); + if(ret < 0) return -1; + + ret = BitStream_appendNum(bstream, (size_t)QRspec_lengthIndicator(QR_MODE_NUM, version), (unsigned int)entry->size); + if(ret < 0) return -1; + } + + words = entry->size / 3; + for(i = 0; i < words; i++) { + val = (unsigned int)(entry->data[i*3 ] - '0') * 100; + val += (unsigned int)(entry->data[i*3+1] - '0') * 10; + val += (unsigned int)(entry->data[i*3+2] - '0'); + + ret = BitStream_appendNum(bstream, 10, val); + if(ret < 0) return -1; + } + + if(entry->size - words * 3 == 1) { + val = (unsigned int)(entry->data[words*3] - '0'); + ret = BitStream_appendNum(bstream, 4, val); + if(ret < 0) return -1; + } else if(entry->size - words * 3 == 2) { + val = (unsigned int)(entry->data[words*3 ] - '0') * 10; + val += (unsigned int)(entry->data[words*3+1] - '0'); + ret = BitStream_appendNum(bstream, 7, val); + if(ret < 0) return -1; + } + + return 0; +} + +/****************************************************************************** + * Alphabet-numeric data + *****************************************************************************/ + +const signed char QRinput_anTable[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 36, -1, -1, -1, 37, 38, -1, -1, -1, -1, 39, 40, -1, 41, 42, 43, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 44, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 +}; + +/** + * Check the input data. + * @param size + * @param data + * @return result + */ +static int QRinput_checkModeAn(int size, const char *data) +{ + int i; + + for(i = 0; i < size; i++) { + if(QRinput_lookAnTable(data[i]) < 0) + return -1; + } + + return 0; +} + +/** + * Estimate the length of the encoded bit stream of alphabet-numeric data. + * @param size + * @return number of bits + */ +int QRinput_estimateBitsModeAn(int size) +{ + int w; + int bits; + + w = size / 2; + bits = w * 11; + if(size & 1) { + bits += 6; + } + + return bits; +} + +/** + * Convert the alphabet-numeric data and append to a bit stream. + * @param entry + * @param mqr + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + * @throw EINVAL invalid version. + */ +static int QRinput_encodeModeAn(QRinput_List *entry, BitStream *bstream, int version, int mqr) +{ + int words, i, ret; + unsigned int val; + + if(mqr) { + if(version < 2) { + errno = ERANGE; + return -1; + } + ret = BitStream_appendNum(bstream, (size_t)(version - 1), MQRSPEC_MODEID_AN); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, (size_t)MQRspec_lengthIndicator(QR_MODE_AN, version), (unsigned int)entry->size); + if(ret < 0) return -1; + } else { + ret = BitStream_appendNum(bstream, 4, QRSPEC_MODEID_AN); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, (size_t)QRspec_lengthIndicator(QR_MODE_AN, version), (unsigned int)entry->size); + if(ret < 0) return -1; + } + + words = entry->size / 2; + for(i = 0; i < words; i++) { + val = (unsigned int)QRinput_lookAnTable(entry->data[i*2 ]) * 45; + val += (unsigned int)QRinput_lookAnTable(entry->data[i*2+1]); + + ret = BitStream_appendNum(bstream, 11, val); + if(ret < 0) return -1; + } + + if(entry->size & 1) { + val = (unsigned int)QRinput_lookAnTable(entry->data[words * 2]); + + ret = BitStream_appendNum(bstream, 6, val); + if(ret < 0) return -1; + } + + return 0; +} + +/****************************************************************************** + * 8 bit data + *****************************************************************************/ + +/** + * Estimate the length of the encoded bit stream of 8 bit data. + * @param size + * @return number of bits + */ +int QRinput_estimateBitsMode8(int size) +{ + return size * 8; +} + +/** + * Convert the 8bits data and append to a bit stream. + * @param entry + * @param mqr + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + */ +static int QRinput_encodeMode8(QRinput_List *entry, BitStream *bstream, int version, int mqr) +{ + int ret; + + if(mqr) { + if(version < 3) { + errno = ERANGE; + return -1; + } + ret = BitStream_appendNum(bstream, (size_t)(version - 1), MQRSPEC_MODEID_8); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, (size_t)MQRspec_lengthIndicator(QR_MODE_8, version), (unsigned int)entry->size); + if(ret < 0) return -1; + } else { + ret = BitStream_appendNum(bstream, 4, QRSPEC_MODEID_8); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, (size_t)QRspec_lengthIndicator(QR_MODE_8, version), (unsigned int)entry->size); + if(ret < 0) return -1; + } + + ret = BitStream_appendBytes(bstream, (size_t)entry->size, entry->data); + if(ret < 0) return -1; + + return 0; +} + + +/****************************************************************************** + * Kanji data + *****************************************************************************/ + +/** + * Estimate the length of the encoded bit stream of kanji data. + * @param size + * @return number of bits + */ +int QRinput_estimateBitsModeKanji(int size) +{ + return (size / 2) * 13; +} + +/** + * Check the input data. + * @param size + * @param data + * @return result + */ +static int QRinput_checkModeKanji(int size, const unsigned char *data) +{ + int i; + unsigned int val; + + if(size & 1) + return -1; + + for(i = 0; i < size; i+=2) { + val = ((unsigned int)data[i] << 8) | data[i+1]; + if(val < 0x8140 || (val > 0x9ffc && val < 0xe040) || val > 0xebbf) { + return -1; + } + } + + return 0; +} + +/** + * Convert the kanji data and append to a bit stream. + * @param entry + * @param mqr + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + * @throw EINVAL invalid version. + */ +static int QRinput_encodeModeKanji(QRinput_List *entry, BitStream *bstream, int version, int mqr) +{ + int ret, i; + unsigned int val, h; + + if(mqr) { + if(version < 2) { + errno = ERANGE; + return -1; + } + ret = BitStream_appendNum(bstream, (size_t)(version - 1), MQRSPEC_MODEID_KANJI); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, (size_t)MQRspec_lengthIndicator(QR_MODE_KANJI, version), (unsigned int)entry->size/2); + if(ret < 0) return -1; + } else { + ret = BitStream_appendNum(bstream, 4, QRSPEC_MODEID_KANJI); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, (size_t)QRspec_lengthIndicator(QR_MODE_KANJI, version), (unsigned int)entry->size/2); + if(ret < 0) return -1; + } + + for(i = 0; i < entry->size; i+=2) { + val = ((unsigned int)entry->data[i] << 8) | entry->data[i+1]; + if(val <= 0x9ffc) { + val -= 0x8140; + } else { + val -= 0xc140; + } + h = (val >> 8) * 0xc0; + val = (val & 0xff) + h; + + ret = BitStream_appendNum(bstream, 13, val); + if(ret < 0) return -1; + } + + return 0; +} + +/****************************************************************************** + * Structured Symbol + *****************************************************************************/ + +/** + * Convert a structure symbol code and append to a bit stream. + * @param entry + * @param mqr + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + * @throw EINVAL invalid entry. + */ +static int QRinput_encodeModeStructure(QRinput_List *entry, BitStream *bstream, int mqr) +{ + int ret; + + if(mqr) { + errno = EINVAL; + return -1; + } + + ret = BitStream_appendNum(bstream, 4, QRSPEC_MODEID_STRUCTURE); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, 4, entry->data[1] - 1U); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, 4, entry->data[0] - 1U); + if(ret < 0) return -1; + ret = BitStream_appendNum(bstream, 8, entry->data[2]); + if(ret < 0) return -1; + + return 0; +} + +/****************************************************************************** + * FNC1 + *****************************************************************************/ + +static int QRinput_checkModeFNC1Second(int size) +{ + if(size != 1) return -1; + + /* No data check required. */ + + return 0; +} + +static int QRinput_encodeModeFNC1Second(QRinput_List *entry, BitStream *bstream) +{ + int ret; + + ret = BitStream_appendNum(bstream, 4, QRSPEC_MODEID_FNC1SECOND); + if(ret < 0) return -1; + + ret = BitStream_appendBytes(bstream, 1, entry->data); + if(ret < 0) return -1; + + return 0; +} + +/****************************************************************************** + * ECI header + *****************************************************************************/ +static unsigned int QRinput_decodeECIfromByteArray(unsigned char *data) +{ + int i; + unsigned int ecinum; + + ecinum = 0; + for(i = 0; i < 4; i++) { + ecinum = ecinum << 8; + ecinum |= data[3-i]; + } + + return ecinum; +} + +static int QRinput_estimateBitsModeECI(unsigned char *data) +{ + unsigned int ecinum; + + ecinum = QRinput_decodeECIfromByteArray(data); + + /* See Table 4 of JISX 0510:2004 pp.17. */ + if(ecinum < 128) { + return MODE_INDICATOR_SIZE + 8; + } else if(ecinum < 16384) { + return MODE_INDICATOR_SIZE + 16; + } else { + return MODE_INDICATOR_SIZE + 24; + } +} + +static int QRinput_encodeModeECI(QRinput_List *entry, BitStream *bstream) +{ + int ret, words; + unsigned int ecinum, code; + + ecinum = QRinput_decodeECIfromByteArray(entry->data); + + /* See Table 4 of JISX 0510:2004 pp.17. */ + if(ecinum < 128) { + words = 1; + code = ecinum; + } else if(ecinum < 16384) { + words = 2; + code = 0x8000 + ecinum; + } else { + words = 3; + code = 0xc0000 + ecinum; + } + + ret = BitStream_appendNum(bstream, 4, QRSPEC_MODEID_ECI); + if(ret < 0) return -1; + + ret = BitStream_appendNum(bstream, (size_t)words * 8, code); + if(ret < 0) return -1; + + return 0; +} + +/****************************************************************************** + * Validation + *****************************************************************************/ + +int QRinput_check(QRencodeMode mode, int size, const unsigned char *data) +{ + if((mode == QR_MODE_FNC1FIRST && size < 0) || size <= 0) return -1; + + switch(mode) { + case QR_MODE_NUM: + return QRinput_checkModeNum(size, (const char *)data); + case QR_MODE_AN: + return QRinput_checkModeAn(size, (const char *)data); + case QR_MODE_KANJI: + return QRinput_checkModeKanji(size, data); + case QR_MODE_8: + return 0; + case QR_MODE_STRUCTURE: + return 0; + case QR_MODE_ECI: + return 0; + case QR_MODE_FNC1FIRST: + return 0; + case QR_MODE_FNC1SECOND: + return QRinput_checkModeFNC1Second(size); + case QR_MODE_NUL: + break; + } + + return -1; +} + +/****************************************************************************** + * Estimation of the bit length + *****************************************************************************/ + +/** + * Estimate the length of the encoded bit stream on the current version. + * @param entry + * @param version version of the symbol + * @param mqr + * @return number of bits + */ +static int QRinput_estimateBitStreamSizeOfEntry(QRinput_List *entry, int version, int mqr) +{ + int bits = 0; + int l, m; + int num; + + if(version == 0) version = 1; + + switch(entry->mode) { + case QR_MODE_NUM: + bits = QRinput_estimateBitsModeNum(entry->size); + break; + case QR_MODE_AN: + bits = QRinput_estimateBitsModeAn(entry->size); + break; + case QR_MODE_8: + bits = QRinput_estimateBitsMode8(entry->size); + break; + case QR_MODE_KANJI: + bits = QRinput_estimateBitsModeKanji(entry->size); + break; + case QR_MODE_STRUCTURE: + return STRUCTURE_HEADER_SIZE; + case QR_MODE_ECI: + bits = QRinput_estimateBitsModeECI(entry->data); + break; + case QR_MODE_FNC1FIRST: + return MODE_INDICATOR_SIZE; + case QR_MODE_FNC1SECOND: + return MODE_INDICATOR_SIZE + 8; + default: + return 0; + } + + if(mqr) { + l = MQRspec_lengthIndicator(entry->mode, version); + m = version - 1; + bits += l + m; + } else { + l = QRspec_lengthIndicator(entry->mode, version); + m = 1 << l; + if(entry->mode == QR_MODE_KANJI) { + num = (entry->size/2 + m - 1) / m; + } else { + num = (entry->size + m - 1) / m; + } + + bits += num * (MODE_INDICATOR_SIZE + l); + } + + return bits; +} + +/** + * Estimate the length of the encoded bit stream of the data. + * @param input input data + * @param version version of the symbol + * @return number of bits + */ +STATIC_IN_RELEASE int QRinput_estimateBitStreamSize(QRinput *input, int version) +{ + QRinput_List *list; + int bits = 0; + + list = input->head; + while(list != NULL) { + bits += QRinput_estimateBitStreamSizeOfEntry(list, version, input->mqr); + list = list->next; + } + + return bits; +} + +/** + * Estimate the required version number of the symbol. + * @param input input data + * @return required version number or -1 for failure. + */ +STATIC_IN_RELEASE int QRinput_estimateVersion(QRinput *input) +{ + int bits; + int version, prev; + + version = 0; + do { + prev = version; + bits = QRinput_estimateBitStreamSize(input, prev); + version = QRspec_getMinimumVersion((bits + 7) / 8, input->level); + if(prev == 0 && version > 1) { + version--; + } + } while (version > prev); + + return version; +} + +/** + * Return required length in bytes for specified mode, version and bits. + * @param mode + * @param version + * @param bits + * @return required length of code words in bytes. + */ +STATIC_IN_RELEASE int QRinput_lengthOfCode(QRencodeMode mode, int version, int bits) +{ + int payload, size, chunks, remain, maxsize; + + payload = bits - 4 - QRspec_lengthIndicator(mode, version); + switch(mode) { + case QR_MODE_NUM: + chunks = payload / 10; + remain = payload - chunks * 10; + size = chunks * 3; + if(remain >= 7) { + size += 2; + } else if(remain >= 4) { + size += 1; + } + break; + case QR_MODE_AN: + chunks = payload / 11; + remain = payload - chunks * 11; + size = chunks * 2; + if(remain >= 6) size++; + break; + case QR_MODE_8: + size = payload / 8; + break; + case QR_MODE_KANJI: + size = (payload / 13) * 2; + break; + case QR_MODE_STRUCTURE: + size = payload / 8; + break; + default: + size = 0; + break; + } + maxsize = QRspec_maximumWords(mode, version); + if(size < 0) size = 0; + if(maxsize > 0 && size > maxsize) size = maxsize; + + return size; +} + +/****************************************************************************** + * Data conversion + *****************************************************************************/ + +/** + * Convert the input data in the data chunk and append to a bit stream. + * @param entry + * @param bstream + * @return number of bits (>0) or -1 for failure. + */ +static int QRinput_encodeBitStream(QRinput_List *entry, BitStream *bstream, int version, int mqr) +{ + int words, ret; + QRinput_List *st1 = NULL, *st2 = NULL; + int prevsize; + + prevsize = (int)BitStream_size(bstream); + + if(mqr) { + words = MQRspec_maximumWords(entry->mode, version); + } else { + words = QRspec_maximumWords(entry->mode, version); + } + if(words != 0 && entry->size > words) { + st1 = QRinput_List_newEntry(entry->mode, words, entry->data); + if(st1 == NULL) goto ABORT; + st2 = QRinput_List_newEntry(entry->mode, entry->size - words, &entry->data[words]); + if(st2 == NULL) goto ABORT; + + ret = QRinput_encodeBitStream(st1, bstream, version, mqr); + if(ret < 0) goto ABORT; + ret = QRinput_encodeBitStream(st2, bstream, version, mqr); + if(ret < 0) goto ABORT; + + QRinput_List_freeEntry(st1); + QRinput_List_freeEntry(st2); + } else { + ret = 0; + switch(entry->mode) { + case QR_MODE_NUM: + ret = QRinput_encodeModeNum(entry, bstream, version, mqr); + break; + case QR_MODE_AN: + ret = QRinput_encodeModeAn(entry, bstream, version, mqr); + break; + case QR_MODE_8: + ret = QRinput_encodeMode8(entry, bstream, version, mqr); + break; + case QR_MODE_KANJI: + ret = QRinput_encodeModeKanji(entry, bstream, version, mqr); + break; + case QR_MODE_STRUCTURE: + ret = QRinput_encodeModeStructure(entry, bstream, mqr); + break; + case QR_MODE_ECI: + ret = QRinput_encodeModeECI(entry, bstream); + break; + case QR_MODE_FNC1SECOND: + ret = QRinput_encodeModeFNC1Second(entry, bstream); + break; + default: + break; + } + if(ret < 0) return -1; + } + + return (int)BitStream_size(bstream) - prevsize; +ABORT: + QRinput_List_freeEntry(st1); + QRinput_List_freeEntry(st2); + return -1; +} + +/** + * Convert the input data to a bit stream. + * @param input input data. + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + */ +static int QRinput_createBitStream(QRinput *input, BitStream *bstream) +{ + QRinput_List *list; + int bits, total = 0; + + list = input->head; + while(list != NULL) { + bits = QRinput_encodeBitStream(list, bstream, input->version, input->mqr); + if(bits < 0) return -1; + total += bits; + list = list->next; + } + + return total; +} + +/** + * Convert the input data to a bit stream. + * When the version number is given and that is not sufficient, it is increased + * automatically. + * @param input input data. + * @param bstream where the converted data is stored. + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ENOMEM unable to allocate memory. + * @throw ERANGE input data is too large. + */ +static int QRinput_convertData(QRinput *input, BitStream *bstream) +{ + int bits; + int ver; + + ver = QRinput_estimateVersion(input); + if(ver > QRinput_getVersion(input)) { + QRinput_setVersion(input, ver); + } + + for(;;) { + BitStream_reset(bstream); + bits = QRinput_createBitStream(input, bstream); + if(bits < 0) return -1; + ver = QRspec_getMinimumVersion((bits + 7) / 8, input->level); + if(ver > QRinput_getVersion(input)) { + QRinput_setVersion(input, ver); + } else { + break; + } + } + + return 0; +} + +/** + * Append padding bits for the input data. + * @param bstream Bitstream to be appended. + * @param input input data. + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ERANGE input data is too large. + * @throw ENOMEM unable to allocate memory. + */ +static int QRinput_appendPaddingBit(BitStream *bstream, QRinput *input) +{ + int bits, maxbits, words, maxwords, i, ret; + int padlen; + + bits = (int)BitStream_size(bstream); + maxwords = QRspec_getDataLength(input->version, input->level); + maxbits = maxwords * 8; + + if(maxbits < bits) { + errno = ERANGE; + return -1; + } + if(maxbits == bits) { + return 0; + } + + if(maxbits - bits <= 4) { + return (int)BitStream_appendNum(bstream, (size_t)(maxbits - bits), 0); + } + + words = (bits + 4 + 7) / 8; + + ret = (int)BitStream_appendNum(bstream, (size_t)(words * 8 - bits), 0); + if(ret < 0) return ret; + + padlen = maxwords - words; + if(padlen > 0) { + for(i = 0; i < padlen; i++) { + ret = (int)BitStream_appendNum(bstream, 8, (i&1)?0x11:0xec); + if(ret < 0) { + return ret; + } + } + } + + return 0; +} + +/** + * Append padding bits for the input data - Micro QR Code version. + * @param bstream Bitstream to be appended. + * @param input input data. + * @retval 0 success + * @retval -1 an error occurred and errno is set to indeicate the error. + * See Execptions for the details. + * @throw ERANGE input data is too large. + * @throw ENOMEM unable to allocate memory. + */ +static int QRinput_appendPaddingBitMQR(BitStream *bstream, QRinput *input) +{ + int bits, maxbits, words, maxwords, i, ret, termbits; + int padlen; + + bits = (int)BitStream_size(bstream); + maxbits = MQRspec_getDataLengthBit(input->version, input->level); + maxwords = maxbits / 8; + + if(maxbits < bits) { + errno = ERANGE; + return -1; + } + if(maxbits == bits) { + return 0; + } + + termbits = input->version * 2 + 1; + + if(maxbits - bits <= termbits) { + return (int)BitStream_appendNum(bstream, (size_t)(maxbits - bits), 0); + } + + bits += termbits; + + words = (bits + 7) / 8; + if(maxbits - words * 8 > 0) { + termbits += words * 8 - bits; + if(words == maxwords) termbits += maxbits - words * 8; + } else { + termbits += words * 8 - bits; + } + ret = (int)BitStream_appendNum(bstream, (size_t)termbits, 0); + if(ret < 0) return ret; + + padlen = maxwords - words; + if(padlen > 0) { + for(i = 0; i < padlen; i++) { + ret = (int)BitStream_appendNum(bstream, 8, (i&1)?0x11:0xec); + if(ret < 0) return ret; + } + termbits = maxbits - maxwords * 8; + if(termbits > 0) { + ret = (int)BitStream_appendNum(bstream, (size_t)termbits, 0); + if(ret < 0) return ret; + } + } + + return 0; +} + +static int QRinput_insertFNC1Header(QRinput *input) +{ + QRinput_List *entry = NULL; + + if(input->fnc1 == 1) { + entry = QRinput_List_newEntry(QR_MODE_FNC1FIRST, 0, NULL); + } else if(input->fnc1 == 2) { + entry = QRinput_List_newEntry(QR_MODE_FNC1SECOND, 1, &(input->appid)); + } + if(entry == NULL) { + return -1; + } + + if(input->head->mode != QR_MODE_STRUCTURE && input->head->mode != QR_MODE_ECI) { + entry->next = input->head; + input->head = entry; + } else { + entry->next = input->head->next; + input->head->next = entry; + } + + return 0; +} + +/** + * Merge all bit streams in the input data. + * @param input input data. + * @return merged bit stream + */ + +STATIC_IN_RELEASE int QRinput_mergeBitStream(QRinput *input, BitStream *bstream) +{ + if(input->mqr) { + if(QRinput_createBitStream(input, bstream) < 0) { + return -1; + } + } else { + if(input->fnc1) { + if(QRinput_insertFNC1Header(input) < 0) { + return -1; + } + } + if(QRinput_convertData(input, bstream) < 0) { + return -1; + } + } + + return 0; +} + +/** + * Merge all bit streams in the input data and append padding bits + * @param input input data. + * @return padded merged bit stream + */ + +STATIC_IN_RELEASE int QRinput_getBitStream(QRinput *input, BitStream *bstream) +{ + int ret; + + ret = QRinput_mergeBitStream(input, bstream); + if(ret < 0) return -1; + + if(input->mqr) { + ret = QRinput_appendPaddingBitMQR(bstream, input); + } else { + ret = QRinput_appendPaddingBit(bstream, input); + } + if(ret < 0) return -1; + + return 0; +} + +/** + * Pack all bit streams padding bits into a byte array. + * @param input input data. + * @return padded merged byte stream + */ + +unsigned char *QRinput_getByteStream(QRinput *input) +{ + BitStream *bstream; + unsigned char *array; + int ret; + + bstream = BitStream_new(); + if(bstream == NULL) { + return NULL; + } + + ret = QRinput_getBitStream(input, bstream); + if(ret < 0) { + BitStream_free(bstream); + return NULL; + } + array = BitStream_toByte(bstream); + BitStream_free(bstream); + + return array; +} + +/****************************************************************************** + * Structured input data + *****************************************************************************/ + +static QRinput_InputList *QRinput_InputList_newEntry(QRinput *input) +{ + QRinput_InputList *entry; + + entry = (QRinput_InputList *)malloc(sizeof(QRinput_InputList)); + if(entry == NULL) return NULL; + + entry->input = input; + entry->next = NULL; + + return entry; +} + +static void QRinput_InputList_freeEntry(QRinput_InputList *entry) +{ + if(entry != NULL) { + QRinput_free(entry->input); + free(entry); + } +} + +QRinput_Struct *QRinput_Struct_new(void) +{ + QRinput_Struct *s; + + s = (QRinput_Struct *)malloc(sizeof(QRinput_Struct)); + if(s == NULL) return NULL; + + s->size = 0; + s->parity = -1; + s->head = NULL; + s->tail = NULL; + + return s; +} + +void QRinput_Struct_setParity(QRinput_Struct *s, unsigned char parity) +{ + s->parity = (int)parity; +} + +int QRinput_Struct_appendInput(QRinput_Struct *s, QRinput *input) +{ + QRinput_InputList *e; + + if(input->mqr) { + errno = EINVAL; + return -1; + } + + e = QRinput_InputList_newEntry(input); + if(e == NULL) return -1; + + s->size++; + if(s->tail == NULL) { + s->head = e; + s->tail = e; + } else { + s->tail->next = e; + s->tail = e; + } + + return s->size; +} + +void QRinput_Struct_free(QRinput_Struct *s) +{ + QRinput_InputList *list, *next; + + if(s != NULL) { + list = s->head; + while(list != NULL) { + next = list->next; + QRinput_InputList_freeEntry(list); + list = next; + } + free(s); + } +} + +static unsigned char QRinput_Struct_calcParity(QRinput_Struct *s) +{ + QRinput_InputList *list; + unsigned char parity = 0; + + list = s->head; + while(list != NULL) { + parity ^= QRinput_calcParity(list->input); + list = list->next; + } + + QRinput_Struct_setParity(s, parity); + + return parity; +} + +static int QRinput_List_shrinkEntry(QRinput_List *entry, int bytes) +{ + unsigned char *data; + + data = (unsigned char *)malloc((size_t)bytes); + if(data == NULL) return -1; + + memcpy(data, entry->data, (size_t)bytes); + free(entry->data); + entry->data = data; + entry->size = bytes; + + return 0; +} + +STATIC_IN_RELEASE int QRinput_splitEntry(QRinput_List *entry, int bytes) +{ + QRinput_List *e; + int ret; + + e = QRinput_List_newEntry(entry->mode, entry->size - bytes, entry->data + bytes); + if(e == NULL) { + return -1; + } + + ret = QRinput_List_shrinkEntry(entry, bytes); + if(ret < 0) { + QRinput_List_freeEntry(e); + return -1; + } + + e->next = entry->next; + entry->next = e; + + return 0; +} + +QRinput_Struct *QRinput_splitQRinputToStruct(QRinput *input) +{ + QRinput *p = NULL; + QRinput_Struct *s = NULL; + int bits, maxbits, nextbits, bytes, ret; + QRinput_List *list, *next, *prev; + BitStream *bstream = NULL; + + if(input->mqr) { + errno = EINVAL; + return NULL; + } + + s = QRinput_Struct_new(); + if(s == NULL) return NULL; + + input = QRinput_dup(input); + if(input == NULL) { + QRinput_Struct_free(s); + return NULL; + } + + QRinput_Struct_setParity(s, QRinput_calcParity(input)); + maxbits = QRspec_getDataLength(input->version, input->level) * 8 - STRUCTURE_HEADER_SIZE; + + if(maxbits <= 0) goto ABORT; + + bstream = BitStream_new(); + if(bstream == NULL) goto ABORT; + + bits = 0; + list = input->head; + prev = NULL; + while(list != NULL) { + nextbits = QRinput_estimateBitStreamSizeOfEntry(list, input->version, input->mqr); + if(bits + nextbits <= maxbits) { + BitStream_reset(bstream); + ret = QRinput_encodeBitStream(list, bstream, input->version, input->mqr); + if(ret < 0) goto ABORT; + bits += ret; + prev = list; + list = list->next; + } else { + bytes = QRinput_lengthOfCode(list->mode, input->version, maxbits - bits); + p = QRinput_new2(input->version, input->level); + if(p == NULL) goto ABORT; + if(bytes > 0) { + /* Splits this entry into 2 entries. */ + ret = QRinput_splitEntry(list, bytes); + if(ret < 0) { + QRinput_free(p); + goto ABORT; + } + /* First half is the tail of the current input. */ + next = list->next; + list->next = NULL; + /* Second half is the head of the next input, p.*/ + p->head = next; + /* Renew QRinput.tail. */ + p->tail = input->tail; + input->tail = list; + /* Point to the next entry. */ + prev = list; + list = next; + } else { + /* Current entry will go to the next input. */ + prev->next = NULL; + p->head = list; + p->tail = input->tail; + input->tail = prev; + } + ret = QRinput_Struct_appendInput(s, input); + if(ret < 0) { + QRinput_free(p); + goto ABORT; + } + input = p; + bits = 0; + } + } + ret = QRinput_Struct_appendInput(s, input); + if(ret < 0) goto ABORT; + if(s->size > MAX_STRUCTURED_SYMBOLS) { + errno = ERANGE; + QRinput_Struct_free(s); + BitStream_free(bstream); + return NULL; + } + ret = QRinput_Struct_insertStructuredAppendHeaders(s); + if(ret < 0) { + QRinput_Struct_free(s); + BitStream_free(bstream); + return NULL; + } + + BitStream_free(bstream); + return s; + +ABORT: + BitStream_free(bstream); + QRinput_free(input); + QRinput_Struct_free(s); + return NULL; +} + +int QRinput_Struct_insertStructuredAppendHeaders(QRinput_Struct *s) +{ + int i; + QRinput_InputList *list; + + if(s->size == 1) { + return 0; + } + + if(s->parity < 0) { + QRinput_Struct_calcParity(s); + } + i = 1; + list = s->head; + while(list != NULL) { + if(QRinput_insertStructuredAppendHeader(list->input, s->size, i, s->parity)) + return -1; + i++; + list = list->next; + } + + return 0; +} + +/****************************************************************************** + * Extended encoding mode (FNC1 and ECI) + *****************************************************************************/ + +int QRinput_setFNC1First(QRinput *input) +{ + if(input->mqr) { + errno = EINVAL; + return -1; + } + input->fnc1 = 1; + + return 0; +} + +int QRinput_setFNC1Second(QRinput *input, unsigned char appid) +{ + if(input->mqr) { + errno = EINVAL; + return -1; + } + input->fnc1 = 2; + input->appid = appid; + + return 0; +} diff --git a/src/otsur/qrencode/qrinput.h b/src/otsur/qrencode/qrinput.h new file mode 100644 index 0000000000..5892c48246 --- /dev/null +++ b/src/otsur/qrencode/qrinput.h @@ -0,0 +1,124 @@ +/* + * qrencode - QR Code encoder + * + * Input data chunk class + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef QRINPUT_H +#define QRINPUT_H + +#include "qrencode.h" +#include "bitstream.h" + +int QRinput_isSplittableMode(QRencodeMode mode); + +/****************************************************************************** + * Entry of input data + *****************************************************************************/ +typedef struct _QRinput_List QRinput_List; + +struct _QRinput_List { + QRencodeMode mode; + int size; ///< Size of data chunk (byte). + unsigned char *data; ///< Data chunk. + BitStream *bstream; + QRinput_List *next; +}; + +/****************************************************************************** + * Input Data + *****************************************************************************/ +struct _QRinput { + int version; + QRecLevel level; + QRinput_List *head; + QRinput_List *tail; + int mqr; + int fnc1; + unsigned char appid; +}; + +/****************************************************************************** + * Structured append input data + *****************************************************************************/ +typedef struct _QRinput_InputList QRinput_InputList; + +struct _QRinput_InputList { + QRinput *input; + QRinput_InputList *next; +}; + +struct _QRinput_Struct { + int size; ///< number of structured symbols + int parity; + QRinput_InputList *head; + QRinput_InputList *tail; +}; + +/** + * Pack all bit streams padding bits into a byte array. + * @param input input data. + * @return padded merged byte stream + */ +extern unsigned char *QRinput_getByteStream(QRinput *input); + + +extern int QRinput_estimateBitsModeNum(int size); +extern int QRinput_estimateBitsModeAn(int size); +extern int QRinput_estimateBitsMode8(int size); +extern int QRinput_estimateBitsModeKanji(int size); + +extern QRinput *QRinput_dup(QRinput *input); + +extern const signed char QRinput_anTable[128]; + +/** + * Look up the alphabet-numeric convesion table (see JIS X0510:2004, pp.19). + * @param __c__ character + * @return value + */ +#define QRinput_lookAnTable(__c__) \ + ((__c__ & 0x80)?-1:QRinput_anTable[(int)__c__]) + +/** + * Length of a standard mode indicator in bits. + */ + +#define MODE_INDICATOR_SIZE 4 + +/** + * Length of a segment of structured-append header. + */ +#define STRUCTURE_HEADER_SIZE 20 + +/** + * Maximum number of symbols in a set of structured-appended symbols. + */ +#define MAX_STRUCTURED_SYMBOLS 16 + +#ifdef WITH_TESTS +extern int QRinput_mergeBitStream(QRinput *input, BitStream *bstream); +extern int QRinput_getBitStream(QRinput *input, BitStream *bstream); +extern int QRinput_estimateBitStreamSize(QRinput *input, int version); +extern int QRinput_splitEntry(QRinput_List *entry, int bytes); +extern int QRinput_estimateVersion(QRinput *input); +extern int QRinput_lengthOfCode(QRencodeMode mode, int version, int bits); +extern int QRinput_insertStructuredAppendHeader(QRinput *input, int size, int index, unsigned char parity); +#endif + +#endif /* QRINPUT_H */ diff --git a/src/otsur/qrencode/qrspec.c b/src/otsur/qrencode/qrspec.c new file mode 100644 index 0000000000..f3d3b2c526 --- /dev/null +++ b/src/otsur/qrencode/qrspec.c @@ -0,0 +1,514 @@ +/* + * qrencode - QR Code encoder + * + * QR Code specification in convenient format. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * The following data / specifications are taken from + * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) + * or + * "Automatic identification and data capture techniques -- + * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include + +#include "qrspec.h" +#include "qrinput.h" + +/****************************************************************************** + * Version and capacity + *****************************************************************************/ + +typedef struct { + int width; //< Edge length of the symbol + int words; //< Data capacity (bytes) + int remainder; //< Remainder bit (bits) + int ec[4]; //< Number of ECC code (bytes) +} QRspec_Capacity; + +/** + * Table of the capacity of symbols + * See Table 1 (pp.13) and Table 12-16 (pp.30-36), JIS X0510:2004. + */ +static const QRspec_Capacity qrspecCapacity[QRSPEC_VERSION_MAX + 1] = { + { 0, 0, 0, { 0, 0, 0, 0}}, + { 21, 26, 0, { 7, 10, 13, 17}}, // 1 + { 25, 44, 7, { 10, 16, 22, 28}}, + { 29, 70, 7, { 15, 26, 36, 44}}, + { 33, 100, 7, { 20, 36, 52, 64}}, + { 37, 134, 7, { 26, 48, 72, 88}}, // 5 + { 41, 172, 7, { 36, 64, 96, 112}}, + { 45, 196, 0, { 40, 72, 108, 130}}, + { 49, 242, 0, { 48, 88, 132, 156}}, + { 53, 292, 0, { 60, 110, 160, 192}}, + { 57, 346, 0, { 72, 130, 192, 224}}, //10 + { 61, 404, 0, { 80, 150, 224, 264}}, + { 65, 466, 0, { 96, 176, 260, 308}}, + { 69, 532, 0, { 104, 198, 288, 352}}, + { 73, 581, 3, { 120, 216, 320, 384}}, + { 77, 655, 3, { 132, 240, 360, 432}}, //15 + { 81, 733, 3, { 144, 280, 408, 480}}, + { 85, 815, 3, { 168, 308, 448, 532}}, + { 89, 901, 3, { 180, 338, 504, 588}}, + { 93, 991, 3, { 196, 364, 546, 650}}, + { 97, 1085, 3, { 224, 416, 600, 700}}, //20 + {101, 1156, 4, { 224, 442, 644, 750}}, + {105, 1258, 4, { 252, 476, 690, 816}}, + {109, 1364, 4, { 270, 504, 750, 900}}, + {113, 1474, 4, { 300, 560, 810, 960}}, + {117, 1588, 4, { 312, 588, 870, 1050}}, //25 + {121, 1706, 4, { 336, 644, 952, 1110}}, + {125, 1828, 4, { 360, 700, 1020, 1200}}, + {129, 1921, 3, { 390, 728, 1050, 1260}}, + {133, 2051, 3, { 420, 784, 1140, 1350}}, + {137, 2185, 3, { 450, 812, 1200, 1440}}, //30 + {141, 2323, 3, { 480, 868, 1290, 1530}}, + {145, 2465, 3, { 510, 924, 1350, 1620}}, + {149, 2611, 3, { 540, 980, 1440, 1710}}, + {153, 2761, 3, { 570, 1036, 1530, 1800}}, + {157, 2876, 0, { 570, 1064, 1590, 1890}}, //35 + {161, 3034, 0, { 600, 1120, 1680, 1980}}, + {165, 3196, 0, { 630, 1204, 1770, 2100}}, + {169, 3362, 0, { 660, 1260, 1860, 2220}}, + {173, 3532, 0, { 720, 1316, 1950, 2310}}, + {177, 3706, 0, { 750, 1372, 2040, 2430}} //40 +}; + +int QRspec_getDataLength(int version, QRecLevel level) +{ + return qrspecCapacity[version].words - qrspecCapacity[version].ec[level]; +} + +int QRspec_getECCLength(int version, QRecLevel level) +{ + return qrspecCapacity[version].ec[level]; +} + +int QRspec_getMinimumVersion(int size, QRecLevel level) +{ + int i; + int words; + + for(i = 1; i <= QRSPEC_VERSION_MAX; i++) { + words = qrspecCapacity[i].words - qrspecCapacity[i].ec[level]; + if(words >= size) return i; + } + + return QRSPEC_VERSION_MAX; +} + +int QRspec_getWidth(int version) +{ + return qrspecCapacity[version].width; +} + +int QRspec_getRemainder(int version) +{ + return qrspecCapacity[version].remainder; +} + +/****************************************************************************** + * Length indicator + *****************************************************************************/ + +static const int lengthTableBits[4][3] = { + {10, 12, 14}, + { 9, 11, 13}, + { 8, 16, 16}, + { 8, 10, 12} +}; + +int QRspec_lengthIndicator(QRencodeMode mode, int version) +{ + int l; + + if(!QRinput_isSplittableMode(mode)) return 0; + if(version <= 9) { + l = 0; + } else if(version <= 26) { + l = 1; + } else { + l = 2; + } + + return lengthTableBits[mode][l]; +} + +int QRspec_maximumWords(QRencodeMode mode, int version) +{ + int l; + int bits; + int words; + + if(!QRinput_isSplittableMode(mode)) return 0; + if(version <= 9) { + l = 0; + } else if(version <= 26) { + l = 1; + } else { + l = 2; + } + + bits = lengthTableBits[mode][l]; + words = (1 << bits) - 1; + if(mode == QR_MODE_KANJI) { + words *= 2; // the number of bytes is required + } + + return words; +} + +/****************************************************************************** + * Error correction code + *****************************************************************************/ + +/** + * Table of the error correction code (Reed-Solomon block) + * See Table 12-16 (pp.30-36), JIS X0510:2004. + */ +static const int eccTable[QRSPEC_VERSION_MAX+1][4][2] = { + {{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}}, + {{ 1, 0}, { 1, 0}, { 1, 0}, { 1, 0}}, // 1 + {{ 1, 0}, { 1, 0}, { 1, 0}, { 1, 0}}, + {{ 1, 0}, { 1, 0}, { 2, 0}, { 2, 0}}, + {{ 1, 0}, { 2, 0}, { 2, 0}, { 4, 0}}, + {{ 1, 0}, { 2, 0}, { 2, 2}, { 2, 2}}, // 5 + {{ 2, 0}, { 4, 0}, { 4, 0}, { 4, 0}}, + {{ 2, 0}, { 4, 0}, { 2, 4}, { 4, 1}}, + {{ 2, 0}, { 2, 2}, { 4, 2}, { 4, 2}}, + {{ 2, 0}, { 3, 2}, { 4, 4}, { 4, 4}}, + {{ 2, 2}, { 4, 1}, { 6, 2}, { 6, 2}}, //10 + {{ 4, 0}, { 1, 4}, { 4, 4}, { 3, 8}}, + {{ 2, 2}, { 6, 2}, { 4, 6}, { 7, 4}}, + {{ 4, 0}, { 8, 1}, { 8, 4}, {12, 4}}, + {{ 3, 1}, { 4, 5}, {11, 5}, {11, 5}}, + {{ 5, 1}, { 5, 5}, { 5, 7}, {11, 7}}, //15 + {{ 5, 1}, { 7, 3}, {15, 2}, { 3, 13}}, + {{ 1, 5}, {10, 1}, { 1, 15}, { 2, 17}}, + {{ 5, 1}, { 9, 4}, {17, 1}, { 2, 19}}, + {{ 3, 4}, { 3, 11}, {17, 4}, { 9, 16}}, + {{ 3, 5}, { 3, 13}, {15, 5}, {15, 10}}, //20 + {{ 4, 4}, {17, 0}, {17, 6}, {19, 6}}, + {{ 2, 7}, {17, 0}, { 7, 16}, {34, 0}}, + {{ 4, 5}, { 4, 14}, {11, 14}, {16, 14}}, + {{ 6, 4}, { 6, 14}, {11, 16}, {30, 2}}, + {{ 8, 4}, { 8, 13}, { 7, 22}, {22, 13}}, //25 + {{10, 2}, {19, 4}, {28, 6}, {33, 4}}, + {{ 8, 4}, {22, 3}, { 8, 26}, {12, 28}}, + {{ 3, 10}, { 3, 23}, { 4, 31}, {11, 31}}, + {{ 7, 7}, {21, 7}, { 1, 37}, {19, 26}}, + {{ 5, 10}, {19, 10}, {15, 25}, {23, 25}}, //30 + {{13, 3}, { 2, 29}, {42, 1}, {23, 28}}, + {{17, 0}, {10, 23}, {10, 35}, {19, 35}}, + {{17, 1}, {14, 21}, {29, 19}, {11, 46}}, + {{13, 6}, {14, 23}, {44, 7}, {59, 1}}, + {{12, 7}, {12, 26}, {39, 14}, {22, 41}}, //35 + {{ 6, 14}, { 6, 34}, {46, 10}, { 2, 64}}, + {{17, 4}, {29, 14}, {49, 10}, {24, 46}}, + {{ 4, 18}, {13, 32}, {48, 14}, {42, 32}}, + {{20, 4}, {40, 7}, {43, 22}, {10, 67}}, + {{19, 6}, {18, 31}, {34, 34}, {20, 61}},//40 +}; + +void QRspec_getEccSpec(int version, QRecLevel level, int spec[5]) +{ + int b1, b2; + int data, ecc; + + b1 = eccTable[version][level][0]; + b2 = eccTable[version][level][1]; + data = QRspec_getDataLength(version, level); + ecc = QRspec_getECCLength(version, level); + + if(b2 == 0) { + spec[0] = b1; + spec[1] = data / b1; + spec[2] = ecc / b1; + spec[3] = spec[4] = 0; + } else { + spec[0] = b1; + spec[1] = data / (b1 + b2); + spec[2] = ecc / (b1 + b2); + spec[3] = b2; + spec[4] = spec[1] + 1; + } +} + +/****************************************************************************** + * Alignment pattern + *****************************************************************************/ + +/** + * Positions of alignment patterns. + * This array includes only the second and the third position of the alignment + * patterns. Rest of them can be calculated from the distance between them. + * + * See Table 1 in Appendix E (pp.71) of JIS X0510:2004. + */ +static const int alignmentPattern[QRSPEC_VERSION_MAX+1][2] = { + { 0, 0}, + { 0, 0}, {18, 0}, {22, 0}, {26, 0}, {30, 0}, // 1- 5 + {34, 0}, {22, 38}, {24, 42}, {26, 46}, {28, 50}, // 6-10 + {30, 54}, {32, 58}, {34, 62}, {26, 46}, {26, 48}, //11-15 + {26, 50}, {30, 54}, {30, 56}, {30, 58}, {34, 62}, //16-20 + {28, 50}, {26, 50}, {30, 54}, {28, 54}, {32, 58}, //21-25 + {30, 58}, {34, 62}, {26, 50}, {30, 54}, {26, 52}, //26-30 + {30, 56}, {34, 60}, {30, 58}, {34, 62}, {30, 54}, //31-35 + {24, 50}, {28, 54}, {32, 58}, {26, 54}, {30, 58}, //35-40 +}; + +/** + * Put an alignment marker. + * @param frame + * @param width + * @param ox,oy center coordinate of the pattern + */ +static void QRspec_putAlignmentMarker(unsigned char *frame, int width, int ox, int oy) +{ + static const unsigned char finder[] = { + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + 0xa1, 0xa0, 0xa0, 0xa0, 0xa1, + 0xa1, 0xa0, 0xa1, 0xa0, 0xa1, + 0xa1, 0xa0, 0xa0, 0xa0, 0xa1, + 0xa1, 0xa1, 0xa1, 0xa1, 0xa1, + }; + int x, y; + const unsigned char *s; + + frame += (oy - 2) * width + ox - 2; + s = finder; + for(y = 0; y < 5; y++) { + for(x = 0; x < 5; x++) { + frame[x] = s[x]; + } + frame += width; + s += 5; + } +} + +static void QRspec_putAlignmentPattern(int version, unsigned char *frame, int width) +{ + int d, w, x, y, cx, cy; + + if(version < 2) return; + + d = alignmentPattern[version][1] - alignmentPattern[version][0]; + if(d < 0) { + w = 2; + } else { + w = (width - alignmentPattern[version][0]) / d + 2; + } + + if(w * w - 3 == 1) { + x = alignmentPattern[version][0]; + y = alignmentPattern[version][0]; + QRspec_putAlignmentMarker(frame, width, x, y); + return; + } + + cx = alignmentPattern[version][0]; + for(x = 1; x < w - 1; x++) { + QRspec_putAlignmentMarker(frame, width, 6, cx); + QRspec_putAlignmentMarker(frame, width, cx, 6); + cx += d; + } + + cy = alignmentPattern[version][0]; + for(y = 0; y < w-1; y++) { + cx = alignmentPattern[version][0]; + for(x = 0; x < w-1; x++) { + QRspec_putAlignmentMarker(frame, width, cx, cy); + cx += d; + } + cy += d; + } +} + +/****************************************************************************** + * Version information pattern + *****************************************************************************/ + +/** + * Version information pattern (BCH coded). + * See Table 1 in Appendix D (pp.68) of JIS X0510:2004. + */ +static const unsigned int versionPattern[QRSPEC_VERSION_MAX - 6] = { + 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, + 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, + 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, + 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, + 0x27541, 0x28c69 +}; + +unsigned int QRspec_getVersionPattern(int version) +{ + if(version < 7 || version > QRSPEC_VERSION_MAX) return 0; + + return versionPattern[version - 7]; +} + +/****************************************************************************** + * Format information + *****************************************************************************/ + +/* See calcFormatInfo in tests/test_qrspec.c */ +static const unsigned int formatInfo[4][8] = { + {0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976}, + {0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0}, + {0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed}, + {0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b} +}; + +unsigned int QRspec_getFormatInfo(int mask, QRecLevel level) +{ + if(mask < 0 || mask > 7) return 0; + + return formatInfo[level][mask]; +} + +/****************************************************************************** + * Frame + *****************************************************************************/ + +/** + * Put a finder pattern. + * @param frame + * @param width + * @param ox,oy upper-left coordinate of the pattern + */ +static void putFinderPattern(unsigned char *frame, int width, int ox, int oy) +{ + static const unsigned char finder[] = { + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc1, 0xc1, 0xc1, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc1, 0xc1, 0xc1, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc1, 0xc1, 0xc1, 0xc0, 0xc1, + 0xc1, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc1, + 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, 0xc1, + }; + int x, y; + const unsigned char *s; + + frame += oy * width + ox; + s = finder; + for(y = 0; y < 7; y++) { + for(x = 0; x < 7; x++) { + frame[x] = s[x]; + } + frame += width; + s += 7; + } +} + + +static unsigned char *QRspec_createFrame(int version) +{ + unsigned char *frame, *p, *q; + int width; + int x, y; + unsigned int verinfo, v; + + width = qrspecCapacity[version].width; + frame = (unsigned char *)malloc((size_t)(width * width)); + if(frame == NULL) return NULL; + + memset(frame, 0, (size_t)(width * width)); + /* Finder pattern */ + putFinderPattern(frame, width, 0, 0); + putFinderPattern(frame, width, width - 7, 0); + putFinderPattern(frame, width, 0, width - 7); + /* Separator */ + p = frame; + q = frame + width * (width - 7); + for(y = 0; y < 7; y++) { + p[7] = 0xc0; + p[width - 8] = 0xc0; + q[7] = 0xc0; + p += width; + q += width; + } + memset(frame + width * 7, 0xc0, 8); + memset(frame + width * 8 - 8, 0xc0, 8); + memset(frame + width * (width - 8), 0xc0, 8); + /* Mask format information area */ + memset(frame + width * 8, 0x84, 9); + memset(frame + width * 9 - 8, 0x84, 8); + p = frame + 8; + for(y = 0; y < 8; y++) { + *p = 0x84; + p += width; + } + p = frame + width * (width - 7) + 8; + for(y = 0; y < 7; y++) { + *p = 0x84; + p += width; + } + /* Timing pattern */ + p = frame + width * 6 + 8; + q = frame + width * 8 + 6; + for(x = 1; x < width-15; x++) { + *p = 0x90 | (x & 1); + *q = 0x90 | (x & 1); + p++; + q += width; + } + /* Alignment pattern */ + QRspec_putAlignmentPattern(version, frame, width); + + /* Version information */ + if(version >= 7) { + verinfo = QRspec_getVersionPattern(version); + + p = frame + width * (width - 11); + v = verinfo; + for(x = 0; x < 6; x++) { + for(y = 0; y < 3; y++) { + p[width * y + x] = 0x88 | (v & 1); + v = v >> 1; + } + } + + p = frame + width - 11; + v = verinfo; + for(y = 0; y < 6; y++) { + for(x = 0; x < 3; x++) { + p[x] = 0x88 | (v & 1); + v = v >> 1; + } + p += width; + } + } + /* and a little bit... */ + frame[width * (width - 8) + 8] = 0x81; + + return frame; +} + +unsigned char *QRspec_newFrame(int version) +{ + if(version < 1 || version > QRSPEC_VERSION_MAX) return NULL; + + return QRspec_createFrame(version); +} diff --git a/src/otsur/qrencode/qrspec.h b/src/otsur/qrencode/qrspec.h new file mode 100644 index 0000000000..4d01879ed3 --- /dev/null +++ b/src/otsur/qrencode/qrspec.h @@ -0,0 +1,174 @@ +/* + * qrencode - QR Code encoder + * + * QR Code specification in convenient format. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef QRSPEC_H +#define QRSPEC_H + +#include "qrencode.h" + +/****************************************************************************** + * Version and capacity + *****************************************************************************/ + +/** + * Maximum width of a symbol + */ +#define QRSPEC_WIDTH_MAX 177 + +/** + * Return maximum data code length (bytes) for the version. + * @param version version of the symbol + * @param level error correction level + * @return maximum size (bytes) + */ +extern int QRspec_getDataLength(int version, QRecLevel level); + +/** + * Return maximum error correction code length (bytes) for the version. + * @param version version of the symbol + * @param level error correction level + * @return ECC size (bytes) + */ +extern int QRspec_getECCLength(int version, QRecLevel level); + +/** + * Return a version number that satisfies the input code length. + * @param size input code length (byte) + * @param level error correction level + * @return version number + */ +extern int QRspec_getMinimumVersion(int size, QRecLevel level); + +/** + * Return the width of the symbol for the version. + * @param version vesion of the symbol + * @return width of the symbol + */ +extern int QRspec_getWidth(int version); + +/** + * Return the numer of remainder bits. + * @param version vesion of the symbol + * @return number of remainder bits + */ +extern int QRspec_getRemainder(int version); + +/****************************************************************************** + * Length indicator + *****************************************************************************/ + +/** + * Return the size of length indicator for the mode and version. + * @param mode encode mode + * @param version vesion of the symbol + * @return the size of the appropriate length indicator (bits). + */ +extern int QRspec_lengthIndicator(QRencodeMode mode, int version); + +/** + * Return the maximum length for the mode and version. + * @param mode encode mode + * @param version vesion of the symbol + * @return the maximum length (bytes) + */ +extern int QRspec_maximumWords(QRencodeMode mode, int version); + +/****************************************************************************** + * Error correction code + *****************************************************************************/ + +/** + * Return an array of ECC specification. + * @param version version of the symbol + * @param level error correction level + * @param spec an array of ECC specification contains as following: + * {# of type1 blocks, # of data code, # of ecc code, + * # of type2 blocks, # of data code} + */ +void QRspec_getEccSpec(int version, QRecLevel level, int spec[5]); + +#define QRspec_rsBlockNum(__spec__) (__spec__[0] + __spec__[3]) +#define QRspec_rsBlockNum1(__spec__) (__spec__[0]) +#define QRspec_rsDataCodes1(__spec__) (__spec__[1]) +#define QRspec_rsEccCodes1(__spec__) (__spec__[2]) +#define QRspec_rsBlockNum2(__spec__) (__spec__[3]) +#define QRspec_rsDataCodes2(__spec__) (__spec__[4]) +#define QRspec_rsEccCodes2(__spec__) (__spec__[2]) + +#define QRspec_rsDataLength(__spec__) \ + ((QRspec_rsBlockNum1(__spec__) * QRspec_rsDataCodes1(__spec__)) + \ + (QRspec_rsBlockNum2(__spec__) * QRspec_rsDataCodes2(__spec__))) +#define QRspec_rsEccLength(__spec__) \ + (QRspec_rsBlockNum(__spec__) * QRspec_rsEccCodes1(__spec__)) + +/****************************************************************************** + * Version information pattern + *****************************************************************************/ + +/** + * Return BCH encoded version information pattern that is used for the symbol + * of version 7 or greater. Use lower 18 bits. + * @param version version of the symbol + * @return BCH encoded version information pattern + */ +extern unsigned int QRspec_getVersionPattern(int version); + +/****************************************************************************** + * Format information + *****************************************************************************/ + +/** + * Return BCH encoded format information pattern. + * @param mask mask number + * @param level error correction level + * @return BCH encoded format information pattern + */ +extern unsigned int QRspec_getFormatInfo(int mask, QRecLevel level); + +/****************************************************************************** + * Frame + *****************************************************************************/ + +/** + * Return a copy of initialized frame. + * @param version version of the symbol + * @return Array of unsigned char. You can free it by free(). + */ +extern unsigned char *QRspec_newFrame(int version); + +/****************************************************************************** + * Mode indicator + *****************************************************************************/ + +/** + * Mode indicator. See Table 2 of JIS X0510:2004, pp.16. + */ +#define QRSPEC_MODEID_ECI 7 +#define QRSPEC_MODEID_NUM 1 +#define QRSPEC_MODEID_AN 2 +#define QRSPEC_MODEID_8 4 +#define QRSPEC_MODEID_KANJI 8 +#define QRSPEC_MODEID_FNC1FIRST 5 +#define QRSPEC_MODEID_FNC1SECOND 9 +#define QRSPEC_MODEID_STRUCTURE 3 +#define QRSPEC_MODEID_TERMINATOR 0 + +#endif /* QRSPEC_H */ diff --git a/src/otsur/qrencode/rsecc.c b/src/otsur/qrencode/rsecc.c new file mode 100644 index 0000000000..b88cae3071 --- /dev/null +++ b/src/otsur/qrencode/rsecc.c @@ -0,0 +1,149 @@ +/* + * qrencode - QR Code encoder + * + * Reed solomon error correction code encoder specialized for QR code. + * This code is rewritten by Kentaro Fukuchi, referring to the FEC library + * developed by Phil Karn (KA9Q). + * + * Copyright (C) 2002, 2003, 2004, 2006 Phil Karn, KA9Q + * Copyright (C) 2014-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#if HAVE_LIBPTHREAD +#include +#endif + +#include "rsecc.h" + +#if HAVE_LIBPTHREAD +static pthread_mutex_t RSECC_mutex = PTHREAD_MUTEX_INITIALIZER; +#endif + +static int initialized = 0; + +#define SYMBOL_SIZE (8) +#define symbols ((1U << SYMBOL_SIZE) - 1) +static const unsigned int proot = 0x11d; /* stands for x^8+x^4+x^3+x^2+1 (see pp.37 of JIS X0510:2004) */ + +/* min/max codeword length of ECC, calculated from the specification. */ +#define min_length (2) +#define max_length (30) +#define max_generatorSize (max_length) + +static unsigned char alpha[symbols + 1]; +static unsigned char aindex[symbols + 1]; +static unsigned char generator[max_length - min_length + 1][max_generatorSize + 1]; +static unsigned char generatorInitialized[max_length - min_length + 1]; + +static void RSECC_initLookupTable(void) +{ + unsigned int i, b; + + alpha[symbols] = 0; + aindex[0] = symbols; + + b = 1; + for(i = 0; i < symbols; i++) { + alpha[i] = b; + aindex[b] = i; + b <<= 1; + if(b & (symbols + 1)) { + b ^= proot; + } + b &= symbols; + } +} + +static void RSECC_init(void) +{ + RSECC_initLookupTable(); + memset(generatorInitialized, 0, (max_length - min_length + 1)); + initialized = 1; +} + +static void generator_init(size_t length) +{ + size_t i, j; + int g[max_generatorSize + 1]; + + g[0] = 1; + for(i = 0; i < length; i++) { + g[i + 1] = 1; + /* Because g[0] never be zero, skipped some conditional checks. */ + for(j = i; j > 0; j--) { + g[j] = g[j - 1] ^ alpha[(aindex[g[j]] + i) % symbols]; + } + g[0] = alpha[(aindex[g[0]] + i) % symbols]; + } + + for(i = 0; i <= length; i++) { + generator[length - min_length][i] = aindex[g[i]]; + } + + generatorInitialized[length - min_length] = 1; +} + +int RSECC_encode(size_t data_length, size_t ecc_length, const unsigned char *data, unsigned char *ecc) +{ + size_t i, j; + unsigned char feedback; + unsigned char *gen; + +#if HAVE_LIBPTHREAD + pthread_mutex_lock(&RSECC_mutex); +#endif + if(!initialized) { + RSECC_init(); + } +#if HAVE_LIBPTHREAD + pthread_mutex_unlock(&RSECC_mutex); +#endif + + if(ecc_length > max_length) return -1; + + memset(ecc, 0, ecc_length); +#if HAVE_LIBPTHREAD + pthread_mutex_lock(&RSECC_mutex); +#endif + if(!generatorInitialized[ecc_length - min_length]) generator_init(ecc_length); +#if HAVE_LIBPTHREAD + pthread_mutex_unlock(&RSECC_mutex); +#endif + gen = generator[ecc_length - min_length]; + + for(i = 0; i < data_length; i++) { + feedback = aindex[data[i] ^ ecc[0]]; + if(feedback != symbols) { + for(j = 1; j < ecc_length; j++) { + ecc[j] ^= alpha[(unsigned int)(feedback + gen[ecc_length - j]) % symbols]; + } + } + memmove(&ecc[0], &ecc[1], ecc_length - 1); + if(feedback != symbols) { + ecc[ecc_length - 1] = alpha[(unsigned int)(feedback + gen[0]) % symbols]; + } else { + ecc[ecc_length - 1] = 0; + } + } + + return 0; +} diff --git a/src/otsur/qrencode/rsecc.h b/src/otsur/qrencode/rsecc.h new file mode 100644 index 0000000000..2c17dedb92 --- /dev/null +++ b/src/otsur/qrencode/rsecc.h @@ -0,0 +1,31 @@ +/* + * qrencode - QR Code encoder + * + * Reed solomon error correction code encoder specialized for QR code. + * This code is rewritten by Kentaro Fukuchi, referring to the FEC library + * developed by Phil Karn (KA9Q). + * + * Copyright (C) 2002, 2003, 2004, 2006 Phil Karn, KA9Q + * Copyright (C) 2014-2017 Kentaro Fukuchi + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef RSECC_H +#define RSECC_H + +extern int RSECC_encode(size_t data_length, size_t ecc_length, const unsigned char *data, unsigned char *ecc); + +#endif /* RSECC_H */ diff --git a/src/otsur/qrencode/split.c b/src/otsur/qrencode/split.c new file mode 100644 index 0000000000..a79bf03854 --- /dev/null +++ b/src/otsur/qrencode/split.c @@ -0,0 +1,323 @@ +/* + * qrencode - QR Code encoder + * + * Input data splitter. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * The following data / specifications are taken from + * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) + * or + * "Automatic identification and data capture techniques -- + * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif +#include +#include +#include +#include "qrencode.h" +#include "qrinput.h" +#include "qrspec.h" +#include "split.h" + +#define isdigit(__c__) ((unsigned char)((signed char)(__c__) - '0') < 10) +#define isalnum(__c__) (QRinput_lookAnTable(__c__) >= 0) + +#if !HAVE_STRDUP +#undef strdup +char *strdup(const char *s) +{ + size_t len = strlen(s) + 1; + void *newstring = malloc(len); + if(newstring == NULL) return NULL; + return (char *)memcpy(newstring, s, len); +} +#endif + +static QRencodeMode Split_identifyMode(const char *string, QRencodeMode hint) +{ + unsigned char c, d; + unsigned int word; + + c = (unsigned char)string[0]; + + if(c == '\0') return QR_MODE_NUL; + if(isdigit(c)) { + return QR_MODE_NUM; + } else if(isalnum(c)) { + return QR_MODE_AN; + } else if(hint == QR_MODE_KANJI) { + d = (unsigned char)string[1]; + if(d != '\0') { + word = ((unsigned int)c << 8) | d; + if((word >= 0x8140 && word <= 0x9ffc) || (word >= 0xe040 && word <= 0xebbf)) { + return QR_MODE_KANJI; + } + } + } + + return QR_MODE_8; +} + +static int Split_eatAn(const char *string, QRinput *input, QRencodeMode hint); +static int Split_eat8(const char *string, QRinput *input, QRencodeMode hint); + +static int Split_eatNum(const char *string, QRinput *input,QRencodeMode hint) +{ + const char *p; + int ret; + int run; + int dif; + int ln; + QRencodeMode mode; + + ln = QRspec_lengthIndicator(QR_MODE_NUM, input->version); + + p = string; + while(isdigit(*p)) { + p++; + } + run = (int)(p - string); + mode = Split_identifyMode(p, hint); + if(mode == QR_MODE_8) { + dif = QRinput_estimateBitsModeNum(run) + 4 + ln + + QRinput_estimateBitsMode8(1) /* + 4 + l8 */ + - QRinput_estimateBitsMode8(run + 1) /* - 4 - l8 */; + if(dif > 0) { + return Split_eat8(string, input, hint); + } + } + if(mode == QR_MODE_AN) { + dif = QRinput_estimateBitsModeNum(run) + 4 + ln + + QRinput_estimateBitsModeAn(1) /* + 4 + la */ + - QRinput_estimateBitsModeAn(run + 1) /* - 4 - la */; + if(dif > 0) { + return Split_eatAn(string, input, hint); + } + } + + ret = QRinput_append(input, QR_MODE_NUM, run, (unsigned char *)string); + if(ret < 0) return -1; + + return run; +} + +static int Split_eatAn(const char *string, QRinput *input, QRencodeMode hint) +{ + const char *p, *q; + int ret; + int run; + int dif; + int la, ln; + + la = QRspec_lengthIndicator(QR_MODE_AN, input->version); + ln = QRspec_lengthIndicator(QR_MODE_NUM, input->version); + + p = string; + while(isalnum(*p)) { + if(isdigit(*p)) { + q = p; + while(isdigit(*q)) { + q++; + } + dif = QRinput_estimateBitsModeAn((int)(p - string)) /* + 4 + la */ + + QRinput_estimateBitsModeNum((int)(q - p)) + 4 + ln + + (isalnum(*q)?(4 + ln):0) + - QRinput_estimateBitsModeAn((int)(q - string)) /* - 4 - la */; + if(dif < 0) { + break; + } + p = q; + } else { + p++; + } + } + + run = (int)(p - string); + + if(*p && !isalnum(*p)) { + dif = QRinput_estimateBitsModeAn(run) + 4 + la + + QRinput_estimateBitsMode8(1) /* + 4 + l8 */ + - QRinput_estimateBitsMode8(run + 1) /* - 4 - l8 */; + if(dif > 0) { + return Split_eat8(string, input, hint); + } + } + + ret = QRinput_append(input, QR_MODE_AN, run, (unsigned char *)string); + if(ret < 0) return -1; + + return run; +} + +static int Split_eatKanji(const char *string, QRinput *input, QRencodeMode hint) +{ + const char *p; + int ret; + int run; + + p = string; + while(Split_identifyMode(p, hint) == QR_MODE_KANJI) { + p += 2; + } + run = (int)(p - string); + ret = QRinput_append(input, QR_MODE_KANJI, run, (unsigned char *)string); + if(ret < 0) return -1; + + return run; +} + +static int Split_eat8(const char *string, QRinput *input, QRencodeMode hint) +{ + const char *p, *q; + QRencodeMode mode; + int ret; + int run; + int dif; + int la, ln, l8; + int swcost; + + la = QRspec_lengthIndicator(QR_MODE_AN, input->version); + ln = QRspec_lengthIndicator(QR_MODE_NUM, input->version); + l8 = QRspec_lengthIndicator(QR_MODE_8, input->version); + + p = string + 1; + while(*p != '\0') { + mode = Split_identifyMode(p, hint); + if(mode == QR_MODE_KANJI) { + break; + } + if(mode == QR_MODE_NUM) { + q = p; + while(isdigit(*q)) { + q++; + } + if(Split_identifyMode(q, hint) == QR_MODE_8) { + swcost = 4 + l8; + } else { + swcost = 0; + } + dif = QRinput_estimateBitsMode8((int)(p - string)) /* + 4 + l8 */ + + QRinput_estimateBitsModeNum((int)(q - p)) + 4 + ln + + swcost + - QRinput_estimateBitsMode8((int)(q - string)) /* - 4 - l8 */; + if(dif < 0) { + break; + } + p = q; + } else if(mode == QR_MODE_AN) { + q = p; + while(isalnum(*q)) { + q++; + } + if(Split_identifyMode(q, hint) == QR_MODE_8) { + swcost = 4 + l8; + } else { + swcost = 0; + } + dif = QRinput_estimateBitsMode8((int)(p - string)) /* + 4 + l8 */ + + QRinput_estimateBitsModeAn((int)(q - p)) + 4 + la + + swcost + - QRinput_estimateBitsMode8((int)(q - string)) /* - 4 - l8 */; + if(dif < 0) { + break; + } + p = q; + } else { + p++; + } + } + + run = (int)(p - string); + ret = QRinput_append(input, QR_MODE_8, run, (unsigned char *)string); + if(ret < 0) return -1; + + return run; +} + +static int Split_splitString(const char *string, QRinput *input, + QRencodeMode hint) +{ + int length; + QRencodeMode mode; + + while(*string != '\0') { + mode = Split_identifyMode(string, hint); + if(mode == QR_MODE_NUM) { + length = Split_eatNum(string, input, hint); + } else if(mode == QR_MODE_AN) { + length = Split_eatAn(string, input, hint); + } else if(mode == QR_MODE_KANJI && hint == QR_MODE_KANJI) { + length = Split_eatKanji(string, input, hint); + } else { + length = Split_eat8(string, input, hint); + } + if(length == 0) break; + if(length < 0) return -1; + string += length; + } + + return 0; +} + +static char *dupAndToUpper(const char *str, QRencodeMode hint) +{ + char *newstr, *p; + QRencodeMode mode; + + newstr = strdup(str); + if(newstr == NULL) return NULL; + + p = newstr; + while(*p != '\0') { + mode = Split_identifyMode(p, hint); + if(mode == QR_MODE_KANJI) { + p += 2; + } else { + if (*p >= 'a' && *p <= 'z') { + *p = (char)((int)*p - 32); + } + p++; + } + } + + return newstr; +} + +int Split_splitStringToQRinput(const char *string, QRinput *input, + QRencodeMode hint, int casesensitive) +{ + char *newstr; + int ret; + + if(string == NULL || *string == '\0') { + errno = EINVAL; + return -1; + } + if(!casesensitive) { + newstr = dupAndToUpper(string, hint); + if(newstr == NULL) return -1; + ret = Split_splitString(newstr, input, hint); + free(newstr); + } else { + ret = Split_splitString(string, input, hint); + } + + return ret; +} diff --git a/src/otsur/qrencode/split.h b/src/otsur/qrencode/split.h new file mode 100644 index 0000000000..81829e078e --- /dev/null +++ b/src/otsur/qrencode/split.h @@ -0,0 +1,47 @@ +/* + * qrencode - QR Code encoder + * + * Input data splitter. + * Copyright (C) 2006-2017 Kentaro Fukuchi + * + * The following data / specifications are taken from + * "Two dimensional symbol -- QR-code -- Basic Specification" (JIS X0510:2004) + * or + * "Automatic identification and data capture techniques -- + * QR Code 2005 bar code symbology specification" (ISO/IEC 18004:2006) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SPLIT_H +#define SPLIT_H + +#include "qrencode.h" + +/** + * Split the input string (null terminated) into QRinput. + * @param string input string + * @param hint give QR_MODE_KANJI if the input string contains Kanji character encoded in Shift-JIS. If not, give QR_MODE_8. + * @param casesensitive 0 for case-insensitive encoding (all alphabet characters are replaced to UPPER-CASE CHARACTERS. + * @retval 0 success. + * @retval -1 an error occurred. errno is set to indicate the error. See + * Exceptions for the details. + * @throw EINVAL invalid input object. + * @throw ENOMEM unable to allocate memory for input objects. + */ +extern int Split_splitStringToQRinput(const char *string, QRinput *input, + QRencodeMode hint, int casesensitive); + +#endif /* SPLIT_H */ diff --git a/src/otsur/qtquick/CMakeLists.txt b/src/otsur/qtquick/CMakeLists.txt new file mode 100644 index 0000000000..3ca1d771b2 --- /dev/null +++ b/src/otsur/qtquick/CMakeLists.txt @@ -0,0 +1,37 @@ +set(URREGISTER_SOURCE + UrRegister.cpp + UrSender.cpp + UrImageProvider.cpp + UrCodeScanner.cpp + ScanThread.cpp +) +set(URREGISTER_HEADERS + UrRegister.h + UrSender.h + UrImageProvider.h + UrCodeScanner.h + ScanThread.h + ScanResult.h +) +if (QT_VERSION_MAJOR EQUAL 6) + find_package(Qt6 COMPONENTS Core Gui Widgets MultimediaWidgets QUIET) +else() + find_package(Qt5 COMPONENTS Core Gui Widgets MultimediaWidgets REQUIRED) +endif() +find_package(ZXing REQUIRED) + +add_library(otsqtquick STATIC ${URREGISTER_SOURCE} ${URREGISTER_HEADERS}) +target_include_directories(otsqtquick PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries(otsqtquick PUBLIC + Qt::Core + Qt::Gui + Qt::Widgets + Qt::MultimediaWidgets + qrcode + bcur + qrencode + otsdata + ZXing::ZXing +) diff --git a/src/otsur/qtquick/ScanResult.h b/src/otsur/qtquick/ScanResult.h new file mode 100644 index 0000000000..0e49e13f3f --- /dev/null +++ b/src/otsur/qtquick/ScanResult.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef CODE_SCAN_RESULT_H +#define CODE_SCAN_RESULT_H + +#include + +class ScanResult +{ +public: + explicit ScanResult(const std::string &text, bool isValid) + : m_text(QString::fromStdString(text)) + , m_valid(isValid){} + + [[nodiscard]] QString text() const { return m_text; } + [[nodiscard]] bool isValid() const { return m_valid; } + +private: + QString m_text = ""; + bool m_valid = false; +}; + +#endif // CODE_SCAN_RESULT_H diff --git a/src/otsur/qtquick/ScanThread.cpp b/src/otsur/qtquick/ScanThread.cpp new file mode 100644 index 0000000000..7a3e063708 --- /dev/null +++ b/src/otsur/qtquick/ScanThread.cpp @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#include "ScanThread.h" + +#include + +ScanThread::ScanThread(QObject *parent) + : QThread(parent) + , m_running(true) +{ +} + +void ScanThread::processQImage(const QImage &qimg) +{ + const auto hints = ZXing::ReaderOptions() + .setFormats(ZXing::BarcodeFormat::QRCode) + .setTryHarder(true) + .setMaxNumberOfSymbols(1); + + const auto result = ScanThread::ReadBarcode(qimg, hints); + + if (result.isValid()) { + emit decoded(result.text()); + } +} + +void ScanThread::stop() +{ + m_running = false; + m_waitCondition.wakeOne(); +} + +void ScanThread::start() +{ + m_queue.clear(); + m_running = true; + m_waitCondition.wakeOne(); + QThread::start(); +} + +void ScanThread::addImage(const QImage &img) +{ + QMutexLocker locker(&m_mutex); + if (m_queue.length() > 100) { + return; + } + m_queue.append(img); + m_waitCondition.wakeOne(); +} + +void ScanThread::run() +{ + while (m_running) { + QMutexLocker locker(&m_mutex); + while (m_queue.isEmpty() && m_running) { + m_waitCondition.wait(&m_mutex); + } + if (!m_queue.isEmpty()) { + processQImage(m_queue.takeFirst()); + } + } +} + +ScanResult ScanThread::ReadBarcode(const QImage& img, const ZXing::ReaderOptions& hints) +{ + auto ImgFmtFromQImg = [](const QImage& img){ + switch (img.format()) { + case QImage::Format_ARGB32: + case QImage::Format_RGB32: +#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN + return ZXing::ImageFormat::BGRX; +#else + return ZXing::ImageFormat::XRGB; +#endif + + case QImage::Format_RGB888: + return ZXing::ImageFormat::RGB; + + case QImage::Format_RGBX8888: + + case QImage::Format_RGBA8888: + return ZXing::ImageFormat::RGBX; + + case QImage::Format_Grayscale8: + return ZXing::ImageFormat::Lum; + + default: + return ZXing::ImageFormat::None; + } + }; + + auto exec = [&](const QImage& img){ + auto res = ZXing::ReadBarcode({ img.bits(), img.width(), img.height(), ImgFmtFromQImg(img) }, hints); + return ScanResult(res.text(), res.isValid()); + }; + + try { + if (ImgFmtFromQImg(img) == ZXing::ImageFormat::None) { + return exec(img.convertToFormat(QImage::Format_RGBX8888)); + } else { + return exec(img); + } + } + catch (...) { + return ScanResult("", false); + } +} + + +QString ScanThread::scanImage(const QImage &img) { + const auto hints = ZXing::ReaderOptions() + .setFormats(ZXing::BarcodeFormat::QRCode | ZXing::BarcodeFormat::DataMatrix) + .setTryHarder(true) + .setBinarizer(ZXing::Binarizer::FixedThreshold); + const auto result = ReadBarcode(img, hints); + return result.text(); +} diff --git a/src/otsur/qtquick/ScanThread.h b/src/otsur/qtquick/ScanThread.h new file mode 100644 index 0000000000..9ba1fecb22 --- /dev/null +++ b/src/otsur/qtquick/ScanThread.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2024 The Monero Project + +#ifndef SCANTHREAD_H +#define SCANTHREAD_H + +#include +#include +#include +#include +#include +#include + +#include "ScanResult.h" +#include + +class ScanThread : public QThread +{ + Q_OBJECT + +public: + explicit ScanThread(QObject *parent = nullptr); + void addImage(const QImage &img); + + virtual void stop(); + virtual void start(); + +signals: + void decoded(const QString &data); + +protected: + void run() override; + void processQImage(const QImage &); + +private: + bool m_running; + QMutex m_mutex; + QWaitCondition m_waitCondition; + QList m_queue; + static QString scanImage(const QImage &img); + static ScanResult ReadBarcode(const QImage& img, const ZXing::ReaderOptions& hints = { }); +}; +#endif diff --git a/src/otsur/qtquick/UrCodeScanner.cpp b/src/otsur/qtquick/UrCodeScanner.cpp new file mode 100644 index 0000000000..21254d5c05 --- /dev/null +++ b/src/otsur/qtquick/UrCodeScanner.cpp @@ -0,0 +1,177 @@ +#include "UrCodeScanner.h" +#include +#include +#include + +UrCodeScanner::UrCodeScanner() + : QObject() + , m_scan_ur(MODE_QR) + , m_data_type(QR_ANY) +{ + this->init(); +} + +void UrCodeScanner::init() { + m_thread = new ScanThread(this); + m_probe = new QVideoProbe(this); + connect(m_thread, &ScanThread::decoded, this, &UrCodeScanner::onDecoded); + connect(m_probe, &QVideoProbe::videoFrameProbed, this, &UrCodeScanner::onFrameCaptured); +} + +void UrCodeScanner::setSource(QCamera *source) +{ + if( source == m_camera) + return; + m_probe->setSource(source); + emit sourceChanged(); +} + +void UrCodeScanner::startCapture(bool scan_ur, const QString &data_type) { + m_scan_ur = scan_ur; + if(!data_type.isEmpty()) + m_data_type = data_type; + if(scan_ur) + emit urCaptureStarted(m_data_type); + else + emit qrCaptureStarted(); + emit estimatedCompletedPercentage(0.0); + if (!m_thread->isRunning()) { + m_thread->start(); + } + m_done = false; + m_decoder = ur::URDecoder(); + m_handleFrames = true; +} + +void UrCodeScanner::reset() { + m_done = false; + m_decoder = ur::URDecoder(); + m_thread->start(); + m_handleFrames = true; +} + +void UrCodeScanner::stop() { + m_thread->stop(); + m_handleFrames = false; +} + +void UrCodeScanner::onFrameCaptured(const QVideoFrame &frame) { + if (!m_handleFrames || !m_thread->isRunning()) + return; + QImage img = this->videoFrameToImage(frame); + if (img.format() == QImage::Format_ARGB32) + m_thread->addImage(img); +} + +QImage UrCodeScanner::videoFrameToImage(const QVideoFrame &videoFrame) +{ +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QImage image = videoFrame.toImage(); +#else + QImage image = videoFrame.image(); +#endif + if (image.isNull()) + return {}; + if (image.format() != QImage::Format_ARGB32) + image = image.convertToFormat(QImage::Format_ARGB32); + return image.copy(); +} + +void UrCodeScanner::onImage(const QImage &image) { + if (!m_handleFrames || !m_thread->isRunning()) + return; + if (image.format() == QImage::Format_ARGB32) { + m_thread->addImage(image); + return; + } + m_thread->addImage(image.copy().convertToFormat(QImage::Format_ARGB32)); +} + +void UrCodeScanner::onDecoded(const QString &data) { + emit decodedFrame(data); + if (m_done) + return; + if (!m_scan_ur) { // scan only a QR code + m_done = true; + m_thread->stop(); + emit qrDataReceived(data); + if (m_data_type == QR_WALLET) + emit wallet(MoneroData::parseWalletData(data, m_fallbackToJson)); + if (m_data_type == QR_TX_DATA) + emit txData(MoneroData::parseTxData(data)); + return; + } + if(!m_data_type.isEmpty() && !data.startsWith("ur:" + m_data_type)) { //check for type + emit unexpectedUrType(extractUrType(data)); + return; + } + bool success = m_decoder.receive_part(data.toStdString()); + emit receivedFrames(m_decoder.received_part_indexes().size()); + emit expectedFrames(m_decoder.expected_part_count()); + emit scannedFrames(m_decoder.received_part_indexes().size(), m_decoder.expected_part_count()); + emit estimatedCompletedPercentage(m_decoder.estimated_percent_complete()); + if (!success) + return; + if (m_decoder.is_complete()) { + m_done = true; + m_thread->stop(); + if(!m_decoder.is_success()) { + if(m_decoder.is_failure()) + emit urDataFailed(getURError()); + return; + } + QString ur_type = getURType().toLower(); + QByteArray ur_data = QByteArray::fromStdString(getURData()); + emit urDataReceived(ur_type, ur_data); // providing the raw data independent if ur_type matches. + if(!m_data_type.isEmpty() && m_data_type != ur_type) + emit unexpectedUrType(ur_type); + if(ur_type == XMR_OUTPUT) + emit outputs(ur_data); + if(ur_type == XMR_KEY_IMAGE) + emit keyImages(ur_data); + if(ur_type == XMR_TX_UNSIGNED) + emit unsignedTx(ur_data); + if(ur_type == XMR_TX_SIGNED) + emit signedTx(ur_data); + } +} + +std::string UrCodeScanner::getURData() { + if (!m_decoder.is_success()) + return ""; + ur::ByteVector cbor = m_decoder.result_ur().cbor(); + std::string data; + auto i = cbor.begin(); + auto end = cbor.end(); + ur::CborLite::decodeBytes(i, end, data); + return data; +} + +QString UrCodeScanner::getURType() { + if (!m_decoder.is_success()) + return ""; + return QString::fromStdString(m_decoder.expected_type().value_or("")); +} + +QString UrCodeScanner::getURError() { + if (!m_decoder.is_failure()) + return {}; + return QString::fromStdString(m_decoder.result_error().what()); +} + +UrCodeScanner::~UrCodeScanner() { + m_thread->stop(); + m_thread->quit(); + if (!m_thread->wait(5000)) + { + m_thread->terminate(); + m_thread->wait(); + } + delete m_probe; +} + +QString UrCodeScanner::extractUrType(const QString& qrFrame) { + if (!qrFrame.startsWith("ur:") || !qrFrame.contains('/')) + return ""; + return qrFrame.mid(3, qrFrame.indexOf('/') - 3); +} diff --git a/src/otsur/qtquick/UrCodeScanner.h b/src/otsur/qtquick/UrCodeScanner.h new file mode 100644 index 0000000000..bf6aad869f --- /dev/null +++ b/src/otsur/qtquick/UrCodeScanner.h @@ -0,0 +1,99 @@ +#ifndef URCODESCANNER_h +#define URCODESCANNER_h + +#define QR_WALLET "wallet" +#define QR_TX_DATA "txdata" +#define QR_ANY "" +#define MODE_QR false +#define MODE_UR true + +#include +#include +#include +#include +#include + +#include "ScanThread.h" +#include +#include + +class QVideoProbe; +class QCamera; + +class UrCodeScanner: public QObject +{ + Q_OBJECT + Q_PROPERTY(QCamera* source READ source WRITE setSource NOTIFY sourceChanged) + Q_PROPERTY(bool fallbackToJson READ fallbackToJson WRITE setFallbackToJson NOTIFY fallbackToJsonChanged) + +public: + UrCodeScanner(); + ~UrCodeScanner() override; + void init(); + + QCamera* source() { return m_camera; } + + Q_INVOKABLE void reset(); // reset scanner because data was invalid + Q_INVOKABLE void stop(); // stop scanning + Q_INVOKABLE void startCapture(bool scan_ur = MODE_QR, const QString &data_type = QR_ANY); + Q_INVOKABLE void scanOutputs() { startCapture(MODE_UR, XMR_OUTPUT); } + Q_INVOKABLE void scanKeyImages() { startCapture(MODE_UR, XMR_KEY_IMAGE); } + Q_INVOKABLE void scanUnsignedTx() { startCapture(MODE_UR, XMR_TX_UNSIGNED); } + Q_INVOKABLE void scanSignedTx() { startCapture(MODE_UR, XMR_TX_SIGNED); } + Q_INVOKABLE void scanWallet() { startCapture(MODE_QR, "wallet"); } + Q_INVOKABLE void scanTxData() { startCapture(MODE_QR, "txdata"); } + Q_INVOKABLE void qr() { startCapture(MODE_QR, QR_ANY); } + bool fallbackToJson() { return m_fallbackToJson; } + void setFallbackToJson(bool on) { m_fallbackToJson = on; emit fallbackToJsonChanged(); } + void setSource(QCamera *source); + +signals: + void outputs(const QByteArray &outputs); + void keyImages(const QByteArray &keyImages); + void unsignedTx(const QByteArray &unsignedTx); + void signedTx(const QByteArray &signedTx); + void wallet(MoneroWalletData* walletData); + void txData(MoneroTxData* txData); + void qrDataReceived(const QString &data); + void urDataReceived(const QString &type, const QByteArray &data); + void urDataFailed(const QString &errorMsg); + void decodedFrame(const QString &data); + void receivedFrames(int count); + void expectedFrames(int total); + void scannedFrames(int count, int total); + void estimatedCompletedPercentage(float complete); + void unexpectedUrType(const QString &ur_type); + + void urCaptureStarted(const QString &type); + void qrCaptureStarted(); + + void sourceChanged(); + void fallbackToJsonChanged(); + void notifyError(const QString &error, bool warning = false); + +public slots: + void onFrameCaptured(const QVideoFrame &videoFrame); + void onImage(const QImage &image); + +private slots: + void onDecoded(const QString &data); + +private: + bool m_scan_ur = false; + QString m_data_type = QR_ANY; + bool m_done = false; + ScanThread *m_thread; + ur::URDecoder m_decoder; + + QImage videoFrameToImage(const QVideoFrame &videoFrame); + std::string getURData(); + QString getURType(); + QString getURError(); +protected: + static QString extractUrType(const QString& qrFrame); + bool m_fallbackToJson = true; + bool m_handleFrames = true; + QVideoProbe *m_probe; + QCamera *m_camera = nullptr; +}; +#endif // URCODESCANNER_h diff --git a/src/otsur/qtquick/UrImageProvider.cpp b/src/otsur/qtquick/UrImageProvider.cpp new file mode 100644 index 0000000000..7557fff0e8 --- /dev/null +++ b/src/otsur/qtquick/UrImageProvider.cpp @@ -0,0 +1,29 @@ +#include +#include "UrImageProvider.h" +#include "UrSender.h" + +UrImageProvider::UrImageProvider() + : QQuickImageProvider(QQuickImageProvider::Image), m_sender(nullptr) +{ +} + +QImage UrImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + if (m_sender) + return m_sender->requestImage(id, size, requestedSize); + + QSize actualSize = requestedSize.isValid() ? requestedSize : QSize(300, 300); + if (size) + *size = actualSize; + + QImage image(actualSize, QImage::Format_ARGB32); + image.fill(Qt::red); // Fill with red background as a fallback + return image; +} + +void UrImageProvider::setSender(UrSender *sender) { + m_sender = sender; +} + +UrImageProvider::~UrImageProvider() { + // UrSender is not owned by UrImageProvider, so we don't delete it here +} diff --git a/src/otsur/qtquick/UrImageProvider.h b/src/otsur/qtquick/UrImageProvider.h new file mode 100644 index 0000000000..17f07c8790 --- /dev/null +++ b/src/otsur/qtquick/UrImageProvider.h @@ -0,0 +1,23 @@ +#ifndef URIMAGEPROVIDER_H +#define URIMAGEPROVIDER_H + +#include +#include + +class UrSender; + +class UrImageProvider : public QQuickImageProvider +{ +public: + explicit UrImageProvider(); + ~UrImageProvider(); + + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override; + void setSender(UrSender *sender); + UrSender* sender() const { return m_sender; } + +private: + UrSender *m_sender; +}; + +#endif // URIMAGEPROVIDER_H diff --git a/src/otsur/qtquick/UrRegister.cpp b/src/otsur/qtquick/UrRegister.cpp new file mode 100644 index 0000000000..58a6240504 --- /dev/null +++ b/src/otsur/qtquick/UrRegister.cpp @@ -0,0 +1,59 @@ +#include "UrRegister.h" + +#include +#include +#include "UrCodeScanner.h" +#include "UrSender.h" +#include "UrImageProvider.h" +#include +#include "qqmlcontext.h" + +namespace OtsUr { + + static UrSender* _urSender = nullptr; + + void registerTypes() + { + qmlRegisterType("OtsUr", 0, 1, "UrSender"); + qmlRegisterType("OtsUr", 0, 1, "UrCodeScannerImpl"); + qmlRegisterType("OtsUr", 0, 1, "MoneroData"); + qmlRegisterType("OtsUr", 0, 1, "MoneroTxData"); + qmlRegisterType("OtsUr", 0, 1, "MoneroWalletData"); + } + + void setupContext(QQmlApplicationEngine &engine) { + UrImageProvider *urcodeImageProvider = new UrImageProvider(); + UrSender *urSender = new UrSender(); + urcodeImageProvider->setSender(urSender); + engine.addImageProvider("urcode", urcodeImageProvider); + engine.rootContext()->setContextProperty("urSender", urSender); + } + + void setupCamera(QQmlApplicationEngine &engine) + { + QObject *urCamera = engine.rootObjects().first()->findChild("urCamera"); + if (!urCamera) + { + qCritical() << "UrCodeScanner : couldn't get camera !"; + return; + } + QCamera *camera = qvariant_cast(urCamera->property("mediaObject")); + if(!camera) { + qCritical() << "UrCodeScanner : couldn't cast camera !"; + return; + } + QObject *urScanner = engine.rootObjects().first()->findChild("urScanner"); + if(!urScanner) { + qCritical() << "UrCodeScanner : couldn't get scanner !"; + return; + } + UrCodeScanner *scanner = qobject_cast(urScanner); + if(!scanner) { + qCritical() << "UrCodeScanner : couldn't cast scanner !"; + return; + } + scanner->init(); + scanner->setSource(camera); + engine.rootContext()->setContextProperty("urScannerObj", scanner); + } +} diff --git a/src/otsur/qtquick/UrRegister.h b/src/otsur/qtquick/UrRegister.h new file mode 100644 index 0000000000..8d2a851dbb --- /dev/null +++ b/src/otsur/qtquick/UrRegister.h @@ -0,0 +1,13 @@ +#ifndef URREGISTER_H +#define URREGISTER_H + + +class QQmlApplicationEngine; + +namespace OtsUr { + void registerTypes(); + void setupContext(QQmlApplicationEngine &engine); + void setupCamera(QQmlApplicationEngine &engine); +} + +#endif // URREGISTER_H diff --git a/src/otsur/qtquick/UrSender.cpp b/src/otsur/qtquick/UrSender.cpp new file mode 100644 index 0000000000..83cc253023 --- /dev/null +++ b/src/otsur/qtquick/UrSender.cpp @@ -0,0 +1,164 @@ +#include +#include +#include +#include + +#include "UrSender.h" +#include + +UrSender::UrSender() + : QObject() +{ + connect(&m_timer, &QTimer::timeout, this, &UrSender::nextQR); +} + +void UrSender::sendClear() { + m_type = ""; + m_data = ""; + m_timer.stop(); + allParts.clear(); + m_qrcode = nullptr; + emit isUrCodeChanged(); +} + +void UrSender::setData(const QString &type, const QByteArray &data) { + m_type = type; + m_data = data.toStdString(); + emit isUrCodeChanged(); + + m_timer.stop(); + allParts.clear(); + emit updateCurrentFrameInfo(0, 0); + + if (m_data.empty()) + return; + + ur::ByteVector cbor; + ur::CborLite::encodeBytes(cbor, ur::string_to_bytes(m_data)); + ur::UR h = ur::UR(m_type.toStdString(), cbor); + + delete m_urencoder; + m_urencoder = new ur::UREncoder(h, m_fragmentLength); + + for (int i=0; i < m_urencoder->seq_len(); i++) { + allParts.append(m_urencoder->next_part()); + } + m_timer.setInterval(m_speed); + m_timer.start(); +} + +void UrSender::nextQR() { + currentIndex = currentIndex % m_urencoder->seq_len(); + + std::string data; + if (m_fountainCodeEnabled) { + data = m_urencoder->next_part(); + } else { + data = allParts[currentIndex]; + } + emit updateCurrentFrameInfo((currentIndex % m_urencoder->seq_len() + 1), m_urencoder->seq_len()); + m_qrcode = new QrCode{QString::fromStdString(data), QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM}; + emit updateQrCode(*m_qrcode); + m_currentFrameInfo = QString("%1/%2").arg((currentIndex % m_urencoder->seq_len() + 1)).arg(m_urencoder->seq_len()); + emit currentFrameInfoChanged(); + currentIndex++; +} + +void UrSender::onSettingsChanged(int fragmentLength, int speed, bool fountainCodeEnabled) { + m_fragmentLength = fragmentLength; + m_speed = speed; + m_fountainCodeEnabled = fountainCodeEnabled; +} + +QImage UrSender::requestImage(const QString &id, QSize *size, const QSize &requestedSize) { + Q_UNUSED(id) + + QSize actualSize = requestedSize.isValid() ? requestedSize : QSize(300, 300); + if (size) + *size = actualSize; + + QImage image(actualSize, QImage::Format_ARGB32); + image.fill(Qt::white); // Fill with white background + if(m_qrcode == nullptr) + return image; + if (!m_svg) { // render from Pixmap to image instead via SVG + image = m_qrcode->toPixmap(4).toImage(); + *size = image.size(); + return image; + } + QByteArray currentSvgData; + QBuffer buffer(¤tSvgData); + buffer.open(QIODevice::WriteOnly); + m_qrcode->writeSvg(&buffer, 1, 4); // Using 1 as DPI, we'll scale in the ImageProvider + buffer.close(); + QSvgRenderer renderer(currentSvgData); + QPainter painter(&image); + renderer.render(&painter); + return image; +} + + +void UrSender::sendOutputs(const QByteArray &outputs) { + setData(XMR_OUTPUT, outputs); +} + + +void UrSender::sendKeyImages(const QByteArray &keyImages) { + setData(XMR_KEY_IMAGE, keyImages); +} + + +void UrSender::sendTxUnsigned(const QByteArray &txUnisgned) { + setData(XMR_TX_UNSIGNED, txUnisgned); +} + + +void UrSender::sendTxSigned(const QByteArray &txSigned) { + setData(XMR_TX_SIGNED, txSigned); +} + +void UrSender::sendQrCode(const QString &qr) { + sendClear(); + m_qrcode = new QrCode{qr, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM}; + emit updateQrCode(*m_qrcode); +} + +void UrSender::sendTx(const QString &address, const QString &txAmount, const QString &txPaymentId, const QString &recipientName, const QString &txDescription) +{ + const QMap data = { + { "tx_amount", txAmount }, + { "tx_payment_id", txPaymentId }, + { "recipient_name", recipientName }, + { "tx_description", txDescription } + }; + sendQrCode(buildUri("monero", address, data)); +} + +void UrSender::sendWallet(const QString &address, const QString &spendKey, const QString &viewKey, const QString &mnemonicSeed, const long &height) +{ + const QMap data = { + { "spend_key", spendKey }, + { "view_key", viewKey }, + { "mnemonic_seed", mnemonicSeed }, + { "heigth", (height>0)?QString("%1").arg(height):"" } + }; + sendQrCode(buildUri("monero_wallet", address, data)); +} + +const QString UrSender::buildUri(const QString &scheme, const QString &address, const QMap &data) +{ + QString out = QString("%1:%2").arg(scheme, address); + bool first = true; + for (const auto& e : data.toStdMap()) { + if(e.second.size() > 0) { + out.append(QString("%1%2=%3").arg(first?"?":"&", e.first, e.second)); + first = false; + } + } + return out; +} + +UrSender::~UrSender() { + if(m_urencoder) + delete m_urencoder; +} diff --git a/src/otsur/qtquick/UrSender.h b/src/otsur/qtquick/UrSender.h new file mode 100644 index 0000000000..99d41e5144 --- /dev/null +++ b/src/otsur/qtquick/UrSender.h @@ -0,0 +1,75 @@ +#ifndef URSENDER_H +#define URSENDER_H + +#include +#include +#include + +class UrSender : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString currentFrameInfo READ currentFrameInfo NOTIFY currentFrameInfoChanged) + Q_PROPERTY(bool isUrCode READ isUrCode NOTIFY isUrCodeChanged) + +public: + explicit UrSender(); + ~UrSender(); + QString currentFrameInfo() const { return m_currentFrameInfo; } + QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize); + Q_INVOKABLE bool isUrCode() { return !m_data.empty(); } + Q_INVOKABLE void sendClear(); + Q_INVOKABLE void sendOutputs(const QByteArray &outputs); + Q_INVOKABLE void sendKeyImages(const QByteArray &keyImages); + Q_INVOKABLE void sendTxUnsigned(const QByteArray &txUnisgned); + Q_INVOKABLE void sendTxSigned(const QByteArray &txSigned); + Q_INVOKABLE void sendQrCode(const QString &qr); + Q_INVOKABLE void sendTx( + const QString &address, + const QString &txAmount = "", + const QString &txPaymentId = "", + const QString &recipientName = "", + const QString &txDescription = "" + ); + Q_INVOKABLE void sendWallet( + const QString &address, + const QString &spendKey = "", + const QString &viewKey = "", + const QString &mnemonicSeed = "", + const long &height = 0 + ); + +signals: + void updateQrCode(const QrCode &qrCode); + void updateCurrentFrameInfo(int current, int total); + void currentFrameInfoChanged(); + void noFrameInfo(); + void isUrCodeChanged(); + +public slots: + void onSettingsChanged(int fragmentLength, int speed, bool fountainCodeEnabled); + void setData(const QString &type, const QByteArray &data); + +private slots: + void nextQR(); + +private: + bool m_svg = true; // use SVG for reandering TODO: need to profile both methods + QTimer m_timer; + ur::UREncoder *m_urencoder = nullptr; + QList allParts; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + qsizetype currentIndex = 0; +#else + int currentIndex = 0; +#endif + + const QString buildUri(const QString &scheme, const QString &address, const QMap &data); + QrCode *m_qrcode = nullptr; + std::string m_data; + QString m_type; + int m_fragmentLength = 150; + int m_speed = 80; + bool m_fountainCodeEnabled = false; + QString m_currentFrameInfo; +}; +#endif // URSENDER_H diff --git a/wizard/WizardHome.qml b/wizard/WizardHome.qml index 9d65a7f0b6..c53adada27 100644 --- a/wizard/WizardHome.qml +++ b/wizard/WizardHome.qml @@ -155,6 +155,26 @@ Rectangle { } } + Rectangle { + Layout.preferredHeight: 1 + Layout.topMargin: 3 + Layout.bottomMargin: 3 + Layout.fillWidth: true + color: MoneroComponents.Style.dividerColor + opacity: MoneroComponents.Style.dividerOpacity + } + + WizardMenuItem { + headerText: qsTr("Restore a view wallet scannig a QR code") + translationManager.emptyString + bodyText: qsTr("Import an existing wallet from a monero URI or json representation of a wallet.") + translationManager.emptyString + imageIcon: "qrc:///images/restore-wallet-from-qr.png" + + onMenuClicked: { + urScannerUi.wallet.connect(wizardHome.onViewWalletFromQr) + urScannerUi.scanWallet() + } + } + RowLayout { Layout.fillWidth: true Layout.topMargin: 16 @@ -252,6 +272,25 @@ Rectangle { } } + function onViewWalletFromQr(walletData) { + disconnectScanner(); + console.warn("walletData: " + walletData) + if(!walletData || !walletData.isViewOnly) + return // handle only view only wallets here + wizardController.restart(); + wizardStateView.state = "wizardRestoreWallet1" + wizardStateView.wizardRestoreWallet1View.onWallet(walletData) + } + + function onViewWalletScanAbort() { + disconnectScanner(); + } + + function disconnectScanner() { + urScannerUi.wallet.disconnect(wizardHome.onViewWalletFromQr) + urScannerUi.canceled.disconnect(wizardHome.onViewWalletScanAbort) + } + function onPageCompleted(){ wizardController.walletOptionsIsRecoveringFromDevice = false; if (networkTypeDropdown.currentIndex != 0) { diff --git a/wizard/WizardRestoreWallet1.qml b/wizard/WizardRestoreWallet1.qml index e4fde84889..104429698e 100644 --- a/wizard/WizardRestoreWallet1.qml +++ b/wizard/WizardRestoreWallet1.qml @@ -152,8 +152,9 @@ Rectangle { seedRadioButton.checked = false; keysRadioButton.checked = false; wizardController.walletRestoreMode = 'qr'; - cameraUi.state = "Capture"; - cameraUi.qrcode_decoded.connect(Wizard.updateFromQrCode); + urScannerUi.wallet.connect(wizardRestoreWallet1.onWallet); + urScannerUi.canceled.connect(wizardRestoreWallet1.onScanCancel); + urScannerUi.scanWallet() } } } @@ -326,6 +327,46 @@ Rectangle { } } + function disconnectScanner() { + urScannerUi.wallet.disconnect(onWallet) + urScannerUi.canceled.disconnect(onScanCancel) + } + + function onScanCancel() { + disconnectScanner() + } + + function onWallet(walletData) { + disconnectScanner() + reset() + console.warn(walletData) + if(!walletData || !walletData.isValid) { + keysRadioButton.clicked() + return + } + if(walletData.isSeed) { + seedRadioButton.clicked() + seedInput.text = walletData.mnemonicSeed + restoreHeight.text = walletData.height + return + } + keysRadioButton.clicked() + addressLine.text = walletData.address + viewKeyLine.text = walletData.viewKey + spendKeyLine.text = walletData.spendKey + restoreHeight.text = walletData.height + } + + function reset() { + seedInput.text = ""; + seedOffsetCheckbox.checked = false; + seedOffset.text = ""; + addressLine.text = ""; + spendKeyLine.text = ""; + viewKeyLine.text = ""; + restoreHeight.text = ""; + } + function onPageCompleted(previousView){ if(previousView.viewName == "wizardHome"){ // cleanup @@ -333,13 +374,7 @@ Rectangle { seedRadioButton.checked = true; keysRadioButton.checked = false; qrRadioButton.checked = false; - seedInput.text = ""; - seedOffsetCheckbox.checked = false; - seedOffset.text = ""; - addressLine.text = ""; - spendKeyLine.text = ""; - viewKeyLine.text = ""; - restoreHeight.text = ""; + reset() } } }