mirror of https://github.com/certusone/dc4bc.git
commit
d108dc98ed
|
@ -216,7 +216,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
|
// 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 (
|
var (
|
||||||
err error
|
err error
|
||||||
|
|
||||||
|
@ -227,28 +227,38 @@ func (am *AirgappedMachine) HandleQR() (string, error) {
|
||||||
resultOperation client.Operation
|
resultOperation client.Operation
|
||||||
)
|
)
|
||||||
|
|
||||||
if qrData, err = am.qrProcessor.ReadQR(); err != nil {
|
if qrData, err = qr.ReadDataFromQRChunks(am.qrProcessor); err != nil {
|
||||||
return "", fmt.Errorf("failed to read QR: %w", err)
|
return nil, fmt.Errorf("failed to read QR: %w", err)
|
||||||
}
|
}
|
||||||
if err = json.Unmarshal(qrData, &operation); err != nil {
|
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 {
|
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)
|
operationBz, err := json.Marshal(resultOperation)
|
||||||
if err != nil {
|
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 {
|
chunks, err := qr.DataToChunks(operationBz)
|
||||||
return "", fmt.Errorf("failed to write QR")
|
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 {
|
func (am *AirgappedMachine) writeErrorRequestToOperation(o *client.Operation, handlerError error) error {
|
||||||
|
|
|
@ -39,7 +39,7 @@ type Poller interface {
|
||||||
SendMessage(message storage.Message) error
|
SendMessage(message storage.Message) error
|
||||||
ProcessMessage(message storage.Message) error
|
ProcessMessage(message storage.Message) error
|
||||||
GetOperations() (map[string]*types.Operation, error)
|
GetOperations() (map[string]*types.Operation, error)
|
||||||
GetOperationQRPath(operationID string) (string, error)
|
GetOperationQRPath(operationID string) ([]string, error)
|
||||||
ReadProcessedOperation() error
|
ReadProcessedOperation() error
|
||||||
StartHTTPServer(listenAddr string) error
|
StartHTTPServer(listenAddr string) error
|
||||||
GetLogger() *logger
|
GetLogger() *logger
|
||||||
|
@ -253,25 +253,36 @@ func (c *Client) getOperationJSON(operationID string) ([]byte, error) {
|
||||||
// GetOperationQRPath returns a path to the image with the QR generated
|
// GetOperationQRPath returns a path to the image with the QR generated
|
||||||
// for the specified operation. It is supposed that the user will open
|
// for the specified operation. It is supposed that the user will open
|
||||||
// this file herself.
|
// this file herself.
|
||||||
func (c *Client) GetOperationQRPath(operationID string) (string, error) {
|
func (c *Client) GetOperationQRPath(operationID string) ([]string, error) {
|
||||||
operationJSON, err := c.getOperationJSON(operationID)
|
operationJSON, err := c.getOperationJSON(operationID)
|
||||||
if err != nil {
|
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)
|
operationQRPath := filepath.Join(QrCodesDir, operationID)
|
||||||
if err := c.qrProcessor.WriteQR(operationQRPath, operationJSON); err != nil {
|
chunks, err := qr.DataToChunks(operationJSON)
|
||||||
return "", fmt.Errorf("failed to WriteQR: %w", err)
|
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
|
// ReadProcessedOperation reads the processed operation from camera, checks that
|
||||||
// the processed operation has its unprocessed counterpart in our state,
|
// the processed operation has its unprocessed counterpart in our state,
|
||||||
// posts a Message to the storage and deletes the operation from our state.
|
// posts a Message to the storage and deletes the operation from our state.
|
||||||
func (c *Client) ReadProcessedOperation() error {
|
func (c *Client) ReadProcessedOperation() error {
|
||||||
bz, err := c.qrProcessor.ReadQR()
|
bz, err := qr.ReadDataFromQRChunks(c.qrProcessor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to ReadQR: %s", err)
|
return fmt.Errorf("failed to ReadQR: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -123,13 +123,13 @@ func (c *Client) getOperationQRPathHandler(w http.ResponseWriter, r *http.Reques
|
||||||
}
|
}
|
||||||
operationID := r.URL.Query().Get("operationID")
|
operationID := r.URL.Query().Get("operationID")
|
||||||
|
|
||||||
qrPath, err := c.GetOperationQRPath(operationID)
|
qrPaths, err := c.GetOperationQRPath(operationID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get operation QR path: %v", err))
|
errorResponse(w, http.StatusInternalServerError, fmt.Sprintf("failed to get operation QR path: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
successResponse(w, qrPath)
|
successResponse(w, qrPaths)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) getOperationQRToBodyHandler(w http.ResponseWriter, r *http.Request) {
|
func (c *Client) getOperationQRToBodyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
|
@ -0,0 +1,79 @@
|
||||||
|
package qr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
|
const chunkSize = 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
|
||||||
|
}
|
2
qr/qr.go
2
qr/qr.go
|
@ -93,7 +93,7 @@ func ReadDataFromQR(img image.Image) ([]byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to decode the QR-code contents: %w", err)
|
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) {
|
func EncodeQR(data []byte) ([]byte, error) {
|
||||||
|
|
|
@ -0,0 +1,94 @@
|
||||||
|
package qr
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
encoder "github.com/skip2/go-qrcode"
|
||||||
|
"image"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestQrProcessor struct {
|
||||||
|
qrs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
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!")
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue