un-qr the agent

This commit is contained in:
Hendrik Hofstadt 2020-11-25 15:50:50 +01:00
parent fa69c1fb62
commit 38de47d012
11 changed files with 69 additions and 536 deletions

View File

@ -74,13 +74,11 @@ build-darwin:
build-linux:
@echo "Building dc4bc_d..."
GOOS=linux GOARCH=amd64 go build -o dc4bc_d_linux ./cmd/dc4bc_d/
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dc4bc_d_linux ./cmd/dc4bc_d/
@echo "Building dc4bc_cli..."
GOOS=linux GOARCH=amd64 go build -o dc4bc_cli_linux ./cmd/dc4bc_cli/
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dc4bc_cli_linux ./cmd/dc4bc_cli/
@echo "Building dc4bc_airgapped..."
GOOS=linux GOARCH=amd64 go build -o dc4bc_airgapped_linux ./cmd/airgapped/
@echo "Building dc4bc_prysm_compatibility_checker..."
GOOS=linux GOARCH=amd64 go build -o dc4bc_prysm_compatibility_checker_linux ./cmd/prysm_compatibility_checker/
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o dc4bc_airgapped_linux ./cmd/airgapped/
clean:
go clean --cache

View File

@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"log"
"path/filepath"
"sync"
vss "github.com/corestario/kyber/share/vss/rabin"
@ -18,7 +17,6 @@ import (
"github.com/lidofinance/dc4bc/fsm/state_machines/signature_proposal_fsm"
"github.com/lidofinance/dc4bc/fsm/state_machines/signing_proposal_fsm"
"github.com/lidofinance/dc4bc/fsm/types/requests"
"github.com/lidofinance/dc4bc/qr"
"github.com/syndtr/goleveldb/leveldb"
)
@ -30,7 +28,6 @@ type Machine struct {
sync.Mutex
dkgInstances map[string]*dkg.DKG
qrProcessor qr.Processor
encryptionKey []byte
pubKey kyber.Point
@ -49,7 +46,6 @@ func NewMachine(dbPath string) (*Machine, error) {
am := &Machine{
dkgInstances: make(map[string]*dkg.DKG),
qrProcessor: qr.NewCameraProcessor(),
}
if am.db, err = leveldb.OpenFile(dbPath, nil); err != nil {
@ -74,18 +70,6 @@ func NewMachine(dbPath string) (*Machine, error) {
return am, nil
}
func (am *Machine) SetQRProcessorFramesDelay(delay int) {
am.qrProcessor.SetDelay(delay)
}
func (am *Machine) CloseCameraReader() {
am.qrProcessor.CloseCameraReader()
}
func (am *Machine) SetQRProcessorChunkSize(chunkSize int) {
am.qrProcessor.SetChunkSize(chunkSize)
}
func (am *Machine) SetResultQRFolder(resultQRFolder string) {
am.resultQRFolder = resultQRFolder
}
@ -237,21 +221,17 @@ func (am *Machine) handleOperation(operation client.Operation) (client.Operation
}
// HandleQR - gets an operation from a QR code, do necessary things for the operation and returns paths to QR-code images
func (am *Machine) HandleQR() (string, error) {
func (am *Machine) HandleQR(data []byte) (string, error) {
var (
err error
// input operation
operation client.Operation
qrData []byte
resultOperation client.Operation
)
if qrData, err = am.qrProcessor.ReadQR(); err != nil {
return "", fmt.Errorf("failed to read QR: %w", err)
}
if err = json.Unmarshal(qrData, &operation); err != nil {
if err = json.Unmarshal(data, &operation); err != nil {
return "", fmt.Errorf("failed to unmarshal operation: %w", err)
}
@ -264,12 +244,7 @@ func (am *Machine) HandleQR() (string, error) {
return "", fmt.Errorf("failed to marshal operation: %w", err)
}
qrPath := filepath.Join(am.resultQRFolder, fmt.Sprintf("dc4bc_qr_%s-response.gif", resultOperation.ID))
if err = am.qrProcessor.WriteQR(qrPath, operationBz); err != nil {
return "", fmt.Errorf("failed to write QR: %w", err)
}
return qrPath, nil
return string(operationBz), nil
}
// writeErrorRequestToOperation writes error to a operation if some bad things happened

View File

@ -7,7 +7,6 @@ import (
"errors"
"fmt"
"log"
"path/filepath"
"sync"
"time"
@ -25,13 +24,11 @@ import (
"github.com/lidofinance/dc4bc/fsm/fsm"
dpf "github.com/lidofinance/dc4bc/fsm/state_machines/dkg_proposal_fsm"
"github.com/lidofinance/dc4bc/qr"
"github.com/lidofinance/dc4bc/storage"
)
const (
pollingPeriod = time.Second
QrCodesDir = "/tmp"
)
type Client interface {
@ -42,20 +39,18 @@ type Client interface {
SendMessage(message storage.Message) error
ProcessMessage(message storage.Message) error
GetOperations() (map[string]*types.Operation, error)
GetOperationQRPath(operationID string) (string, error)
StartHTTPServer(listenAddr string) error
}
type BaseClient struct {
sync.Mutex
Logger *logger
userName string
pubKey ed25519.PublicKey
ctx context.Context
state State
storage storage.Storage
keyStore KeyStore
qrProcessor qr.Processor
Logger *logger
userName string
pubKey ed25519.PublicKey
ctx context.Context
state State
storage storage.Storage
keyStore KeyStore
}
func NewClient(
@ -64,7 +59,6 @@ func NewClient(
state State,
storage storage.Storage,
keyStore KeyStore,
qrProcessor qr.Processor,
) (Client, error) {
keyPair, err := keyStore.LoadKeys(userName, "")
if err != nil {
@ -72,14 +66,13 @@ func NewClient(
}
return &BaseClient{
ctx: ctx,
Logger: newLogger(userName),
userName: userName,
pubKey: keyPair.Pub,
state: state,
storage: storage,
keyStore: keyStore,
qrProcessor: qrProcessor,
ctx: ctx,
Logger: newLogger(userName),
userName: userName,
pubKey: keyPair.Pub,
state: state,
storage: storage,
keyStore: keyStore,
}, nil
}
@ -322,25 +315,6 @@ func (c *BaseClient) getOperationJSON(operationID string) ([]byte, error) {
return operationJSON, nil
}
// GetOperationQRPath returns a path to the image with the QR generated
// for the specified operation. It is supposed that the user will open
// this file herself.
func (c *BaseClient) GetOperationQRPath(operationID string) (string, error) {
operationJSON, err := c.getOperationJSON(operationID)
if err != nil {
return "", fmt.Errorf("failed to get operation in JSON: %w", err)
}
operationQRPath := filepath.Join(QrCodesDir, fmt.Sprintf("dc4bc_qr_%s", operationID))
qrPath := fmt.Sprintf("%s.gif", operationQRPath)
if err = c.qrProcessor.WriteQR(qrPath, operationJSON); err != nil {
return "", err
}
return qrPath, nil
}
// handleProcessedOperation handles an operation which was processed by the airgapped machine
// It checks that the operation exists in an operation pool, signs the operation, sends it to an append-only log and
// deletes it from the pool.

View File

@ -17,7 +17,6 @@ import (
sif "github.com/lidofinance/dc4bc/fsm/state_machines/signing_proposal_fsm"
"github.com/lidofinance/dc4bc/fsm/types/requests"
"github.com/lidofinance/dc4bc/qr"
"github.com/lidofinance/dc4bc/storage"
)
@ -67,12 +66,10 @@ func (c *BaseClient) StartHTTPServer(listenAddr string) error {
mux.HandleFunc("/sendMessage", c.sendMessageHandler)
mux.HandleFunc("/getOperations", c.getOperationsHandler)
mux.HandleFunc("/getOperationQRPath", c.getOperationQRPathHandler)
mux.HandleFunc("/getSignatures", c.getSignaturesHandler)
mux.HandleFunc("/getSignatureByID", c.getSignatureByIDHandler)
mux.HandleFunc("/getOperationQR", c.getOperationQRToBodyHandler)
mux.HandleFunc("/handleProcessedOperationJSON", c.handleJSONOperationHandler)
mux.HandleFunc("/getOperation", c.getOperationHandler)
@ -252,22 +249,6 @@ func (c *BaseClient) getSignatureByIDHandler(w http.ResponseWriter, r *http.Requ
successResponse(w, signature)
}
func (c *BaseClient) getOperationQRPathHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
errorResponse(w, http.StatusBadRequest, "Wrong HTTP method")
return
}
operationID := r.URL.Query().Get("operationID")
qrPaths, err := c.GetOperationQRPath(operationID)
if err != nil {
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get operation QR path: %v", err))
return
}
successResponse(w, qrPaths)
}
func (c *BaseClient) getOperationHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
errorResponse(w, http.StatusBadRequest, "Wrong HTTP method")
@ -284,30 +265,6 @@ func (c *BaseClient) getOperationHandler(w http.ResponseWriter, r *http.Request)
successResponse(w, operation)
}
func (c *BaseClient) getOperationQRToBodyHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
errorResponse(w, http.StatusBadRequest, "Wrong HTTP method")
return
}
operationID := r.URL.Query().Get("operationID")
operationJSON, err := c.getOperationJSON(operationID)
if err != nil {
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get operation in JSON: %v", err))
return
}
encodedData, err := qr.EncodeQR(operationJSON)
if err != nil {
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to encode operation: %v", err))
return
}
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(encodedData)))
rawResponse(w, encodedData)
}
func (c *BaseClient) startDKGHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
errorResponse(w, http.StatusBadRequest, "Wrong HTTP method")

View File

@ -8,6 +8,7 @@ import (
"fmt"
"github.com/syndtr/goleveldb/leveldb"
"io"
"io/ioutil"
"log"
"os"
"os/signal"
@ -48,7 +49,7 @@ type prompt struct {
func NewPrompt(machine *airgapped.Machine) (*prompt, error) {
p := prompt{
reader: bufio.NewReader(os.Stdin),
reader: bufio.NewReaderSize(os.Stdin, 100000),
airgapped: machine,
commands: make(map[string]*promptCommand),
currentCommand: "",
@ -61,9 +62,9 @@ func NewPrompt(machine *airgapped.Machine) (*prompt, error) {
}
p.initTerminal()
p.addCommand("read_qr", &promptCommand{
commandHandler: p.readQRCommand,
description: "Reads QR chunks from camera, handle a decoded operation and returns paths to qr chunks of operation's result",
p.addCommand("read_op", &promptCommand{
commandHandler: p.readOPCommand,
description: "Reads a JSON file, handles a decoded operation and returns paths to qr chunks of operation's result",
})
p.addCommand("help", &promptCommand{
commandHandler: p.helpCommand,
@ -139,14 +140,32 @@ func (p *prompt) addCommand(name string, command *promptCommand) {
p.commands[name] = command
}
func (p *prompt) readQRCommand() error {
qrPath, err := p.airgapped.HandleQR()
func (p *prompt) readOPCommand() error {
p.print("> Enter the operation ID: ")
fileName, _, err := p.reader.ReadLine()
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}
operationFile := fmt.Sprintf("%s.json", string(fileName))
f, err := ioutil.ReadFile(operationFile)
if err != nil {
return fmt.Errorf("failed to open operation file %s: %w", operationFile, err)
}
opResponse, err := p.airgapped.HandleQR(f)
if err != nil {
return err
}
p.println("An operation in the read QR code handled successfully, a result operation saved by chunks in following qr codes:")
p.printf("Operation's chunk: %s\n", qrPath)
outFileName := fmt.Sprintf("%s_res.json", string(fileName))
err = ioutil.WriteFile(outFileName, []byte(opResponse), 0400)
if err != nil {
return fmt.Errorf("failed to write result to %s: %w", outFileName, err)
}
p.println("Success - ")
return nil
}
@ -221,7 +240,6 @@ func (p *prompt) changeConfigurationCommand() error {
if err != nil {
return fmt.Errorf("failed to parse new frames delay: %w", err)
}
p.airgapped.SetQRProcessorFramesDelay(framesDelay)
p.printf("Frames delay was changed to: %d\n", framesDelay)
}
@ -235,7 +253,6 @@ func (p *prompt) changeConfigurationCommand() error {
if err != nil {
return fmt.Errorf("failed to parse new chunk size: %w", err)
}
p.airgapped.SetQRProcessorChunkSize(chunkSize)
p.printf("Chunk size was changed to: %d\n", chunkSize)
}
@ -473,8 +490,6 @@ func main() {
if err != nil {
log.Fatalf("failed to init airgapped machine %v", err)
}
air.SetQRProcessorFramesDelay(framesDelay)
air.SetQRProcessorChunkSize(chunkSize)
air.SetResultQRFolder(qrCodesFolder)
c := make(chan os.Signal, 1)
@ -489,7 +504,6 @@ func main() {
go func() {
for range c {
if p.currentCommand == "read_qr" {
p.airgapped.CloseCameraReader()
continue
}
p.printf("Intercepting SIGINT, please type `exit` to stop the machine\n")

View File

@ -11,7 +11,6 @@ import (
"io/ioutil"
"log"
"net/http"
"path/filepath"
"sort"
"strconv"
"strings"
@ -24,7 +23,6 @@ import (
"github.com/lidofinance/dc4bc/client"
"github.com/lidofinance/dc4bc/fsm/types/requests"
"github.com/lidofinance/dc4bc/qr"
"github.com/spf13/cobra"
)
@ -270,26 +268,14 @@ func getOperationRequest(host string, operationID string) (*OperationResponse, e
func getOperationQRPathCommand() *cobra.Command {
return &cobra.Command{
Use: "get_operation_qr [operationID]",
Use: "write_operation [operationID]",
Args: cobra.ExactArgs(1),
Short: "returns path to QR codes which contains the operation",
Short: "returns path to a JSON file which contains the operation",
RunE: func(cmd *cobra.Command, args []string) error {
listenAddr, err := cmd.Flags().GetString(flagListenAddr)
if err != nil {
return fmt.Errorf("failed to read configuration: %v", err)
}
framesDelay, err := cmd.Flags().GetInt(flagFramesDelay)
if err != nil {
return fmt.Errorf("failed to read configuration: %w", err)
}
chunkSize, err := cmd.Flags().GetInt(flagChunkSize)
if err != nil {
return fmt.Errorf("failed to read configuration: %w", err)
}
qrCodeFolder, err := cmd.Flags().GetString(flagQRCodesFolder)
if err != nil {
return fmt.Errorf("failed to read configuration: %w", err)
}
operationID := args[0]
operation, err := getOperationRequest(listenAddr, operationID)
@ -300,19 +286,14 @@ func getOperationQRPathCommand() *cobra.Command {
return fmt.Errorf("failed to get operations: %s", operation.ErrorMessage)
}
operationQRPath := filepath.Join(qrCodeFolder, fmt.Sprintf("dc4bc_qr_%s-request", operationID))
qrPath := fmt.Sprintf("%s.gif", operationQRPath)
processor := qr.NewCameraProcessor()
processor.SetChunkSize(chunkSize)
processor.SetDelay(framesDelay)
if err = processor.WriteQR(qrPath, operation.Result); err != nil {
return fmt.Errorf("failed to save QR gif: %w", err)
outFileName := fmt.Sprintf("%s.json", args[0])
err = ioutil.WriteFile(outFileName, operation.Result, 0400)
if err != nil {
return fmt.Errorf("failed to write result to %s: %w", outFileName, err)
}
fmt.Printf("QR code was saved to: %s\n", qrPath)
fmt.Printf("wrote operation to %s\n", outFileName)
return nil
},
}
@ -459,21 +440,23 @@ func rawPostRequest(url string, contentType string, data []byte) (*client.Respon
func readOperationFromCameraCommand() *cobra.Command {
return &cobra.Command{
Use: "read_qr",
Short: "opens the camera and reads QR codes which should contain a processed operation",
Use: "read_op [operation-id]",
Args: cobra.ExactArgs(1),
Short: "reads the file operation-id_res.json which should contain a processed operation",
RunE: func(cmd *cobra.Command, args []string) error {
listenAddr, err := cmd.Flags().GetString(flagListenAddr)
if err != nil {
return fmt.Errorf("failed to read configuration: %v", err)
}
processor := qr.NewCameraProcessor()
data, err := processor.ReadQR()
opID := args[0]
d, err := ioutil.ReadFile(opID + "_res.json")
if err != nil {
return fmt.Errorf("failed to read data from QR: %w", err)
return fmt.Errorf("failed to read response: %w", err)
}
resp, err := rawPostRequest(fmt.Sprintf("http://%s/handleProcessedOperationJSON", listenAddr),
"application/json", data)
"application/json", d)
if err != nil {
return fmt.Errorf("failed to handle processed operation: %w", err)
}

View File

@ -10,7 +10,6 @@ import (
"syscall"
"github.com/lidofinance/dc4bc/client"
"github.com/lidofinance/dc4bc/qr"
"github.com/lidofinance/dc4bc/storage"
"github.com/spf13/cobra"
@ -160,13 +159,7 @@ func startClientCommand() *cobra.Command {
return fmt.Errorf("failed to init key store: %w", err)
}
framesDelay := viper.GetInt(flagFramesDelay)
chunkSize := viper.GetInt(flagChunkSize)
processor := qr.NewCameraProcessor()
processor.SetDelay(framesDelay)
processor.SetChunkSize(chunkSize)
cli, err := client.NewClient(ctx, username, state, stg, keyStore, processor)
cli, err := client.NewClient(ctx, username, state, stg, keyStore)
if err != nil {
return fmt.Errorf("failed to init client: %w", err)
}

11
go.sum
View File

@ -36,6 +36,7 @@ github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxB
github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM=
github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc=
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
@ -134,8 +135,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/corestario/kyber v1.6.0 h1:ix91T0CMHjT2dlLaJbg5e/qYOG/Fvx0YHC4qdiE8BxY=
github.com/corestario/kyber v1.6.0/go.mod h1:8seqKJ5KGwEPN98iYQLoKFulfFn90ZxcdKSxb29A5XM=
github.com/corestario/kyber v1.6.1-0.20201110123848-0eac241a9f75 h1:n7+2NN6aB+ZE/eM5+1E1KFO7tASgdohQ97OWHORgogM=
github.com/corestario/kyber v1.6.1-0.20201110123848-0eac241a9f75/go.mod h1:8seqKJ5KGwEPN98iYQLoKFulfFn90ZxcdKSxb29A5XM=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
@ -326,6 +325,7 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1-0.20190629185528-ae1634f6a989/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
@ -455,6 +455,7 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jsternberg/zap-logfmt v1.0.0/go.mod h1:uvPs/4X51zdkcm5jXl5SYoN+4RK21K8mysFmDaM/h+o=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
github.com/juju/fslock v0.0.0-20160525022230-4d5c94c67b4b h1:FQ7+9fxhyp82ks9vAuyPzG0/vVbWwMwLJ+P6yJI5FN8=
@ -633,8 +634,6 @@ github.com/libp2p/go-yamux v1.3.3/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZ
github.com/libp2p/go-yamux v1.3.5/go.mod h1:FGTiPvoV/3DVdgWpX+tM0OW3tsM+W5bSE3gZwqQTcow=
github.com/libp2p/go-yamux v1.3.7/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE=
github.com/libp2p/go-yamux v1.3.8/go.mod h1:fr7aVgmdNGJK+N1g+b6DW6VxzbRCjCOejR/hkmpooHE=
github.com/lidofinance/kyber-bls12381 v0.0.0-20200929134032-c24859b7d890 h1:ra3VcXLAwGdHzcPRXkDVVr2Gb9wpi+XHyljk0J566vs=
github.com/lidofinance/kyber-bls12381 v0.0.0-20200929134032-c24859b7d890/go.mod h1:82QP3olqMtRnlRCNxEc9/EKk1qlFCOklxasHvSnXMSI=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
@ -901,7 +900,9 @@ github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smola/gocompat v0.2.0/go.mod h1:1B0MlxbmoZNo3h8guHp8HztB3BSYR5itql9qtVc0ypY=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
@ -962,8 +963,6 @@ github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca h1:Ld/zXl5t4+D6
github.com/syndtr/goleveldb v1.0.1-0.20200815110645-5c35d600f0ca/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=
github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=
github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5 h1:u8i49c+BxloX3XQ55cvzFNXplizZP/q00i+IlttUjAU=
github.com/tendermint/crypto v0.0.0-20180820045704-3764759f34a5/go.mod h1:z4YtwM70uOnk8h0pjJYlj3zdYwi9l03By6iAIF5j/Pk=
github.com/tinylib/msgp v1.0.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
github.com/tjfoc/gmsm v1.3.0/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=

View File

@ -1,53 +0,0 @@
package qr
import (
"encoding/json"
"fmt"
"math"
)
type chunk struct {
Data []byte
Index uint
Total uint
}
// DataToChunks divides a data on chunks with a size chunkSize
func DataToChunks(data []byte, chunkSize int) ([][]byte, error) {
chunksCount := int(math.Ceil(float64(len(data)) / float64(chunkSize)))
chunks := make([][]byte, 0, chunksCount)
index := uint(0)
for offset := 0; offset < len(data); offset += chunkSize {
offsetEnd := offset + chunkSize
if offsetEnd > len(data) {
offsetEnd = len(data)
}
chunkBz, err := encodeChunk(chunk{
Data: data[offset:offsetEnd],
Total: uint(chunksCount),
Index: index,
})
if err != nil {
return nil, fmt.Errorf("failed to encode chunk: %w", err)
}
chunks = append(chunks, chunkBz)
index++
}
return chunks, nil
}
func decodeChunk(data []byte) (*chunk, error) {
var (
c chunk
err error
)
if err = json.Unmarshal(data, &c); err != nil {
return nil, err
}
return &c, nil
}
func encodeChunk(c chunk) ([]byte, error) {
return json.Marshal(c)
}

179
qr/qr.go
View File

@ -1,179 +0,0 @@
package qr
import (
"fmt"
"image"
"image/color"
"image/draw"
"image/gif"
"log"
"os"
"gocv.io/x/gocv"
encoder "github.com/skip2/go-qrcode"
"github.com/makiuchi-d/gozxing"
"github.com/makiuchi-d/gozxing/qrcode"
)
var palette = color.Palette{
image.Transparent,
image.Black,
image.White,
color.RGBA{G: 255, A: 255},
color.RGBA{G: 100, A: 255},
}
type Processor interface {
ReadQR() ([]byte, error)
WriteQR(path string, data []byte) error
SetDelay(delay int)
SetChunkSize(chunkSize int)
CloseCameraReader()
}
type CameraProcessor struct {
gifFramesDelay int
chunkSize int
closeCameraReader chan bool
}
func NewCameraProcessor() Processor {
return &CameraProcessor{
closeCameraReader: make(chan bool),
}
}
func (p *CameraProcessor) CloseCameraReader() {
p.closeCameraReader <- true
}
func (p *CameraProcessor) SetDelay(delay int) {
p.gifFramesDelay = delay
}
func (p *CameraProcessor) SetChunkSize(chunkSize int) {
p.chunkSize = chunkSize
}
func (p *CameraProcessor) ReadQR() ([]byte, error) {
webcam, err := gocv.OpenVideoCapture(0)
if err != nil {
return nil, fmt.Errorf("failed to OpenVideoCapture: %w", err)
}
window := gocv.NewWindow("Please, show a gif with QR codes")
defer func() {
if err := webcam.Close(); err != nil {
log.Fatalf("failed to close camera: %v", err)
}
}()
defer func() {
if err := window.Close(); err != nil {
log.Fatalf("failed to close camera window: %v", err)
}
}()
img := gocv.NewMat()
defer img.Close()
chunks := make([]*chunk, 0)
decodedChunksCount := uint(0)
// detects and scans QR-cods from camera until we scan successfully
READER:
for {
select {
case <-p.closeCameraReader:
return nil, fmt.Errorf("camera reader was closed")
default:
webcam.Read(&img)
window.IMShow(img)
window.WaitKey(1)
imgObject, err := img.ToImage()
if err != nil {
return nil, fmt.Errorf("failed to get image object: %w", err)
}
data, err := ReadDataFromQR(imgObject)
if err != nil {
continue
}
decodedChunk, err := decodeChunk(data)
if err != nil {
return nil, err
}
if cap(chunks) == 0 {
chunks = make([]*chunk, decodedChunk.Total)
}
if decodedChunk.Index > decodedChunk.Total {
return nil, fmt.Errorf("invalid QR-code chunk")
}
if chunks[decodedChunk.Index] != nil {
continue
}
chunks[decodedChunk.Index] = decodedChunk
decodedChunksCount++
window.SetWindowTitle(fmt.Sprintf("Read %d/%d chunks", decodedChunksCount, decodedChunk.Total))
if decodedChunksCount == decodedChunk.Total {
break READER
}
}
}
window.SetWindowTitle("QR-code chunks successfully read!")
data := make([]byte, 0)
for _, c := range chunks {
data = append(data, c.Data...)
}
return data, nil
}
func (p *CameraProcessor) WriteQR(path string, data []byte) error {
chunks, err := DataToChunks(data, p.chunkSize)
if err != nil {
return fmt.Errorf("failed to divide data on chunks: %w", err)
}
outGif := &gif.GIF{}
for _, c := range chunks {
code, err := encoder.New(string(c), encoder.Medium)
if err != nil {
return fmt.Errorf("failed to create a QR code: %w", err)
}
frame := code.Image(512)
bounds := frame.Bounds()
palettedImage := image.NewPaletted(bounds, palette)
draw.Draw(palettedImage, palettedImage.Rect, frame, bounds.Min, draw.Src)
outGif.Image = append(outGif.Image, palettedImage)
outGif.Delay = append(outGif.Delay, p.gifFramesDelay)
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()
if err := gif.EncodeAll(f, outGif); err != nil {
return fmt.Errorf("failed to encode qr gif: %w", err)
}
return nil
}
func ReadDataFromQR(img image.Image) ([]byte, error) {
bmp, err := gozxing.NewBinaryBitmapFromImage(img)
if err != nil {
return nil, fmt.Errorf("failed to get NewBinaryBitmapFromImage: %w", err)
}
qrReader := qrcode.NewQRCodeReader()
result, err := qrReader.Decode(bmp, nil)
if err != nil {
return nil, fmt.Errorf("failed to decode the QR-code contents: %w", err)
}
return []byte(result.String()), nil
}
func EncodeQR(data []byte) ([]byte, error) {
return encoder.Encode(string(data), encoder.Medium, 512)
}

View File

@ -1,128 +0,0 @@
package qr
import (
"fmt"
encoder "github.com/skip2/go-qrcode"
"image"
"image/draw"
"image/gif"
"math/rand"
"os"
"reflect"
"testing"
)
type TestQrProcessor struct {
qr string
chunkSize int
}
func NewTestQRProcessor() *TestQrProcessor {
return &TestQrProcessor{}
}
func (p *TestQrProcessor) ReadQR() ([]byte, error) {
file, err := os.Open(p.qr)
if err != nil {
return nil, err
}
defer file.Close()
decodedGIF, err := gif.DecodeAll(file)
if err != nil {
return nil, err
}
chunks := make([]*chunk, 0)
decodedChunksCount := uint(0)
for _, frame := range decodedGIF.Image {
data, err := ReadDataFromQR(frame)
if err != nil {
continue
}
decodedChunk, err := decodeChunk(data)
if err != nil {
return nil, err
}
if cap(chunks) == 0 {
chunks = make([]*chunk, decodedChunk.Total)
}
if chunks[decodedChunk.Index] != nil {
continue
}
chunks[decodedChunk.Index] = decodedChunk
decodedChunksCount++
if decodedChunksCount == decodedChunk.Total {
break
}
}
data := make([]byte, 0)
for _, c := range chunks {
data = append(data, c.Data...)
}
if err = os.Remove(p.qr); err != nil {
return nil, err
}
return data, nil
}
func (p *TestQrProcessor) WriteQR(path string, data []byte) error {
chunks, err := DataToChunks(data, p.chunkSize)
if err != nil {
return fmt.Errorf("failed to divide data on chunks: %w", err)
}
outGif := &gif.GIF{}
for _, c := range chunks {
code, err := encoder.New(string(c), encoder.Medium)
if err != nil {
return fmt.Errorf("failed to create a QR code: %w", err)
}
frame := code.Image(512)
bounds := frame.Bounds()
palettedImage := image.NewPaletted(bounds, palette)
draw.Draw(palettedImage, palettedImage.Rect, frame, bounds.Min, draw.Src)
outGif.Image = append(outGif.Image, palettedImage)
outGif.Delay = append(outGif.Delay, 10)
}
f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600)
if err != nil {
return fmt.Errorf("failed to open file: %w", err)
}
defer f.Close()
if err := gif.EncodeAll(f, outGif); err != nil {
return fmt.Errorf("failed to encode qr gif: %w", err)
}
p.qr = path
return nil
}
func genBytes(n int) []byte {
data := make([]byte, n)
if _, err := rand.Read(data); err != nil {
return nil
}
return data
}
func TestReadDataFromQRChunks(t *testing.T) {
N := 5000
data := genBytes(N)
p := NewTestQRProcessor()
p.chunkSize = 128
if err := p.WriteQR("/tmp/test_gif.gif", data); err != nil {
t.Fatalf(err.Error())
}
recoveredDataFromQRChunks, err := p.ReadQR()
if err != nil {
t.Fatalf(err.Error())
}
if !reflect.DeepEqual(data, recoveredDataFromQRChunks) {
t.Fatal("recovered data from chunks and initial data are not equal!")
}
}