cashless2ecash

cashless2ecash: pay with cards for digital cash (experimental)
Log | Files | Refs | README

commit e6a4d687e8290d15900c743af6538a67643b5130
parent f061d4f2739b838a11743716fcd311d322d7c2fb
Author: Joel-Haeberli <haebu@rubigen.ch>
Date:   Mon, 20 May 2024 12:00:07 +0200

fix: taler conformant Amount format

Diffstat:
Mcli/cli.go | 88++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcli/db.go | 1+
Acli/encoding.go | 127+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcli/go.mod | 2++
Msimulation/c2ec-simulation | 0
Mwallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt | 2+-
Mwallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/model/TerminalsApiModel.kt | 8++++----
7 files changed, 220 insertions(+), 8 deletions(-)

diff --git a/cli/cli.go b/cli/cli.go @@ -11,8 +11,10 @@ import ( "os" "strconv" "strings" + "time" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgxpool" "golang.org/x/crypto/argon2" ) @@ -22,6 +24,7 @@ const ACTION_REGISTER_PROVIDER = "rp" const ACTION_REGISTER_TERMINAL = "rt" const ACTION_DEACTIVATE_TERMINAL = "dt" const ACTION_ACTIVATE_TERMINAL = "at" +const ACTION_WITHDRAWAL_INFOMRATION = "w" const ACTION_CONNECT_DB = "db" const ACTION_QUIT = "q" @@ -32,7 +35,7 @@ type WalleeCredentials struct { ApplicationUserKey string `json:"application-user-key"` } -var DB *pgx.Conn +var DB *pgxpool.Pool // enter database credentials (host, port, database, username, password) // register terminal -> read password, hash, save to database @@ -235,6 +238,70 @@ func activateTerminal() error { return nil } +func withdrawalInformation() error { + + if DB == nil { + return errors.New("connect to the database first (cmd: db)") + } + + wopid := read("WOPID (encoded): ") + wopidDecoded, err := decodeCrock(wopid) + if err != nil { + return err + } + rows, err := DB.Query( + context.Background(), + GET_WITHDRAWAL_BY_WOPID, + wopidDecoded, + ) + if err != nil { + return err + } + + type TalerAmountCurrency struct { + Val int64 `db:"val"` + Frac int32 `db:"frac"` + Curr string `db:"curr"` + } + type Withdrawal struct { + WithdrawalRowId uint64 `db:"withdrawal_row_id"` + RequestUid string `db:"request_uid"` + Wopid []byte `db:"wopid"` + ReservePubKey []byte `db:"reserve_pub_key"` + RegistrationTs int64 `db:"registration_ts"` + Amount *TalerAmountCurrency `db:"amount" scan:"follow"` + SuggestedAmount *TalerAmountCurrency `db:"suggested_amount" scan:"follow"` + TerminalFees *TalerAmountCurrency `db:"terminal_fees" scan:"follow"` + WithdrawalStatus string `db:"withdrawal_status"` + TerminalId int `db:"terminal_id"` + ProviderTransactionId *string `db:"provider_transaction_id"` + LastRetryTs *int64 `db:"last_retry_ts"` + RetryCounter int32 `db:"retry_counter"` + CompletionProof []byte `db:"completion_proof"` + } + + w, err := pgx.CollectOneRow(rows, pgx.RowToAddrOfStructByName[Withdrawal]) + if err != nil { + return err + } + rows.Close() + + indent := " -" + fmt.Println("Withdrawal:") + fmt.Println(indent, "wopid :", encodeCrock(w.Wopid)) + fmt.Println(indent, "status :", w.WithdrawalStatus) + fmt.Println(indent, "reserve public key:", encodeCrock(w.ReservePubKey)) + fmt.Println(indent, "provider tid :", *w.ProviderTransactionId) + fmt.Println(indent, "amount :", w.Amount) + fmt.Println(indent, "terminal :", w.TerminalId) + fmt.Println(indent, "attest retries :", w.RetryCounter) + if w.LastRetryTs != nil { + fmt.Println(indent, "last retry :", time.Unix(*w.LastRetryTs, 0).Format("yyyy-MM-dd hh:mm:ss")) + } + + return nil +} + func setupSimulation() error { if DB == nil { @@ -340,12 +407,13 @@ func connectDatabase() error { } func connectDbUsingString(connString string) error { - dbCfg, err := pgx.ParseConfig(connString) + dbCfg, err := pgxpool.ParseConfig(connString) if err != nil { return err } - DB, err = pgx.ConnectConfig(context.Background(), dbCfg) + dbCfg.AfterConnect = registerCustomTypesHook + DB, err = pgxpool.NewWithConfig(context.Background(), dbCfg) if err != nil { return err } @@ -360,6 +428,7 @@ func showHelp() error { fmt.Println("deactivate wallee terminal (", ACTION_DEACTIVATE_TERMINAL, ")") fmt.Println("activate wallee terminal (", ACTION_ACTIVATE_TERMINAL, ")") fmt.Println("setup simulation (", ACTION_SETUP_SIMULATION, ")") + fmt.Println("withdrawal information (", ACTION_WITHDRAWAL_INFOMRATION, ")") fmt.Println("connect database (", ACTION_CONNECT_DB, ")") fmt.Println("show help (", ACTION_HELP, ")") fmt.Println("quit (", ACTION_QUIT, ")") @@ -427,6 +496,8 @@ func dispatchCommand(cmd string) error { err = deactivateTerminal() case ACTION_ACTIVATE_TERMINAL: err = activateTerminal() + case ACTION_WITHDRAWAL_INFOMRATION: + err = withdrawalInformation() case ACTION_SETUP_SIMULATION: err = setupSimulation() default: @@ -445,3 +516,14 @@ func read(prefix string) string { } return strings.Trim(inp, "\n") } + +func registerCustomTypesHook(ctx context.Context, conn *pgx.Conn) error { + + t, err := conn.LoadType(ctx, "c2ec.taler_amount_currency") + if err != nil { + return err + } + + conn.TypeMap().RegisterType(t) + return nil +} diff --git a/cli/db.go b/cli/db.go @@ -6,6 +6,7 @@ const DEACTIVATE_TERMINAL = "UPDATE c2ec.terminal SET active = false WHERE termi const ACTIVATE_TERMINAL = "UPDATE c2ec.terminal SET active = true WHERE terminal_id=$1" const GET_PROVIDER_BY_NAME = "SELECT * FROM c2ec.provider WHERE name=$1" const GET_LAST_INSERTED_TERMINAL = "SELECT * FROM c2ec.terminal WHERE terminal_id = (SELECT MAX(terminal_id) FROM c2ec.terminal)" +const GET_WITHDRAWAL_BY_WOPID = "SELECT * FROM c2ec.withdrawal WHERE wopid=$1" type Provider struct { ProviderId int64 `db:"provider_id"` diff --git a/cli/encoding.go b/cli/encoding.go @@ -0,0 +1,127 @@ +package main + +import ( + "errors" + "math" + "strings" +) + +func talerBinaryEncode(byts []byte) string { + + return encodeCrock(byts) +} + +func talerBinaryDecode(str string) ([]byte, error) { + + return decodeCrock(str) +} + +func ParseWopid(wopid string) ([]byte, error) { + + wopidBytes, err := talerBinaryDecode(wopid) + if err != nil { + return nil, err + } + + if len(wopidBytes) != 32 { + err = errors.New("invalid wopid") + return nil, err + } + + return wopidBytes, nil +} + +func FormatWopid(wopid []byte) string { + + return talerBinaryEncode(wopid) +} + +func decodeCrock(e string) ([]byte, error) { + size := len(e) + bitpos := 0 + bitbuf := 0 + readPosition := 0 + outLen := int(math.Floor((float64(size) * 5.0) / 8.0)) + out := make([]byte, outLen) + outPos := 0 + + getValue := func(c byte) (int, error) { + alphabet := "0123456789ABCDEFGHJKMNPQRSTVWXYZ" + switch c { + case 'o', 'O': + return 0, nil + case 'i', 'I', 'l', 'L': + return 1, nil + case 'u', 'U': + return 27, nil + } + + i := strings.IndexRune(alphabet, rune(c)) + if i > -1 && i < 32 { + return i, nil + } + + return -1, errors.New("encoding error") + } + + for readPosition < size || bitpos > 0 { + if readPosition < size { + v, err := getValue(e[readPosition]) + if err != nil { + return nil, err + } + readPosition++ + bitbuf = bitbuf<<5 | v + bitpos += 5 + } + for bitpos >= 8 { + d := byte(bitbuf >> (bitpos - 8) & 0xff) + out[outPos] = d + outPos++ + bitpos -= 8 + } + if readPosition == size && bitpos > 0 { + bitbuf = bitbuf << (8 - bitpos) & 0xff + if bitbuf == 0 { + bitpos = 0 + } else { + bitpos = 8 + } + } + } + return out, nil +} + +func encodeCrock(data []byte) string { + out := "" + bitbuf := 0 + bitpos := 0 + + encodeValue := func(value int) byte { + alphabet := "ABCDEFGHJKMNPQRSTVWXYZ" + switch { + case value >= 0 && value <= 9: + return byte('0' + value) + case value >= 10 && value <= 31: + return alphabet[value-10] + default: + panic("Invalid value for encoding") + } + } + + for _, b := range data { + bitbuf = bitbuf<<8 | int(b&0xff) + bitpos += 8 + for bitpos >= 5 { + value := bitbuf >> (bitpos - 5) & 0x1f + out += string(encodeValue(value)) + bitpos -= 5 + } + } + if bitpos > 0 { + bitbuf = bitbuf << (5 - bitpos) + value := bitbuf & 0x1f + out += string(encodeValue(value)) + } + return out +} diff --git a/cli/go.mod b/cli/go.mod @@ -10,6 +10,8 @@ require ( require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + golang.org/x/sync v0.1.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect ) diff --git a/simulation/c2ec-simulation b/simulation/c2ec-simulation Binary files differ. diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/TerminalClientMock.kt @@ -48,7 +48,7 @@ class TerminalClientMock: TerminalClient { Optional.of( BankWithdrawalOperationStatus( WithdrawalOperationStatus.selected, - Amount(10,0), + "CHF:10", // Amount(10,0), // Amount(0,0), // Amount(0,0), diff --git a/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/model/TerminalsApiModel.kt b/wallee-c2ec/app/src/main/java/ch/bfh/habej2/wallee_c2ec/client/taler/model/TerminalsApiModel.kt @@ -40,10 +40,10 @@ enum class WithdrawalOperationStatus(val value: String) { data class BankWithdrawalOperationStatus( @Json(name = "status") val status: WithdrawalOperationStatus, - @Json(name = "amount") val amount: Amount, -// @Json(name = "suggested_amount") val suggestedAmount: Amount, -// @Json(name = "max_amount") val maxAmount: Amount, -// @Json(name = "card_fees") val cardFees: Amount, + @Json(name = "amount") val amount: String, +// @Json(name = "suggested_amount") val suggestedAmount: String, +// @Json(name = "max_amount") val maxAmount: String, +// @Json(name = "card_fees") val cardFees: String, @Json(name = "sender_wire") val senderWire: String, // @Json(name = "suggested_exchange") val suggestedExchange: String, // @Json(name = "required_exchange") val requiredExchange: String,