From 033c68f373840a319b418781d1c5b351da8adf44 Mon Sep 17 00:00:00 2001 From: programmer10110 Date: Tue, 1 Sep 2020 18:06:37 +0300 Subject: [PATCH 1/2] WIP: qr chunks --- airgapped/airgapped.go | 30 +++++++++----- airgapped/airgapped_test.go | 10 ++--- client/client.go | 23 ++++++++--- client/http_server.go | 7 ++-- qr/chunk.go | 79 +++++++++++++++++++++++++++++++++++++ qr/qr.go | 2 +- qr/qr_test.go | 29 ++++++++++++++ 7 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 qr/chunk.go create mode 100644 qr/qr_test.go diff --git a/airgapped/airgapped.go b/airgapped/airgapped.go index 2ba6589..d4899e7 100644 --- a/airgapped/airgapped.go +++ b/airgapped/airgapped.go @@ -214,7 +214,7 @@ func (am *AirgappedMachine) HandleOperation(operation client.Operation) (client. } // HandleQR - gets an operation from a QR code, do necessary things for the operation and returns paths to QR-code images -func (am *AirgappedMachine) HandleQR() (string, error) { +func (am *AirgappedMachine) HandleQR() ([]string, error) { var ( err error @@ -225,28 +225,38 @@ func (am *AirgappedMachine) HandleQR() (string, error) { resultOperation client.Operation ) - if qrData, err = am.qrProcessor.ReadQR(); err != nil { - return "", fmt.Errorf("failed to read QR: %w", err) + if qrData, err = qr.ReadDataFromQRChunks(am.qrProcessor); err != nil { + return nil, fmt.Errorf("failed to read QR: %w", err) } if err = json.Unmarshal(qrData, &operation); err != nil { - return "", fmt.Errorf("failed to unmarshal operation: %w", err) + return nil, fmt.Errorf("failed to unmarshal operation: %w", err) } if resultOperation, err = am.HandleOperation(operation); err != nil { - return "", err + return nil, err } - qrPath := fmt.Sprintf("%s/%s_%s_%s.png", resultQRFolder, resultOperation.Type, resultOperation.ID, resultOperation.To) operationBz, err := json.Marshal(resultOperation) if err != nil { - return "", fmt.Errorf("failed to marshal operation: %w", err) + return nil, fmt.Errorf("failed to marshal operation: %w", err) } - if err = am.qrProcessor.WriteQR(qrPath, operationBz); err != nil { - return "", fmt.Errorf("failed to write QR") + chunks, err := qr.DataToChunks(operationBz) + if err != nil { + return nil, fmt.Errorf("failed to divide a data on chunks: %w", err) + } + qrPaths := make([]string, 0, len(chunks)) + + for idx, chunk := range chunks { + qrPath := fmt.Sprintf("%s/%s_%s_%s-%d.png", resultQRFolder, resultOperation.Type, resultOperation.ID, + resultOperation.To, idx) + if err = am.qrProcessor.WriteQR(qrPath, chunk); err != nil { + return nil, fmt.Errorf("failed to write QR") + } + qrPaths = append(qrPaths, qrPath) } - return qrPath, nil + return qrPaths, nil } func (am *AirgappedMachine) writeErrorRequestToOperation(o *client.Operation, handlerError error) error { diff --git a/airgapped/airgapped_test.go b/airgapped/airgapped_test.go index e09f549..fb78e7d 100644 --- a/airgapped/airgapped_test.go +++ b/airgapped/airgapped_test.go @@ -31,7 +31,7 @@ type Node struct { deals []requests.DKGProposalDealConfirmationRequest responses []requests.DKGProposalResponseConfirmationRequest masterKeys []requests.DKGProposalMasterKeyConfirmationRequest - partialSigns []requests.SigningProposalPartialKeyRequest + partialSigns []requests.SigningProposalPartialSignRequest } func (n *Node) storeOperation(t *testing.T, msg storage.Message) { @@ -60,8 +60,8 @@ func (n *Node) storeOperation(t *testing.T, msg storage.Message) { t.Fatalf("failed to unmarshal fsm req: %v", err) } n.masterKeys = append(n.masterKeys, req) - case signing_proposal_fsm.EventSigningPartialKeyReceived: - var req requests.SigningProposalPartialKeyRequest + case signing_proposal_fsm.EventSigningPartialSignReceived: + var req requests.SigningProposalPartialSignRequest if err := json.Unmarshal(msg.Data, &req); err != nil { t.Fatalf("failed to unmarshal fsm req: %v", err) } @@ -259,7 +259,7 @@ func TestAirgappedAllSteps(t *testing.T) { SrcPayload: msgToSign, } - op := createOperation(t, string(signing_proposal_fsm.StateSigningAwaitPartialKeys), "", payload) + op := createOperation(t, string(signing_proposal_fsm.StateSigningAwaitPartialSigns), "", payload) operation, err := n.Machine.HandleOperation(op) if err != nil { @@ -284,7 +284,7 @@ func TestAirgappedAllSteps(t *testing.T) { payload.Participants = append(payload.Participants, &p) } payload.SrcPayload = msgToSign - op := createOperation(t, string(signing_proposal_fsm.StateSigningPartialKeysCollected), "", payload) + op := createOperation(t, string(signing_proposal_fsm.StateSigningPartialSignsCollected), "", payload) operation, err := n.Machine.HandleOperation(op) if err != nil { diff --git a/client/client.go b/client/client.go index 739dc11..b2f41d0 100644 --- a/client/client.go +++ b/client/client.go @@ -263,25 +263,36 @@ func (c *Client) getOperationJSON(operationID string) ([]byte, error) { // 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 *Client) GetOperationQRPath(operationID string) (string, error) { +func (c *Client) GetOperationQRPath(operationID string) ([]string, error) { operationJSON, err := c.getOperationJSON(operationID) if err != nil { - return "", fmt.Errorf("failed to get operation in JSON: %w", err) + return nil, fmt.Errorf("failed to get operation in JSON: %w", err) } operationQRPath := filepath.Join(QrCodesDir, operationID) - if err := c.qrProcessor.WriteQR(operationQRPath, operationJSON); err != nil { - return "", fmt.Errorf("failed to WriteQR: %w", err) + chunks, err := qr.DataToChunks(operationJSON) + if err != nil { + return nil, fmt.Errorf("failed to divide a data on chunks: %w", err) } - return operationQRPath, nil + qrs := make([]string, 0, len(chunks)) + + for idx, chunk := range chunks { + qrPath := fmt.Sprintf("%s-%d", operationQRPath, idx) + if err = c.qrProcessor.WriteQR(qrPath, chunk); err != nil { + return nil, err + } + qrs = append(qrs, qrPath) + } + + return qrs, nil } // ReadProcessedOperation reads the processed operation from camera, checks that // the processed operation has its unprocessed counterpart in our state, // posts a Message to the storage and deletes the operation from our state. func (c *Client) ReadProcessedOperation() error { - bz, err := c.qrProcessor.ReadQR() + bz, err := qr.ReadDataFromQRChunks(c.qrProcessor) if err != nil { return fmt.Errorf("failed to ReadQR: %s", err) } diff --git a/client/http_server.go b/client/http_server.go index f6782ea..f825c0c 100644 --- a/client/http_server.go +++ b/client/http_server.go @@ -93,13 +93,14 @@ func (c *Client) getOperationQRPathHandler(w http.ResponseWriter, r *http.Reques } operationID := r.URL.Query().Get("operationID") - qrPath, err := c.GetOperationQRPath(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, []byte(qrPath)) + for _, qrPath := range qrPaths { + successResponse(w, []byte(qrPath)) + } } func (c *Client) getOperationQRToBodyHandler(w http.ResponseWriter, r *http.Request) { diff --git a/qr/chunk.go b/qr/chunk.go new file mode 100644 index 0000000..2f67d7b --- /dev/null +++ b/qr/chunk.go @@ -0,0 +1,79 @@ +package qr + +import ( + "bytes" + "encoding/gob" + "fmt" + "math" +) + +const chunkSize = 3 * 1024 + +type chunk struct { + Data []byte + IsFinal bool +} + +func ReadDataFromQRChunks(p Processor) ([]byte, error) { + var ( + fullData, chunkBz []byte + err error + ) + for { + chunkBz, err = p.ReadQR() + if err != nil { + return nil, err + } + chunk, err := decodeChunk(chunkBz) + if err != nil { + return nil, fmt.Errorf("failed to decode chunk: %w", err) + } + fullData = append(fullData, chunk.Data...) + if chunk.IsFinal { + return fullData, nil + } + } +} + +func DataToChunks(data []byte) ([][]byte, error) { + chunksCount := int(math.Ceil(float64(len(data)) / chunkSize)) + chunks := make([][]byte, 0, chunksCount) + + for offset := 0; offset < len(data); offset += chunkSize { + offsetEnd := offset + chunkSize + if offsetEnd > len(data) { + offsetEnd = len(data) + } + isFinal := offsetEnd == len(data) + chunkBz, err := encodeChunk(chunk{ + Data: data[offset:offsetEnd], + IsFinal: isFinal, + }) + if err != nil { + return nil, fmt.Errorf("failed to encode chunk: %w", err) + } + chunks = append(chunks, chunkBz) + } + return chunks, nil +} + +func decodeChunk(data []byte) (*chunk, error) { + var ( + c chunk + err error + ) + dec := gob.NewDecoder(bytes.NewBuffer(data)) + if err = dec.Decode(&c); err != nil { + return nil, fmt.Errorf("failed to decode chunk: %w", err) + } + return &c, nil +} + +func encodeChunk(c chunk) ([]byte, error) { + buf := bytes.NewBuffer(nil) + enc := gob.NewEncoder(buf) + if err := enc.Encode(c); err != nil { + return nil, fmt.Errorf("failed to encode chunk: %w", err) + } + return buf.Bytes(), nil +} diff --git a/qr/qr.go b/qr/qr.go index 6e98b72..aedb4e2 100644 --- a/qr/qr.go +++ b/qr/qr.go @@ -92,7 +92,7 @@ func ReadDataFromQR(img image.Image) ([]byte, error) { if err != nil { return nil, fmt.Errorf("failed to decode the QR-code contents: %w", err) } - return result.GetRawBytes(), nil + return []byte(result.String()), nil } func EncodeQR(data []byte) ([]byte, error) { diff --git a/qr/qr_test.go b/qr/qr_test.go new file mode 100644 index 0000000..5662154 --- /dev/null +++ b/qr/qr_test.go @@ -0,0 +1,29 @@ +package qr + +import ( + "fmt" + "testing" +) + +func TestWriteChunks(t *testing.T) { + data := []byte("hello i am a string for testing please don't delete me") + p := NewCameraProcessor() + chunks, err := DataToChunks(data) + if err != nil { + t.Fatal(err.Error()) + } + for idx, chunk := range chunks { + if err := p.WriteQR(fmt.Sprintf("%d.png", idx), chunk); err != nil { + t.Fatal(err.Error()) + } + } +} + +func TestReadChunks(t *testing.T) { + p := NewCameraProcessor() + data, err := ReadDataFromQRChunks(p) + if err != nil { + t.Fatal(err.Error()) + } + fmt.Println(string(data)) +} From 33f4cfc56413575f63c7b199ccf536126e3e83af Mon Sep 17 00:00:00 2001 From: programmer10110 Date: Wed, 2 Sep 2020 13:46:27 +0300 Subject: [PATCH 2/2] tests --- qr/chunk.go | 2 +- qr/qr_test.go | 103 ++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/qr/chunk.go b/qr/chunk.go index 2f67d7b..7b8f964 100644 --- a/qr/chunk.go +++ b/qr/chunk.go @@ -7,7 +7,7 @@ import ( "math" ) -const chunkSize = 3 * 1024 +const chunkSize = 1024 type chunk struct { Data []byte diff --git a/qr/qr_test.go b/qr/qr_test.go index 5662154..715d168 100644 --- a/qr/qr_test.go +++ b/qr/qr_test.go @@ -2,28 +2,93 @@ package qr import ( "fmt" + encoder "github.com/skip2/go-qrcode" + "image" + "math/rand" + "os" + "reflect" "testing" ) -func TestWriteChunks(t *testing.T) { - data := []byte("hello i am a string for testing please don't delete me") - p := NewCameraProcessor() - chunks, err := DataToChunks(data) - if err != nil { - t.Fatal(err.Error()) - } - for idx, chunk := range chunks { - if err := p.WriteQR(fmt.Sprintf("%d.png", idx), chunk); err != nil { - t.Fatal(err.Error()) - } - } +type TestQrProcessor struct { + qrs []string } -func TestReadChunks(t *testing.T) { - p := NewCameraProcessor() - data, err := ReadDataFromQRChunks(p) - if err != nil { - t.Fatal(err.Error()) - } - fmt.Println(string(data)) +func NewTestQRProcessor() *TestQrProcessor { + return &TestQrProcessor{} +} + +func (p *TestQrProcessor) ReadQR() ([]byte, error) { + if len(p.qrs) == 0 { + return nil, fmt.Errorf("qr not found") + } + qr := p.qrs[0] + file, err := os.Open(qr) + if err != nil { + return nil, err + } + defer file.Close() + + img, _, err := image.Decode(file) + if err != nil { + return nil, err + } + + data, err := ReadDataFromQR(img) + if err != nil { + return nil, err + } + + p.qrs = p.qrs[1:] + if err = os.Remove(qr); err != nil { + return nil, err + } + return data, nil +} + +func (p *TestQrProcessor) WriteQR(path string, data []byte) error { + err := encoder.WriteFile(string(data), encoder.Medium, 512, path) + if err != nil { + return fmt.Errorf("failed to encode the data: %w", err) + } + + p.qrs = append(p.qrs, 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) + + chunks, err := DataToChunks(data) + if err != nil { + t.Fatalf(err.Error()) + } + + p := NewTestQRProcessor() + + for idx, chunk := range chunks { + path := fmt.Sprintf("/tmp/%d.png", idx) + if err = p.WriteQR(path, chunk); err != nil { + t.Fatalf(err.Error()) + } + } + + recoveredDataFromQRChunks, err := ReadDataFromQRChunks(p) + if err != nil { + t.Fatalf(err.Error()) + } + + if !reflect.DeepEqual(data, recoveredDataFromQRChunks) { + t.Fatal("recovered data from chunks and initial data are not equal!") + } }