forked from cerc-io/plugeth
Merge pull request #14885 from karalabe/trezor-boom
accounts, console, internal: support trezor hardware wallet
This commit is contained in:
commit
ef0edc6e32
@ -42,8 +42,9 @@ type Wallet interface {
|
|||||||
URL() URL
|
URL() URL
|
||||||
|
|
||||||
// Status returns a textual status to aid the user in the current state of the
|
// Status returns a textual status to aid the user in the current state of the
|
||||||
// wallet.
|
// wallet. It also returns an error indicating any failure the wallet might have
|
||||||
Status() string
|
// encountered.
|
||||||
|
Status() (string, error)
|
||||||
|
|
||||||
// Open initializes access to a wallet instance. It is not meant to unlock or
|
// Open initializes access to a wallet instance. It is not meant to unlock or
|
||||||
// decrypt account keys, rather simply to establish a connection to hardware
|
// decrypt account keys, rather simply to establish a connection to hardware
|
||||||
@ -147,9 +148,26 @@ type Backend interface {
|
|||||||
Subscribe(sink chan<- WalletEvent) event.Subscription
|
Subscribe(sink chan<- WalletEvent) event.Subscription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WalletEventType represents the different event types that can be fired by
|
||||||
|
// the wallet subscription subsystem.
|
||||||
|
type WalletEventType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// WalletArrived is fired when a new wallet is detected either via USB or via
|
||||||
|
// a filesystem event in the keystore.
|
||||||
|
WalletArrived WalletEventType = iota
|
||||||
|
|
||||||
|
// WalletOpened is fired when a wallet is successfully opened with the purpose
|
||||||
|
// of starting any background processes such as automatic key derivation.
|
||||||
|
WalletOpened
|
||||||
|
|
||||||
|
// WalletDropped
|
||||||
|
WalletDropped
|
||||||
|
)
|
||||||
|
|
||||||
// WalletEvent is an event fired by an account backend when a wallet arrival or
|
// WalletEvent is an event fired by an account backend when a wallet arrival or
|
||||||
// departure is detected.
|
// departure is detected.
|
||||||
type WalletEvent struct {
|
type WalletEvent struct {
|
||||||
Wallet Wallet // Wallet instance arrived or departed
|
Wallet Wallet // Wallet instance arrived or departed
|
||||||
Arrive bool // Whether the wallet was added or removed
|
Kind WalletEventType // Event type that happened in the system
|
||||||
}
|
}
|
||||||
|
@ -27,12 +27,17 @@ import (
|
|||||||
// DefaultRootDerivationPath is the root path to which custom derivation endpoints
|
// DefaultRootDerivationPath is the root path to which custom derivation endpoints
|
||||||
// are appended. As such, the first account will be at m/44'/60'/0'/0, the second
|
// are appended. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||||
// at m/44'/60'/0'/1, etc.
|
// at m/44'/60'/0'/1, etc.
|
||||||
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0}
|
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||||
|
|
||||||
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
|
||||||
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||||
// at m/44'/60'/0'/1, etc.
|
// at m/44'/60'/0'/1, etc.
|
||||||
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
|
||||||
|
|
||||||
|
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints
|
||||||
|
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
|
||||||
|
// at m/44'/60'/0'/1, etc.
|
||||||
|
var DefaultLedgerBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
|
||||||
|
|
||||||
// DerivationPath represents the computer friendly version of a hierarchical
|
// DerivationPath represents the computer friendly version of a hierarchical
|
||||||
// deterministic wallet account derivaion path.
|
// deterministic wallet account derivaion path.
|
||||||
|
@ -37,11 +37,11 @@ func TestHDPathParsing(t *testing.T) {
|
|||||||
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
{"m/2147483692/2147483708/2147483648/2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
// Plain relative derivation paths
|
// Plain relative derivation paths
|
||||||
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
{"0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
|
||||||
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
|
{"128", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
|
||||||
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
{"0'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
|
{"128'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
|
||||||
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
{"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
// Hexadecimal absolute derivation paths
|
// Hexadecimal absolute derivation paths
|
||||||
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
{"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
@ -52,11 +52,11 @@ func TestHDPathParsing(t *testing.T) {
|
|||||||
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
{"m/0x8000002C/0x8000003c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
// Hexadecimal relative derivation paths
|
// Hexadecimal relative derivation paths
|
||||||
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
{"0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}},
|
||||||
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 128}},
|
{"0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 128}},
|
||||||
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
{"0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 128}},
|
{"0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 128}},
|
||||||
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0x80000000 + 0}},
|
{"0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0x80000000 + 0}},
|
||||||
|
|
||||||
// Weird inputs just to ensure they work
|
// Weird inputs just to ensure they work
|
||||||
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
{" m / 44 '\n/\n 60 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}},
|
||||||
|
@ -143,14 +143,14 @@ func (ks *KeyStore) refreshWallets() {
|
|||||||
for _, account := range accs {
|
for _, account := range accs {
|
||||||
// Drop wallets while they were in front of the next account
|
// Drop wallets while they were in front of the next account
|
||||||
for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
|
for len(ks.wallets) > 0 && ks.wallets[0].URL().Cmp(account.URL) < 0 {
|
||||||
events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Arrive: false})
|
events = append(events, accounts.WalletEvent{Wallet: ks.wallets[0], Kind: accounts.WalletDropped})
|
||||||
ks.wallets = ks.wallets[1:]
|
ks.wallets = ks.wallets[1:]
|
||||||
}
|
}
|
||||||
// If there are no more wallets or the account is before the next, wrap new wallet
|
// If there are no more wallets or the account is before the next, wrap new wallet
|
||||||
if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
|
if len(ks.wallets) == 0 || ks.wallets[0].URL().Cmp(account.URL) > 0 {
|
||||||
wallet := &keystoreWallet{account: account, keystore: ks}
|
wallet := &keystoreWallet{account: account, keystore: ks}
|
||||||
|
|
||||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: true})
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||||
wallets = append(wallets, wallet)
|
wallets = append(wallets, wallet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -163,7 +163,7 @@ func (ks *KeyStore) refreshWallets() {
|
|||||||
}
|
}
|
||||||
// Drop any leftover wallets and set the new batch
|
// Drop any leftover wallets and set the new batch
|
||||||
for _, wallet := range ks.wallets {
|
for _, wallet := range ks.wallets {
|
||||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: false})
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
|
||||||
}
|
}
|
||||||
ks.wallets = wallets
|
ks.wallets = wallets
|
||||||
ks.mu.Unlock()
|
ks.mu.Unlock()
|
||||||
|
@ -296,8 +296,8 @@ func TestWalletNotifications(t *testing.T) {
|
|||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case event := <-updates:
|
case event := <-updates:
|
||||||
if !event.Arrive {
|
if event.Kind != accounts.WalletArrived {
|
||||||
t.Errorf("departure event on account creation")
|
t.Errorf("non-arrival event on account creation")
|
||||||
}
|
}
|
||||||
if event.Wallet.Accounts()[0] != account {
|
if event.Wallet.Accounts()[0] != account {
|
||||||
t.Errorf("account mismatch on created wallet: have %v, want %v", event.Wallet.Accounts()[0], account)
|
t.Errorf("account mismatch on created wallet: have %v, want %v", event.Wallet.Accounts()[0], account)
|
||||||
@ -319,8 +319,8 @@ func TestWalletNotifications(t *testing.T) {
|
|||||||
}
|
}
|
||||||
select {
|
select {
|
||||||
case event := <-updates:
|
case event := <-updates:
|
||||||
if event.Arrive {
|
if event.Kind != accounts.WalletDropped {
|
||||||
t.Errorf("arrival event on account deletion")
|
t.Errorf("non-drop event on account deletion")
|
||||||
}
|
}
|
||||||
if event.Wallet.Accounts()[0] != account {
|
if event.Wallet.Accounts()[0] != account {
|
||||||
t.Errorf("account mismatch on deleted wallet: have %v, want %v", event.Wallet.Accounts()[0], account)
|
t.Errorf("account mismatch on deleted wallet: have %v, want %v", event.Wallet.Accounts()[0], account)
|
||||||
|
@ -36,16 +36,16 @@ func (w *keystoreWallet) URL() accounts.URL {
|
|||||||
return w.account.URL
|
return w.account.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status implements accounts.Wallet, always returning "open", since there is no
|
// Status implements accounts.Wallet, returning whether the account held by the
|
||||||
// concept of open/close for plain keystore accounts.
|
// keystore wallet is unlocked or not.
|
||||||
func (w *keystoreWallet) Status() string {
|
func (w *keystoreWallet) Status() (string, error) {
|
||||||
w.keystore.mu.RLock()
|
w.keystore.mu.RLock()
|
||||||
defer w.keystore.mu.RUnlock()
|
defer w.keystore.mu.RUnlock()
|
||||||
|
|
||||||
if _, ok := w.keystore.unlocked[w.account.Address]; ok {
|
if _, ok := w.keystore.unlocked[w.account.Address]; ok {
|
||||||
return "Unlocked"
|
return "Unlocked", nil
|
||||||
}
|
}
|
||||||
return "Locked"
|
return "Locked", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open implements accounts.Wallet, but is a noop for plain wallets since there
|
// Open implements accounts.Wallet, but is a noop for plain wallets since there
|
||||||
|
@ -96,9 +96,10 @@ func (am *Manager) update() {
|
|||||||
case event := <-am.updates:
|
case event := <-am.updates:
|
||||||
// Wallet event arrived, update local cache
|
// Wallet event arrived, update local cache
|
||||||
am.lock.Lock()
|
am.lock.Lock()
|
||||||
if event.Arrive {
|
switch event.Kind {
|
||||||
|
case WalletArrived:
|
||||||
am.wallets = merge(am.wallets, event.Wallet)
|
am.wallets = merge(am.wallets, event.Wallet)
|
||||||
} else {
|
case WalletDropped:
|
||||||
am.wallets = drop(am.wallets, event.Wallet)
|
am.wallets = drop(am.wallets, event.Wallet)
|
||||||
}
|
}
|
||||||
am.lock.Unlock()
|
am.lock.Unlock()
|
||||||
|
@ -14,10 +14,6 @@
|
|||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// This file contains the implementation for interacting with the Ledger hardware
|
|
||||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
|
||||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
|
||||||
|
|
||||||
package usbwallet
|
package usbwallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -33,24 +29,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
// LedgerScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
var LedgerScheme = "ledger"
|
const LedgerScheme = "ledger"
|
||||||
|
|
||||||
// ledgerDeviceIDs are the known device IDs that Ledger wallets use.
|
// TrezorScheme is the protocol scheme prefixing account and wallet URLs.
|
||||||
var ledgerDeviceIDs = []deviceID{
|
const TrezorScheme = "trezor"
|
||||||
{Vendor: 0x2c97, Product: 0x0000}, // Ledger Blue
|
|
||||||
{Vendor: 0x2c97, Product: 0x0001}, // Ledger Nano S
|
|
||||||
}
|
|
||||||
|
|
||||||
// Maximum time between wallet refreshes (if USB hotplug notifications don't work).
|
// refreshCycle is the maximum time between wallet refreshes (if USB hotplug
|
||||||
const ledgerRefreshCycle = time.Second
|
// notifications don't work).
|
||||||
|
const refreshCycle = time.Second
|
||||||
|
|
||||||
// Minimum time between wallet refreshes to avoid USB trashing.
|
// refreshThrottling is the minimum time between wallet refreshes to avoid USB
|
||||||
const ledgerRefreshThrottling = 500 * time.Millisecond
|
// trashing.
|
||||||
|
const refreshThrottling = 500 * time.Millisecond
|
||||||
|
|
||||||
|
// Hub is a accounts.Backend that can find and handle generic USB hardware wallets.
|
||||||
|
type Hub struct {
|
||||||
|
scheme string // Protocol scheme prefixing account and wallet URLs.
|
||||||
|
vendorID uint16 // USB vendor identifier used for device discovery
|
||||||
|
productIDs []uint16 // USB product identifiers used for device discovery
|
||||||
|
makeDriver func(log.Logger) driver // Factory method to construct a vendor specific driver
|
||||||
|
|
||||||
// LedgerHub is a accounts.Backend that can find and handle Ledger hardware wallets.
|
|
||||||
type LedgerHub struct {
|
|
||||||
refreshed time.Time // Time instance when the list of wallets was last refreshed
|
refreshed time.Time // Time instance when the list of wallets was last refreshed
|
||||||
wallets []accounts.Wallet // List of Ledger devices currently tracking
|
wallets []accounts.Wallet // List of USB wallet devices currently tracking
|
||||||
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
updateFeed event.Feed // Event feed to notify wallet additions/removals
|
||||||
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
|
||||||
updating bool // Whether the event notification loop is running
|
updating bool // Whether the event notification loop is running
|
||||||
@ -65,11 +65,25 @@ type LedgerHub struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
// NewLedgerHub creates a new hardware wallet manager for Ledger devices.
|
||||||
func NewLedgerHub() (*LedgerHub, error) {
|
func NewLedgerHub() (*Hub, error) {
|
||||||
|
return newHub(LedgerScheme, 0x2c97, []uint16{0x0000 /* Ledger Blue */, 0x0001 /* Ledger Nano S */}, newLedgerDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTrezorHub creates a new hardware wallet manager for Trezor devices.
|
||||||
|
func NewTrezorHub() (*Hub, error) {
|
||||||
|
return newHub(TrezorScheme, 0x534c, []uint16{0x0001 /* Trezor 1 */}, newTrezorDriver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newHub creates a new hardware wallet manager for generic USB devices.
|
||||||
|
func newHub(scheme string, vendorID uint16, productIDs []uint16, makeDriver func(log.Logger) driver) (*Hub, error) {
|
||||||
if !hid.Supported() {
|
if !hid.Supported() {
|
||||||
return nil, errors.New("unsupported platform")
|
return nil, errors.New("unsupported platform")
|
||||||
}
|
}
|
||||||
hub := &LedgerHub{
|
hub := &Hub{
|
||||||
|
scheme: scheme,
|
||||||
|
vendorID: vendorID,
|
||||||
|
productIDs: productIDs,
|
||||||
|
makeDriver: makeDriver,
|
||||||
quit: make(chan chan error),
|
quit: make(chan chan error),
|
||||||
}
|
}
|
||||||
hub.refreshWallets()
|
hub.refreshWallets()
|
||||||
@ -77,8 +91,8 @@ func NewLedgerHub() (*LedgerHub, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wallets implements accounts.Backend, returning all the currently tracked USB
|
// Wallets implements accounts.Backend, returning all the currently tracked USB
|
||||||
// devices that appear to be Ledger hardware wallets.
|
// devices that appear to be hardware wallets.
|
||||||
func (hub *LedgerHub) Wallets() []accounts.Wallet {
|
func (hub *Hub) Wallets() []accounts.Wallet {
|
||||||
// Make sure the list of wallets is up to date
|
// Make sure the list of wallets is up to date
|
||||||
hub.refreshWallets()
|
hub.refreshWallets()
|
||||||
|
|
||||||
@ -92,17 +106,17 @@ func (hub *LedgerHub) Wallets() []accounts.Wallet {
|
|||||||
|
|
||||||
// refreshWallets scans the USB devices attached to the machine and updates the
|
// refreshWallets scans the USB devices attached to the machine and updates the
|
||||||
// list of wallets based on the found devices.
|
// list of wallets based on the found devices.
|
||||||
func (hub *LedgerHub) refreshWallets() {
|
func (hub *Hub) refreshWallets() {
|
||||||
// Don't scan the USB like crazy it the user fetches wallets in a loop
|
// Don't scan the USB like crazy it the user fetches wallets in a loop
|
||||||
hub.stateLock.RLock()
|
hub.stateLock.RLock()
|
||||||
elapsed := time.Since(hub.refreshed)
|
elapsed := time.Since(hub.refreshed)
|
||||||
hub.stateLock.RUnlock()
|
hub.stateLock.RUnlock()
|
||||||
|
|
||||||
if elapsed < ledgerRefreshThrottling {
|
if elapsed < refreshThrottling {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Retrieve the current list of Ledger devices
|
// Retrieve the current list of USB wallet devices
|
||||||
var ledgers []hid.DeviceInfo
|
var devices []hid.DeviceInfo
|
||||||
|
|
||||||
if runtime.GOOS == "linux" {
|
if runtime.GOOS == "linux" {
|
||||||
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
// hidapi on Linux opens the device during enumeration to retrieve some infos,
|
||||||
@ -117,10 +131,10 @@ func (hub *LedgerHub) refreshWallets() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, info := range hid.Enumerate(0, 0) { // Can't enumerate directly, one valid ID is the 0 wildcard
|
for _, info := range hid.Enumerate(hub.vendorID, 0) {
|
||||||
for _, id := range ledgerDeviceIDs {
|
for _, id := range hub.productIDs {
|
||||||
if info.VendorID == id.Vendor && info.ProductID == id.Product {
|
if info.ProductID == id && info.Interface == 0 {
|
||||||
ledgers = append(ledgers, info)
|
devices = append(devices, info)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,22 +146,29 @@ func (hub *LedgerHub) refreshWallets() {
|
|||||||
// Transform the current list of wallets into the new one
|
// Transform the current list of wallets into the new one
|
||||||
hub.stateLock.Lock()
|
hub.stateLock.Lock()
|
||||||
|
|
||||||
wallets := make([]accounts.Wallet, 0, len(ledgers))
|
wallets := make([]accounts.Wallet, 0, len(devices))
|
||||||
events := []accounts.WalletEvent{}
|
events := []accounts.WalletEvent{}
|
||||||
|
|
||||||
for _, ledger := range ledgers {
|
for _, device := range devices {
|
||||||
url := accounts.URL{Scheme: LedgerScheme, Path: ledger.Path}
|
url := accounts.URL{Scheme: hub.scheme, Path: device.Path}
|
||||||
|
|
||||||
// Drop wallets in front of the next device or those that failed for some reason
|
// Drop wallets in front of the next device or those that failed for some reason
|
||||||
for len(hub.wallets) > 0 && (hub.wallets[0].URL().Cmp(url) < 0 || hub.wallets[0].(*ledgerWallet).failed()) {
|
for len(hub.wallets) > 0 {
|
||||||
events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Arrive: false})
|
// Abort if we're past the current device and found an operational one
|
||||||
|
_, failure := hub.wallets[0].Status()
|
||||||
|
if hub.wallets[0].URL().Cmp(url) >= 0 || failure == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Drop the stale and failed devices
|
||||||
|
events = append(events, accounts.WalletEvent{Wallet: hub.wallets[0], Kind: accounts.WalletDropped})
|
||||||
hub.wallets = hub.wallets[1:]
|
hub.wallets = hub.wallets[1:]
|
||||||
}
|
}
|
||||||
// If there are no more wallets or the device is before the next, wrap new wallet
|
// If there are no more wallets or the device is before the next, wrap new wallet
|
||||||
if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 {
|
if len(hub.wallets) == 0 || hub.wallets[0].URL().Cmp(url) > 0 {
|
||||||
wallet := &ledgerWallet{hub: hub, url: &url, info: ledger, log: log.New("url", url)}
|
logger := log.New("url", url)
|
||||||
|
wallet := &wallet{hub: hub, driver: hub.makeDriver(logger), url: &url, info: device, log: logger}
|
||||||
|
|
||||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: true})
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletArrived})
|
||||||
wallets = append(wallets, wallet)
|
wallets = append(wallets, wallet)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -160,7 +181,7 @@ func (hub *LedgerHub) refreshWallets() {
|
|||||||
}
|
}
|
||||||
// Drop any leftover wallets and set the new batch
|
// Drop any leftover wallets and set the new batch
|
||||||
for _, wallet := range hub.wallets {
|
for _, wallet := range hub.wallets {
|
||||||
events = append(events, accounts.WalletEvent{Wallet: wallet, Arrive: false})
|
events = append(events, accounts.WalletEvent{Wallet: wallet, Kind: accounts.WalletDropped})
|
||||||
}
|
}
|
||||||
hub.refreshed = time.Now()
|
hub.refreshed = time.Now()
|
||||||
hub.wallets = wallets
|
hub.wallets = wallets
|
||||||
@ -173,8 +194,8 @@ func (hub *LedgerHub) refreshWallets() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Subscribe implements accounts.Backend, creating an async subscription to
|
// Subscribe implements accounts.Backend, creating an async subscription to
|
||||||
// receive notifications on the addition or removal of Ledger wallets.
|
// receive notifications on the addition or removal of USB wallets.
|
||||||
func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
func (hub *Hub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscription {
|
||||||
// We need the mutex to reliably start/stop the update loop
|
// We need the mutex to reliably start/stop the update loop
|
||||||
hub.stateLock.Lock()
|
hub.stateLock.Lock()
|
||||||
defer hub.stateLock.Unlock()
|
defer hub.stateLock.Unlock()
|
||||||
@ -190,16 +211,13 @@ func (hub *LedgerHub) Subscribe(sink chan<- accounts.WalletEvent) event.Subscrip
|
|||||||
return sub
|
return sub
|
||||||
}
|
}
|
||||||
|
|
||||||
// updater is responsible for maintaining an up-to-date list of wallets stored in
|
// updater is responsible for maintaining an up-to-date list of wallets managed
|
||||||
// the keystore, and for firing wallet addition/removal events. It listens for
|
// by the USB hub, and for firing wallet addition/removal events.
|
||||||
// account change events from the underlying account cache, and also periodically
|
func (hub *Hub) updater() {
|
||||||
// forces a manual refresh (only triggers for systems where the filesystem notifier
|
|
||||||
// is not running).
|
|
||||||
func (hub *LedgerHub) updater() {
|
|
||||||
for {
|
for {
|
||||||
// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout
|
// TODO: Wait for a USB hotplug event (not supported yet) or a refresh timeout
|
||||||
// <-hub.changes
|
// <-hub.changes
|
||||||
time.Sleep(ledgerRefreshCycle)
|
time.Sleep(refreshCycle)
|
||||||
|
|
||||||
// Run the wallet refresher
|
// Run the wallet refresher
|
||||||
hub.refreshWallets()
|
hub.refreshWallets()
|
3081
accounts/usbwallet/internal/trezor/messages.pb.go
Normal file
3081
accounts/usbwallet/internal/trezor/messages.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
903
accounts/usbwallet/internal/trezor/messages.proto
Normal file
903
accounts/usbwallet/internal/trezor/messages.proto
Normal file
@ -0,0 +1,903 @@
|
|||||||
|
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||||
|
// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto
|
||||||
|
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Messages for TREZOR communication
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Sugar for easier handling in Java
|
||||||
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
|
option java_outer_classname = "TrezorMessage";
|
||||||
|
|
||||||
|
import "types.proto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mapping between Trezor wire identifier (uint) and a protobuf message
|
||||||
|
*/
|
||||||
|
enum MessageType {
|
||||||
|
MessageType_Initialize = 0 [(wire_in) = true];
|
||||||
|
MessageType_Ping = 1 [(wire_in) = true];
|
||||||
|
MessageType_Success = 2 [(wire_out) = true];
|
||||||
|
MessageType_Failure = 3 [(wire_out) = true];
|
||||||
|
MessageType_ChangePin = 4 [(wire_in) = true];
|
||||||
|
MessageType_WipeDevice = 5 [(wire_in) = true];
|
||||||
|
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
MessageType_FirmwareRequest = 8 [(wire_out) = true, (wire_bootloader) = true];
|
||||||
|
MessageType_GetEntropy = 9 [(wire_in) = true];
|
||||||
|
MessageType_Entropy = 10 [(wire_out) = true];
|
||||||
|
MessageType_GetPublicKey = 11 [(wire_in) = true];
|
||||||
|
MessageType_PublicKey = 12 [(wire_out) = true];
|
||||||
|
MessageType_LoadDevice = 13 [(wire_in) = true];
|
||||||
|
MessageType_ResetDevice = 14 [(wire_in) = true];
|
||||||
|
MessageType_SignTx = 15 [(wire_in) = true];
|
||||||
|
MessageType_SimpleSignTx = 16 [(wire_in) = true, deprecated = true];
|
||||||
|
MessageType_Features = 17 [(wire_out) = true];
|
||||||
|
MessageType_PinMatrixRequest = 18 [(wire_out) = true];
|
||||||
|
MessageType_PinMatrixAck = 19 [(wire_in) = true, (wire_tiny) = true];
|
||||||
|
MessageType_Cancel = 20 [(wire_in) = true];
|
||||||
|
MessageType_TxRequest = 21 [(wire_out) = true];
|
||||||
|
MessageType_TxAck = 22 [(wire_in) = true];
|
||||||
|
MessageType_CipherKeyValue = 23 [(wire_in) = true];
|
||||||
|
MessageType_ClearSession = 24 [(wire_in) = true];
|
||||||
|
MessageType_ApplySettings = 25 [(wire_in) = true];
|
||||||
|
MessageType_ButtonRequest = 26 [(wire_out) = true];
|
||||||
|
MessageType_ButtonAck = 27 [(wire_in) = true, (wire_tiny) = true];
|
||||||
|
MessageType_ApplyFlags = 28 [(wire_in) = true];
|
||||||
|
MessageType_GetAddress = 29 [(wire_in) = true];
|
||||||
|
MessageType_Address = 30 [(wire_out) = true];
|
||||||
|
MessageType_SelfTest = 32 [(wire_in) = true, (wire_bootloader) = true];
|
||||||
|
MessageType_BackupDevice = 34 [(wire_in) = true];
|
||||||
|
MessageType_EntropyRequest = 35 [(wire_out) = true];
|
||||||
|
MessageType_EntropyAck = 36 [(wire_in) = true];
|
||||||
|
MessageType_SignMessage = 38 [(wire_in) = true];
|
||||||
|
MessageType_VerifyMessage = 39 [(wire_in) = true];
|
||||||
|
MessageType_MessageSignature = 40 [(wire_out) = true];
|
||||||
|
MessageType_PassphraseRequest = 41 [(wire_out) = true];
|
||||||
|
MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true];
|
||||||
|
MessageType_EstimateTxSize = 43 [(wire_in) = true, deprecated = true];
|
||||||
|
MessageType_TxSize = 44 [(wire_out) = true, deprecated = true];
|
||||||
|
MessageType_RecoveryDevice = 45 [(wire_in) = true];
|
||||||
|
MessageType_WordRequest = 46 [(wire_out) = true];
|
||||||
|
MessageType_WordAck = 47 [(wire_in) = true];
|
||||||
|
MessageType_CipheredKeyValue = 48 [(wire_out) = true];
|
||||||
|
MessageType_EncryptMessage = 49 [(wire_in) = true, deprecated = true];
|
||||||
|
MessageType_EncryptedMessage = 50 [(wire_out) = true, deprecated = true];
|
||||||
|
MessageType_DecryptMessage = 51 [(wire_in) = true, deprecated = true];
|
||||||
|
MessageType_DecryptedMessage = 52 [(wire_out) = true, deprecated = true];
|
||||||
|
MessageType_SignIdentity = 53 [(wire_in) = true];
|
||||||
|
MessageType_SignedIdentity = 54 [(wire_out) = true];
|
||||||
|
MessageType_GetFeatures = 55 [(wire_in) = true];
|
||||||
|
MessageType_EthereumGetAddress = 56 [(wire_in) = true];
|
||||||
|
MessageType_EthereumAddress = 57 [(wire_out) = true];
|
||||||
|
MessageType_EthereumSignTx = 58 [(wire_in) = true];
|
||||||
|
MessageType_EthereumTxRequest = 59 [(wire_out) = true];
|
||||||
|
MessageType_EthereumTxAck = 60 [(wire_in) = true];
|
||||||
|
MessageType_GetECDHSessionKey = 61 [(wire_in) = true];
|
||||||
|
MessageType_ECDHSessionKey = 62 [(wire_out) = true];
|
||||||
|
MessageType_SetU2FCounter = 63 [(wire_in) = true];
|
||||||
|
MessageType_EthereumSignMessage = 64 [(wire_in) = true];
|
||||||
|
MessageType_EthereumVerifyMessage = 65 [(wire_in) = true];
|
||||||
|
MessageType_EthereumMessageSignature = 66 [(wire_out) = true];
|
||||||
|
MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true, (wire_tiny) = true];
|
||||||
|
MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkState = 102 [(wire_debug_out) = true];
|
||||||
|
MessageType_DebugLinkStop = 103 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkLog = 104 [(wire_debug_out) = true];
|
||||||
|
MessageType_DebugLinkMemoryRead = 110 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkMemory = 111 [(wire_debug_out) = true];
|
||||||
|
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true];
|
||||||
|
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true];
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////
|
||||||
|
// Basic messages //
|
||||||
|
////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Reset device to default state and ask for device details
|
||||||
|
* @next Features
|
||||||
|
*/
|
||||||
|
message Initialize {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask for device details (no device reset)
|
||||||
|
* @next Features
|
||||||
|
*/
|
||||||
|
message GetFeatures {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Reports various information about the device
|
||||||
|
* @prev Initialize
|
||||||
|
* @prev GetFeatures
|
||||||
|
*/
|
||||||
|
message Features {
|
||||||
|
optional string vendor = 1; // name of the manufacturer, e.g. "bitcointrezor.com"
|
||||||
|
optional uint32 major_version = 2; // major version of the device, e.g. 1
|
||||||
|
optional uint32 minor_version = 3; // minor version of the device, e.g. 0
|
||||||
|
optional uint32 patch_version = 4; // patch version of the device, e.g. 0
|
||||||
|
optional bool bootloader_mode = 5; // is device in bootloader mode?
|
||||||
|
optional string device_id = 6; // device's unique identifier
|
||||||
|
optional bool pin_protection = 7; // is device protected by PIN?
|
||||||
|
optional bool passphrase_protection = 8; // is node/mnemonic encrypted using passphrase?
|
||||||
|
optional string language = 9; // device language
|
||||||
|
optional string label = 10; // device description label
|
||||||
|
repeated CoinType coins = 11; // supported coins
|
||||||
|
optional bool initialized = 12; // does device contain seed?
|
||||||
|
optional bytes revision = 13; // SCM revision of firmware
|
||||||
|
optional bytes bootloader_hash = 14; // hash of the bootloader
|
||||||
|
optional bool imported = 15; // was storage imported from an external source?
|
||||||
|
optional bool pin_cached = 16; // is PIN already cached in session?
|
||||||
|
optional bool passphrase_cached = 17; // is passphrase already cached in session?
|
||||||
|
optional bool firmware_present = 18; // is valid firmware loaded?
|
||||||
|
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup)
|
||||||
|
optional uint32 flags = 20; // device flags (equals to Storage.flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: clear session (removes cached PIN, passphrase, etc).
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message ClearSession {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: change language and/or label of the device
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
* @next ButtonRequest
|
||||||
|
* @next PinMatrixRequest
|
||||||
|
*/
|
||||||
|
message ApplySettings {
|
||||||
|
optional string language = 1;
|
||||||
|
optional string label = 2;
|
||||||
|
optional bool use_passphrase = 3;
|
||||||
|
optional bytes homescreen = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: set flags of the device
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message ApplyFlags {
|
||||||
|
optional uint32 flags = 1; // bitmask, can only set bits, not unset
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Starts workflow for setting/changing/removing the PIN
|
||||||
|
* @next ButtonRequest
|
||||||
|
* @next PinMatrixRequest
|
||||||
|
*/
|
||||||
|
message ChangePin {
|
||||||
|
optional bool remove = 1; // is PIN removal requested?
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Test if the device is alive, device sends back the message in Success response
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message Ping {
|
||||||
|
optional string message = 1; // message to send back in Success message
|
||||||
|
optional bool button_protection = 2; // ask for button press
|
||||||
|
optional bool pin_protection = 3; // ask for PIN if set in device
|
||||||
|
optional bool passphrase_protection = 4; // ask for passphrase if set in device
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Success of the previous request
|
||||||
|
*/
|
||||||
|
message Success {
|
||||||
|
optional string message = 1; // human readable description of action or request-specific payload
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Failure of the previous request
|
||||||
|
*/
|
||||||
|
message Failure {
|
||||||
|
optional FailureType code = 1; // computer-readable definition of the error state
|
||||||
|
optional string message = 2; // human-readable message of the error state
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device is waiting for HW button press.
|
||||||
|
* @next ButtonAck
|
||||||
|
* @next Cancel
|
||||||
|
*/
|
||||||
|
message ButtonRequest {
|
||||||
|
optional ButtonRequestType code = 1;
|
||||||
|
optional string data = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Computer agrees to wait for HW button press
|
||||||
|
* @prev ButtonRequest
|
||||||
|
*/
|
||||||
|
message ButtonAck {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device is asking computer to show PIN matrix and awaits PIN encoded using this matrix scheme
|
||||||
|
* @next PinMatrixAck
|
||||||
|
* @next Cancel
|
||||||
|
*/
|
||||||
|
message PinMatrixRequest {
|
||||||
|
optional PinMatrixRequestType type = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Computer responds with encoded PIN
|
||||||
|
* @prev PinMatrixRequest
|
||||||
|
*/
|
||||||
|
message PinMatrixAck {
|
||||||
|
required string pin = 1; // matrix encoded PIN entered by user
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Abort last operation that required user interaction
|
||||||
|
* @prev ButtonRequest
|
||||||
|
* @prev PinMatrixRequest
|
||||||
|
* @prev PassphraseRequest
|
||||||
|
*/
|
||||||
|
message Cancel {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device awaits encryption passphrase
|
||||||
|
* @next PassphraseAck
|
||||||
|
* @next Cancel
|
||||||
|
*/
|
||||||
|
message PassphraseRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Send passphrase back
|
||||||
|
* @prev PassphraseRequest
|
||||||
|
*/
|
||||||
|
message PassphraseAck {
|
||||||
|
required string passphrase = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Request a sample of random data generated by hardware RNG. May be used for testing.
|
||||||
|
* @next ButtonRequest
|
||||||
|
* @next Entropy
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message GetEntropy {
|
||||||
|
required uint32 size = 1; // size of requested entropy
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Reply with random data generated by internal RNG
|
||||||
|
* @prev GetEntropy
|
||||||
|
*/
|
||||||
|
message Entropy {
|
||||||
|
required bytes entropy = 1; // stream of random generated bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device for public key corresponding to address_n path
|
||||||
|
* @next PassphraseRequest
|
||||||
|
* @next PublicKey
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message GetPublicKey {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional string ecdsa_curve_name = 2; // ECDSA curve name to use
|
||||||
|
optional bool show_display = 3; // optionally show on display before sending the result
|
||||||
|
optional string coin_name = 4 [default='Bitcoin'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Contains public key derived from device private seed
|
||||||
|
* @prev GetPublicKey
|
||||||
|
*/
|
||||||
|
message PublicKey {
|
||||||
|
required HDNodeType node = 1; // BIP32 public node
|
||||||
|
optional string xpub = 2; // serialized form of public node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device for address corresponding to address_n path
|
||||||
|
* @next PassphraseRequest
|
||||||
|
* @next Address
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message GetAddress {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional string coin_name = 2 [default='Bitcoin'];
|
||||||
|
optional bool show_display = 3 ; // optionally show on display before sending the result
|
||||||
|
optional MultisigRedeemScriptType multisig = 4; // filled if we are showing a multisig address
|
||||||
|
optional InputScriptType script_type = 5 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device for Ethereum address corresponding to address_n path
|
||||||
|
* @next PassphraseRequest
|
||||||
|
* @next EthereumAddress
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumGetAddress {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional bool show_display = 2; // optionally show on display before sending the result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Contains address derived from device private seed
|
||||||
|
* @prev GetAddress
|
||||||
|
*/
|
||||||
|
message Address {
|
||||||
|
required string address = 1; // Coin address in Base58 encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Contains an Ethereum address derived from device private seed
|
||||||
|
* @prev EthereumGetAddress
|
||||||
|
*/
|
||||||
|
message EthereumAddress {
|
||||||
|
required bytes address = 1; // Coin address as an Ethereum 160 bit hash
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Request device to wipe all sensitive data and settings
|
||||||
|
* @next ButtonRequest
|
||||||
|
*/
|
||||||
|
message WipeDevice {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Load seed and related internal settings from the computer
|
||||||
|
* @next ButtonRequest
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message LoadDevice {
|
||||||
|
optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words)
|
||||||
|
optional HDNodeType node = 2; // BIP-32 node
|
||||||
|
optional string pin = 3; // set PIN protection
|
||||||
|
optional bool passphrase_protection = 4; // enable master node encryption using passphrase
|
||||||
|
optional string language = 5 [default='english']; // device language
|
||||||
|
optional string label = 6; // device label
|
||||||
|
optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum
|
||||||
|
optional uint32 u2f_counter = 8; // U2F counter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to do initialization involving user interaction
|
||||||
|
* @next EntropyRequest
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message ResetDevice {
|
||||||
|
optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy
|
||||||
|
optional uint32 strength = 2 [default=256]; // strength of seed in bits
|
||||||
|
optional bool passphrase_protection = 3; // enable master node encryption using passphrase
|
||||||
|
optional bool pin_protection = 4; // enable PIN protection
|
||||||
|
optional string language = 5 [default='english']; // device language
|
||||||
|
optional string label = 6; // device label
|
||||||
|
optional uint32 u2f_counter = 7; // U2F counter
|
||||||
|
optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Perform backup of the device seed if not backed up using ResetDevice
|
||||||
|
* @next ButtonRequest
|
||||||
|
*/
|
||||||
|
message BackupDevice {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Ask for additional entropy from host computer
|
||||||
|
* @prev ResetDevice
|
||||||
|
* @next EntropyAck
|
||||||
|
*/
|
||||||
|
message EntropyRequest {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Provide additional entropy for seed generation function
|
||||||
|
* @prev EntropyRequest
|
||||||
|
* @next ButtonRequest
|
||||||
|
*/
|
||||||
|
message EntropyAck {
|
||||||
|
optional bytes entropy = 1; // 256 bits (32 bytes) of random data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Start recovery workflow asking user for specific words of mnemonic
|
||||||
|
* Used to recovery device safely even on untrusted computer.
|
||||||
|
* @next WordRequest
|
||||||
|
*/
|
||||||
|
message RecoveryDevice {
|
||||||
|
optional uint32 word_count = 1; // number of words in BIP-39 mnemonic
|
||||||
|
optional bool passphrase_protection = 2; // enable master node encryption using passphrase
|
||||||
|
optional bool pin_protection = 3; // enable PIN protection
|
||||||
|
optional string language = 4 [default='english']; // device language
|
||||||
|
optional string label = 5; // device label
|
||||||
|
optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process
|
||||||
|
// 7 reserved for unused recovery method
|
||||||
|
optional uint32 type = 8; // supported recovery type (see RecoveryType)
|
||||||
|
optional uint32 u2f_counter = 9; // U2F counter
|
||||||
|
optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device is waiting for user to enter word of the mnemonic
|
||||||
|
* Its position is shown only on device's internal display.
|
||||||
|
* @prev RecoveryDevice
|
||||||
|
* @prev WordAck
|
||||||
|
*/
|
||||||
|
message WordRequest {
|
||||||
|
optional WordRequestType type = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Computer replies with word from the mnemonic
|
||||||
|
* @prev WordRequest
|
||||||
|
* @next WordRequest
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message WordAck {
|
||||||
|
required string word = 1; // one word of mnemonic on asked position
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////
|
||||||
|
// Message signing messages //
|
||||||
|
//////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to sign message
|
||||||
|
* @next MessageSignature
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message SignMessage {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
required bytes message = 2; // message to be signed
|
||||||
|
optional string coin_name = 3 [default='Bitcoin']; // coin to use for signing
|
||||||
|
optional InputScriptType script_type = 4 [default=SPENDADDRESS]; // used to distinguish between various address formats (non-segwit, segwit, etc.)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to verify message
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message VerifyMessage {
|
||||||
|
optional string address = 1; // address to verify
|
||||||
|
optional bytes signature = 2; // signature to verify
|
||||||
|
optional bytes message = 3; // message to verify
|
||||||
|
optional string coin_name = 4 [default='Bitcoin']; // coin to use for verifying
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Signed message
|
||||||
|
* @prev SignMessage
|
||||||
|
*/
|
||||||
|
message MessageSignature {
|
||||||
|
optional string address = 1; // address used to sign the message
|
||||||
|
optional bytes signature = 2; // signature of the message
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////
|
||||||
|
// Encryption/decryption //
|
||||||
|
///////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to encrypt message
|
||||||
|
* @next EncryptedMessage
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EncryptMessage {
|
||||||
|
optional bytes pubkey = 1; // public key
|
||||||
|
optional bytes message = 2; // message to encrypt
|
||||||
|
optional bool display_only = 3; // show just on display? (don't send back via wire)
|
||||||
|
repeated uint32 address_n = 4; // BIP-32 path to derive the signing key from master node
|
||||||
|
optional string coin_name = 5 [default='Bitcoin']; // coin to use for signing
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Encrypted message
|
||||||
|
* @prev EncryptMessage
|
||||||
|
*/
|
||||||
|
message EncryptedMessage {
|
||||||
|
optional bytes nonce = 1; // nonce used during encryption
|
||||||
|
optional bytes message = 2; // encrypted message
|
||||||
|
optional bytes hmac = 3; // message hmac
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to decrypt message
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message DecryptMessage {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the decryption key from master node
|
||||||
|
optional bytes nonce = 2; // nonce used during encryption
|
||||||
|
optional bytes message = 3; // message to decrypt
|
||||||
|
optional bytes hmac = 4; // message hmac
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Decrypted message
|
||||||
|
* @prev DecryptedMessage
|
||||||
|
*/
|
||||||
|
message DecryptedMessage {
|
||||||
|
optional bytes message = 1; // decrypted message
|
||||||
|
optional string address = 2; // address used to sign the message (if used)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to encrypt or decrypt value of given key
|
||||||
|
* @next CipheredKeyValue
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message CipherKeyValue {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional string key = 2; // key component of key:value
|
||||||
|
optional bytes value = 3; // value component of key:value
|
||||||
|
optional bool encrypt = 4; // are we encrypting (True) or decrypting (False)?
|
||||||
|
optional bool ask_on_encrypt = 5; // should we ask on encrypt operation?
|
||||||
|
optional bool ask_on_decrypt = 6; // should we ask on decrypt operation?
|
||||||
|
optional bytes iv = 7; // initialization vector (will be computed if not set)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Return ciphered/deciphered value
|
||||||
|
* @prev CipherKeyValue
|
||||||
|
*/
|
||||||
|
message CipheredKeyValue {
|
||||||
|
optional bytes value = 1; // ciphered/deciphered value
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////
|
||||||
|
// Transaction signing messages //
|
||||||
|
//////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Estimated size of the transaction
|
||||||
|
* This behaves exactly like SignTx, which means that it can ask using TxRequest
|
||||||
|
* This call is non-blocking (except possible PassphraseRequest to unlock the seed)
|
||||||
|
* @next TxSize
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EstimateTxSize {
|
||||||
|
required uint32 outputs_count = 1; // number of transaction outputs
|
||||||
|
required uint32 inputs_count = 2; // number of transaction inputs
|
||||||
|
optional string coin_name = 3 [default='Bitcoin']; // coin to use
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Estimated size of the transaction
|
||||||
|
* @prev EstimateTxSize
|
||||||
|
*/
|
||||||
|
message TxSize {
|
||||||
|
optional uint32 tx_size = 1; // estimated size of transaction in bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to sign transaction
|
||||||
|
* @next PassphraseRequest
|
||||||
|
* @next PinMatrixRequest
|
||||||
|
* @next TxRequest
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message SignTx {
|
||||||
|
required uint32 outputs_count = 1; // number of transaction outputs
|
||||||
|
required uint32 inputs_count = 2; // number of transaction inputs
|
||||||
|
optional string coin_name = 3 [default='Bitcoin']; // coin to use
|
||||||
|
optional uint32 version = 4 [default=1]; // transaction version
|
||||||
|
optional uint32 lock_time = 5 [default=0]; // transaction lock_time
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Simplified transaction signing
|
||||||
|
* This method doesn't support streaming, so there are hardware limits in number of inputs and outputs.
|
||||||
|
* In case of success, the result is returned using TxRequest message.
|
||||||
|
* @next PassphraseRequest
|
||||||
|
* @next PinMatrixRequest
|
||||||
|
* @next TxRequest
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message SimpleSignTx {
|
||||||
|
repeated TxInputType inputs = 1; // transaction inputs
|
||||||
|
repeated TxOutputType outputs = 2; // transaction outputs
|
||||||
|
repeated TransactionType transactions = 3; // transactions whose outputs are used to build current inputs
|
||||||
|
optional string coin_name = 4 [default='Bitcoin']; // coin to use
|
||||||
|
optional uint32 version = 5 [default=1]; // transaction version
|
||||||
|
optional uint32 lock_time = 6 [default=0]; // transaction lock_time
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device asks for information for signing transaction or returns the last result
|
||||||
|
* If request_index is set, device awaits TxAck message (with fields filled in according to request_type)
|
||||||
|
* If signature_index is set, 'signature' contains signed input of signature_index's input
|
||||||
|
* @prev SignTx
|
||||||
|
* @prev SimpleSignTx
|
||||||
|
* @prev TxAck
|
||||||
|
*/
|
||||||
|
message TxRequest {
|
||||||
|
optional RequestType request_type = 1; // what should be filled in TxAck message?
|
||||||
|
optional TxRequestDetailsType details = 2; // request for tx details
|
||||||
|
optional TxRequestSerializedType serialized = 3; // serialized data and request for next
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Reported transaction data
|
||||||
|
* @prev TxRequest
|
||||||
|
* @next TxRequest
|
||||||
|
*/
|
||||||
|
message TxAck {
|
||||||
|
optional TransactionType tx = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to sign transaction
|
||||||
|
* All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
|
||||||
|
* Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
||||||
|
* @next PassphraseRequest
|
||||||
|
* @next PinMatrixRequest
|
||||||
|
* @next EthereumTxRequest
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumSignTx {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
optional bytes nonce = 2; // <=256 bit unsigned big endian
|
||||||
|
optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
|
||||||
|
optional bytes gas_limit = 4; // <=256 bit unsigned big endian
|
||||||
|
optional bytes to = 5; // 160 bit address hash
|
||||||
|
optional bytes value = 6; // <=256 bit unsigned big endian (in wei)
|
||||||
|
optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes)
|
||||||
|
optional uint32 data_length = 8; // Length of transaction payload
|
||||||
|
optional uint32 chain_id = 9; // Chain Id for EIP 155
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device asks for more data from transaction payload, or returns the signature.
|
||||||
|
* If data_length is set, device awaits that many more bytes of payload.
|
||||||
|
* Otherwise, the signature_* fields contain the computed transaction signature. All three fields will be present.
|
||||||
|
* @prev EthereumSignTx
|
||||||
|
* @next EthereumTxAck
|
||||||
|
*/
|
||||||
|
message EthereumTxRequest {
|
||||||
|
optional uint32 data_length = 1; // Number of bytes being requested (<= 1024)
|
||||||
|
optional uint32 signature_v = 2; // Computed signature (recovery parameter, limited to 27 or 28)
|
||||||
|
optional bytes signature_r = 3; // Computed signature R component (256 bit)
|
||||||
|
optional bytes signature_s = 4; // Computed signature S component (256 bit)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Transaction payload data.
|
||||||
|
* @prev EthereumTxRequest
|
||||||
|
* @next EthereumTxRequest
|
||||||
|
*/
|
||||||
|
message EthereumTxAck {
|
||||||
|
optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////
|
||||||
|
// Ethereum: Message signing messages //
|
||||||
|
////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to sign message
|
||||||
|
* @next EthereumMessageSignature
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumSignMessage {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
required bytes message = 2; // message to be signed
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to verify message
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message EthereumVerifyMessage {
|
||||||
|
optional bytes address = 1; // address to verify
|
||||||
|
optional bytes signature = 2; // signature to verify
|
||||||
|
optional bytes message = 3; // message to verify
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Signed message
|
||||||
|
* @prev EthereumSignMessage
|
||||||
|
*/
|
||||||
|
message EthereumMessageSignature {
|
||||||
|
optional bytes address = 1; // address used to sign the message
|
||||||
|
optional bytes signature = 2; // signature of the message
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////
|
||||||
|
// Identity messages //
|
||||||
|
///////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to sign identity
|
||||||
|
* @next SignedIdentity
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message SignIdentity {
|
||||||
|
optional IdentityType identity = 1; // identity
|
||||||
|
optional bytes challenge_hidden = 2; // non-visible challenge
|
||||||
|
optional string challenge_visual = 3; // challenge shown on display (e.g. date+time)
|
||||||
|
optional string ecdsa_curve_name = 4; // ECDSA curve name to use
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device provides signed identity
|
||||||
|
* @prev SignIdentity
|
||||||
|
*/
|
||||||
|
message SignedIdentity {
|
||||||
|
optional string address = 1; // identity address
|
||||||
|
optional bytes public_key = 2; // identity public key
|
||||||
|
optional bytes signature = 3; // signature of the identity data
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// ECDH messages //
|
||||||
|
///////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to generate ECDH session key
|
||||||
|
* @next ECDHSessionKey
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message GetECDHSessionKey {
|
||||||
|
optional IdentityType identity = 1; // identity
|
||||||
|
optional bytes peer_public_key = 2; // peer's public key
|
||||||
|
optional string ecdsa_curve_name = 3; // ECDSA curve name to use
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device provides ECDH session key
|
||||||
|
* @prev GetECDHSessionKey
|
||||||
|
*/
|
||||||
|
message ECDHSessionKey {
|
||||||
|
optional bytes session_key = 1; // ECDH session key
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////
|
||||||
|
// U2F messages //
|
||||||
|
///////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Set U2F counter
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message SetU2FCounter {
|
||||||
|
optional uint32 u2f_counter = 1; // counter
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////
|
||||||
|
// Bootloader messages //
|
||||||
|
/////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to erase its firmware (so it can be replaced via FirmwareUpload)
|
||||||
|
* @next Success
|
||||||
|
* @next FirmwareRequest
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message FirmwareErase {
|
||||||
|
optional uint32 length = 1; // length of new firmware
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Ask for firmware chunk
|
||||||
|
* @next FirmwareUpload
|
||||||
|
*/
|
||||||
|
message FirmwareRequest {
|
||||||
|
optional uint32 offset = 1; // offset of requested firmware chunk
|
||||||
|
optional uint32 length = 2; // length of requested firmware chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Send firmware in binary form to the device
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message FirmwareUpload {
|
||||||
|
required bytes payload = 1; // firmware to be loaded into device
|
||||||
|
optional bytes hash = 2; // hash of the payload
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Perform a device self-test
|
||||||
|
* @next Success
|
||||||
|
* @next Failure
|
||||||
|
*/
|
||||||
|
message SelfTest {
|
||||||
|
optional bytes payload = 1; // payload to be used in self-test
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
// Debug messages (only available if DebugLink is enabled) //
|
||||||
|
/////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: "Press" the button on the device
|
||||||
|
* @next Success
|
||||||
|
*/
|
||||||
|
message DebugLinkDecision {
|
||||||
|
required bool yes_no = 1; // true for "Confirm", false for "Cancel"
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Computer asks for device state
|
||||||
|
* @next DebugLinkState
|
||||||
|
*/
|
||||||
|
message DebugLinkGetState {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device current state
|
||||||
|
* @prev DebugLinkGetState
|
||||||
|
*/
|
||||||
|
message DebugLinkState {
|
||||||
|
optional bytes layout = 1; // raw buffer of display
|
||||||
|
optional string pin = 2; // current PIN, blank if PIN is not set/enabled
|
||||||
|
optional string matrix = 3; // current PIN matrix
|
||||||
|
optional string mnemonic = 4; // current BIP-39 mnemonic
|
||||||
|
optional HDNodeType node = 5; // current BIP-32 node
|
||||||
|
optional bool passphrase_protection = 6; // is node/mnemonic encrypted using passphrase?
|
||||||
|
optional string reset_word = 7; // word on device display during ResetDevice workflow
|
||||||
|
optional bytes reset_entropy = 8; // current entropy during ResetDevice workflow
|
||||||
|
optional string recovery_fake_word = 9; // (fake) word on display during RecoveryDevice workflow
|
||||||
|
optional uint32 recovery_word_pos = 10; // index of mnemonic word the device is expecting during RecoveryDevice workflow
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Ask device to restart
|
||||||
|
*/
|
||||||
|
message DebugLinkStop {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device wants host to log event
|
||||||
|
*/
|
||||||
|
message DebugLinkLog {
|
||||||
|
optional uint32 level = 1;
|
||||||
|
optional string bucket = 2;
|
||||||
|
optional string text = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Read memory from device
|
||||||
|
* @next DebugLinkMemory
|
||||||
|
*/
|
||||||
|
message DebugLinkMemoryRead {
|
||||||
|
optional uint32 address = 1;
|
||||||
|
optional uint32 length = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response: Device sends memory back
|
||||||
|
* @prev DebugLinkMemoryRead
|
||||||
|
*/
|
||||||
|
message DebugLinkMemory {
|
||||||
|
optional bytes memory = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Write memory to device.
|
||||||
|
* WARNING: Writing to the wrong location can irreparably break the device.
|
||||||
|
*/
|
||||||
|
message DebugLinkMemoryWrite {
|
||||||
|
optional uint32 address = 1;
|
||||||
|
optional bytes memory = 2;
|
||||||
|
optional bool flash = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request: Erase block of flash on device
|
||||||
|
* WARNING: Writing to the wrong location can irreparably break the device.
|
||||||
|
*/
|
||||||
|
message DebugLinkFlashErase {
|
||||||
|
optional uint32 sector = 1;
|
||||||
|
}
|
46
accounts/usbwallet/internal/trezor/trezor.go
Normal file
46
accounts/usbwallet/internal/trezor/trezor.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// This file contains the implementation for interacting with the Trezor hardware
|
||||||
|
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||||
|
// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
|
||||||
|
|
||||||
|
//go:generate protoc --go_out=Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor,import_path=trezor:. types.proto messages.proto
|
||||||
|
|
||||||
|
// Package trezor contains the wire protocol wrapper in Go.
|
||||||
|
package trezor
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type returns the protocol buffer type number of a specific message. If the
|
||||||
|
// message is nil, this method panics!
|
||||||
|
func Type(msg proto.Message) uint16 {
|
||||||
|
return uint16(MessageType_value["MessageType_"+reflect.TypeOf(msg).Elem().Name()])
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the friendly message type name of a specific protocol buffer
|
||||||
|
// type numbers.
|
||||||
|
func Name(kind uint16) string {
|
||||||
|
name := MessageType_name[int32(kind)]
|
||||||
|
if len(name) < 12 {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return name[12:]
|
||||||
|
}
|
1333
accounts/usbwallet/internal/trezor/types.pb.go
Normal file
1333
accounts/usbwallet/internal/trezor/types.pb.go
Normal file
File diff suppressed because it is too large
Load Diff
276
accounts/usbwallet/internal/trezor/types.proto
Normal file
276
accounts/usbwallet/internal/trezor/types.proto
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||||
|
// https://github.com/trezor/trezor-common/blob/master/protob/types.proto
|
||||||
|
// dated 28.07.2017, commit dd8ec3231fb5f7992360aff9bdfe30bb58130f4b.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Types for TREZOR communication
|
||||||
|
*
|
||||||
|
* @author Marek Palatinus <slush@satoshilabs.com>
|
||||||
|
* @version 1.2
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Sugar for easier handling in Java
|
||||||
|
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||||
|
option java_outer_classname = "TrezorType";
|
||||||
|
|
||||||
|
import "google/protobuf/descriptor.proto";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for specifying message direction and type of wire (normal/debug)
|
||||||
|
*/
|
||||||
|
extend google.protobuf.EnumValueOptions {
|
||||||
|
optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR
|
||||||
|
optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC
|
||||||
|
optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR
|
||||||
|
optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC
|
||||||
|
optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode
|
||||||
|
optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of failures returned by Failure message
|
||||||
|
* @used_in Failure
|
||||||
|
*/
|
||||||
|
enum FailureType {
|
||||||
|
Failure_UnexpectedMessage = 1;
|
||||||
|
Failure_ButtonExpected = 2;
|
||||||
|
Failure_DataError = 3;
|
||||||
|
Failure_ActionCancelled = 4;
|
||||||
|
Failure_PinExpected = 5;
|
||||||
|
Failure_PinCancelled = 6;
|
||||||
|
Failure_PinInvalid = 7;
|
||||||
|
Failure_InvalidSignature = 8;
|
||||||
|
Failure_ProcessError = 9;
|
||||||
|
Failure_NotEnoughFunds = 10;
|
||||||
|
Failure_NotInitialized = 11;
|
||||||
|
Failure_FirmwareError = 99;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of script which will be used for transaction output
|
||||||
|
* @used_in TxOutputType
|
||||||
|
*/
|
||||||
|
enum OutputScriptType {
|
||||||
|
PAYTOADDRESS = 0; // used for all addresses (bitcoin, p2sh, witness)
|
||||||
|
PAYTOSCRIPTHASH = 1; // p2sh address (deprecated; use PAYTOADDRESS)
|
||||||
|
PAYTOMULTISIG = 2; // only for change output
|
||||||
|
PAYTOOPRETURN = 3; // op_return
|
||||||
|
PAYTOWITNESS = 4; // only for change output
|
||||||
|
PAYTOP2SHWITNESS = 5; // only for change output
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of script which will be used for transaction output
|
||||||
|
* @used_in TxInputType
|
||||||
|
*/
|
||||||
|
enum InputScriptType {
|
||||||
|
SPENDADDRESS = 0; // standard p2pkh address
|
||||||
|
SPENDMULTISIG = 1; // p2sh multisig address
|
||||||
|
EXTERNAL = 2; // reserved for external inputs (coinjoin)
|
||||||
|
SPENDWITNESS = 3; // native segwit
|
||||||
|
SPENDP2SHWITNESS = 4; // segwit over p2sh (backward compatible)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of information required by transaction signing process
|
||||||
|
* @used_in TxRequest
|
||||||
|
*/
|
||||||
|
enum RequestType {
|
||||||
|
TXINPUT = 0;
|
||||||
|
TXOUTPUT = 1;
|
||||||
|
TXMETA = 2;
|
||||||
|
TXFINISHED = 3;
|
||||||
|
TXEXTRADATA = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of button request
|
||||||
|
* @used_in ButtonRequest
|
||||||
|
*/
|
||||||
|
enum ButtonRequestType {
|
||||||
|
ButtonRequest_Other = 1;
|
||||||
|
ButtonRequest_FeeOverThreshold = 2;
|
||||||
|
ButtonRequest_ConfirmOutput = 3;
|
||||||
|
ButtonRequest_ResetDevice = 4;
|
||||||
|
ButtonRequest_ConfirmWord = 5;
|
||||||
|
ButtonRequest_WipeDevice = 6;
|
||||||
|
ButtonRequest_ProtectCall = 7;
|
||||||
|
ButtonRequest_SignTx = 8;
|
||||||
|
ButtonRequest_FirmwareCheck = 9;
|
||||||
|
ButtonRequest_Address = 10;
|
||||||
|
ButtonRequest_PublicKey = 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of PIN request
|
||||||
|
* @used_in PinMatrixRequest
|
||||||
|
*/
|
||||||
|
enum PinMatrixRequestType {
|
||||||
|
PinMatrixRequestType_Current = 1;
|
||||||
|
PinMatrixRequestType_NewFirst = 2;
|
||||||
|
PinMatrixRequestType_NewSecond = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of recovery procedure. These should be used as bitmask, e.g.,
|
||||||
|
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix`
|
||||||
|
* listing every method supported by the host computer.
|
||||||
|
*
|
||||||
|
* Note that ScrambledWords must be supported by every implementation
|
||||||
|
* for backward compatibility; there is no way to not support it.
|
||||||
|
*
|
||||||
|
* @used_in RecoveryDevice
|
||||||
|
*/
|
||||||
|
enum RecoveryDeviceType {
|
||||||
|
// use powers of two when extending this field
|
||||||
|
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order
|
||||||
|
RecoveryDeviceType_Matrix = 1; // matrix recovery type
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of Recovery Word request
|
||||||
|
* @used_in WordRequest
|
||||||
|
*/
|
||||||
|
enum WordRequestType {
|
||||||
|
WordRequestType_Plain = 0;
|
||||||
|
WordRequestType_Matrix9 = 1;
|
||||||
|
WordRequestType_Matrix6 = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing BIP32 (hierarchical deterministic) node
|
||||||
|
* Used for imports of private key into the device and exporting public key out of device
|
||||||
|
* @used_in PublicKey
|
||||||
|
* @used_in LoadDevice
|
||||||
|
* @used_in DebugLinkState
|
||||||
|
* @used_in Storage
|
||||||
|
*/
|
||||||
|
message HDNodeType {
|
||||||
|
required uint32 depth = 1;
|
||||||
|
required uint32 fingerprint = 2;
|
||||||
|
required uint32 child_num = 3;
|
||||||
|
required bytes chain_code = 4;
|
||||||
|
optional bytes private_key = 5;
|
||||||
|
optional bytes public_key = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message HDNodePathType {
|
||||||
|
required HDNodeType node = 1; // BIP-32 node in deserialized form
|
||||||
|
repeated uint32 address_n = 2; // BIP-32 path to derive the key from node
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing Coin
|
||||||
|
* @used_in Features
|
||||||
|
*/
|
||||||
|
message CoinType {
|
||||||
|
optional string coin_name = 1;
|
||||||
|
optional string coin_shortcut = 2;
|
||||||
|
optional uint32 address_type = 3 [default=0];
|
||||||
|
optional uint64 maxfee_kb = 4;
|
||||||
|
optional uint32 address_type_p2sh = 5 [default=5];
|
||||||
|
optional string signed_message_header = 8;
|
||||||
|
optional uint32 xpub_magic = 9 [default=76067358]; // default=0x0488b21e
|
||||||
|
optional uint32 xprv_magic = 10 [default=76066276]; // default=0x0488ade4
|
||||||
|
optional bool segwit = 11;
|
||||||
|
optional uint32 forkid = 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type of redeem script used in input
|
||||||
|
* @used_in TxInputType
|
||||||
|
*/
|
||||||
|
message MultisigRedeemScriptType {
|
||||||
|
repeated HDNodePathType pubkeys = 1; // pubkeys from multisig address (sorted lexicographically)
|
||||||
|
repeated bytes signatures = 2; // existing signatures for partially signed input
|
||||||
|
optional uint32 m = 3; // "m" from n, how many valid signatures is necessary for spending
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing transaction input
|
||||||
|
* @used_in SimpleSignTx
|
||||||
|
* @used_in TransactionType
|
||||||
|
*/
|
||||||
|
message TxInputType {
|
||||||
|
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||||
|
required bytes prev_hash = 2; // hash of previous transaction output to spend by this input
|
||||||
|
required uint32 prev_index = 3; // index of previous output to spend
|
||||||
|
optional bytes script_sig = 4; // script signature, unset for tx to sign
|
||||||
|
optional uint32 sequence = 5 [default=4294967295]; // sequence (default=0xffffffff)
|
||||||
|
optional InputScriptType script_type = 6 [default=SPENDADDRESS]; // defines template of input script
|
||||||
|
optional MultisigRedeemScriptType multisig = 7; // Filled if input is going to spend multisig tx
|
||||||
|
optional uint64 amount = 8; // amount of previous transaction output (for segwit only)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing transaction output
|
||||||
|
* @used_in SimpleSignTx
|
||||||
|
* @used_in TransactionType
|
||||||
|
*/
|
||||||
|
message TxOutputType {
|
||||||
|
optional string address = 1; // target coin address in Base58 encoding
|
||||||
|
repeated uint32 address_n = 2; // BIP-32 path to derive the key from master node; has higher priority than "address"
|
||||||
|
required uint64 amount = 3; // amount to spend in satoshis
|
||||||
|
required OutputScriptType script_type = 4; // output script type
|
||||||
|
optional MultisigRedeemScriptType multisig = 5; // defines multisig address; script_type must be PAYTOMULTISIG
|
||||||
|
optional bytes op_return_data = 6; // defines op_return data; script_type must be PAYTOOPRETURN, amount must be 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing compiled transaction output
|
||||||
|
* @used_in TransactionType
|
||||||
|
*/
|
||||||
|
message TxOutputBinType {
|
||||||
|
required uint64 amount = 1;
|
||||||
|
required bytes script_pubkey = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing transaction
|
||||||
|
* @used_in SimpleSignTx
|
||||||
|
*/
|
||||||
|
message TransactionType {
|
||||||
|
optional uint32 version = 1;
|
||||||
|
repeated TxInputType inputs = 2;
|
||||||
|
repeated TxOutputBinType bin_outputs = 3;
|
||||||
|
repeated TxOutputType outputs = 5;
|
||||||
|
optional uint32 lock_time = 4;
|
||||||
|
optional uint32 inputs_cnt = 6;
|
||||||
|
optional uint32 outputs_cnt = 7;
|
||||||
|
optional bytes extra_data = 8;
|
||||||
|
optional uint32 extra_data_len = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing request details
|
||||||
|
* @used_in TxRequest
|
||||||
|
*/
|
||||||
|
message TxRequestDetailsType {
|
||||||
|
optional uint32 request_index = 1; // device expects TxAck message from the computer
|
||||||
|
optional bytes tx_hash = 2; // tx_hash of requested transaction
|
||||||
|
optional uint32 extra_data_len = 3; // length of requested extra data
|
||||||
|
optional uint32 extra_data_offset = 4; // offset of requested extra data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing serialized data
|
||||||
|
* @used_in TxRequest
|
||||||
|
*/
|
||||||
|
message TxRequestSerializedType {
|
||||||
|
optional uint32 signature_index = 1; // 'signature' field contains signed input of this index
|
||||||
|
optional bytes signature = 2; // signature of the signature_index input
|
||||||
|
optional bytes serialized_tx = 3; // part of serialized and signed transaction
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Structure representing identity data
|
||||||
|
* @used_in IdentityType
|
||||||
|
*/
|
||||||
|
message IdentityType {
|
||||||
|
optional string proto = 1; // proto part of URI
|
||||||
|
optional string user = 2; // user part of URI
|
||||||
|
optional string host = 3; // host part of URI
|
||||||
|
optional string port = 4; // port part of URI
|
||||||
|
optional string path = 5; // path part of URI
|
||||||
|
optional uint32 index = 6 [default=0]; // identity index
|
||||||
|
}
|
464
accounts/usbwallet/ledger.go
Normal file
464
accounts/usbwallet/ledger.go
Normal file
@ -0,0 +1,464 @@
|
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// This file contains the implementation for interacting with the Ledger hardware
|
||||||
|
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
||||||
|
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
||||||
|
|
||||||
|
package usbwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ledgerOpcode is an enumeration encoding the supported Ledger opcodes.
|
||||||
|
type ledgerOpcode byte
|
||||||
|
|
||||||
|
// ledgerParam1 is an enumeration encoding the supported Ledger parameters for
|
||||||
|
// specific opcodes. The same parameter values may be reused between opcodes.
|
||||||
|
type ledgerParam1 byte
|
||||||
|
|
||||||
|
// ledgerParam2 is an enumeration encoding the supported Ledger parameters for
|
||||||
|
// specific opcodes. The same parameter values may be reused between opcodes.
|
||||||
|
type ledgerParam2 byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
|
||||||
|
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
|
||||||
|
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
||||||
|
|
||||||
|
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
||||||
|
ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address
|
||||||
|
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
||||||
|
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
||||||
|
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
||||||
|
ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address
|
||||||
|
)
|
||||||
|
|
||||||
|
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
|
||||||
|
// if the device replies with a mismatching header. This usually means the device
|
||||||
|
// is in browser mode.
|
||||||
|
var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header")
|
||||||
|
|
||||||
|
// errLedgerInvalidVersionReply is the error message returned by a Ledger version retrieval
|
||||||
|
// when a response does arrive, but it does not contain the expected data.
|
||||||
|
var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply")
|
||||||
|
|
||||||
|
// ledgerDriver implements the communication with a Ledger hardware wallet.
|
||||||
|
type ledgerDriver struct {
|
||||||
|
device io.ReadWriter // USB device connection to communicate through
|
||||||
|
version [3]byte // Current version of the Ledger firmware (zero if app is offline)
|
||||||
|
browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch)
|
||||||
|
failure error // Any failure that would make the device unusable
|
||||||
|
log log.Logger // Contextual logger to tag the ledger with its id
|
||||||
|
}
|
||||||
|
|
||||||
|
// newLedgerDriver creates a new instance of a Ledger USB protocol driver.
|
||||||
|
func newLedgerDriver(logger log.Logger) driver {
|
||||||
|
return &ledgerDriver{
|
||||||
|
log: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status implements usbwallet.driver, returning various states the Ledger can
|
||||||
|
// currently be in.
|
||||||
|
func (w *ledgerDriver) Status() (string, error) {
|
||||||
|
if w.failure != nil {
|
||||||
|
return fmt.Sprintf("Failed: %v", w.failure), w.failure
|
||||||
|
}
|
||||||
|
if w.browser {
|
||||||
|
return "Ethereum app in browser mode", w.failure
|
||||||
|
}
|
||||||
|
if w.offline() {
|
||||||
|
return "Ethereum app offline", w.failure
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure
|
||||||
|
}
|
||||||
|
|
||||||
|
// offline returns whether the wallet and the Ethereum app is offline or not.
|
||||||
|
//
|
||||||
|
// The method assumes that the state lock is held!
|
||||||
|
func (w *ledgerDriver) offline() bool {
|
||||||
|
return w.version == [3]byte{0, 0, 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements usbwallet.driver, attempting to initialize the connection to the
|
||||||
|
// Ledger hardware wallet. The Ledger does not require a user passphrase, so that
|
||||||
|
// parameter is silently discarded.
|
||||||
|
func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||||
|
w.device, w.failure = device, nil
|
||||||
|
|
||||||
|
_, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath)
|
||||||
|
if err != nil {
|
||||||
|
// Ethereum app is not running or in browser mode, nothing more to do, return
|
||||||
|
if err == errLedgerReplyInvalidHeader {
|
||||||
|
w.browser = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Try to resolve the Ethereum app's version, will fail prior to v1.0.2
|
||||||
|
if w.version, err = w.ledgerVersion(); err != nil {
|
||||||
|
w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements usbwallet.driver, cleaning up and metadata maintained within
|
||||||
|
// the Ledger driver.
|
||||||
|
func (w *ledgerDriver) Close() error {
|
||||||
|
w.browser, w.version = false, [3]byte{}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heartbeat implements usbwallet.driver, performing a sanity check against the
|
||||||
|
// Ledger to see if it's still online.
|
||||||
|
func (w *ledgerDriver) Heartbeat() error {
|
||||||
|
if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive implements usbwallet.driver, sending a derivation request to the Ledger
|
||||||
|
// and returning the Ethereum address located on that derivation path.
|
||||||
|
func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, error) {
|
||||||
|
return w.ledgerDerive(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx implements usbwallet.driver, sending the transaction to the Ledger and
|
||||||
|
// waiting for the user to confirm or deny the transaction.
|
||||||
|
//
|
||||||
|
// Note, if the version of the Ethereum application running on the Ledger wallet is
|
||||||
|
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
|
||||||
|
// will be returned opposed to silently signing in Homestead mode.
|
||||||
|
func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||||
|
// If the Ethereum app doesn't run, abort
|
||||||
|
if w.offline() {
|
||||||
|
return common.Address{}, nil, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
// Ensure the wallet is capable of signing the given transaction
|
||||||
|
if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 {
|
||||||
|
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
||||||
|
}
|
||||||
|
// All infos gathered and metadata checks out, request signing
|
||||||
|
return w.ledgerSign(path, tx, chainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
||||||
|
// on the Ledger wallet.
|
||||||
|
//
|
||||||
|
// The version retrieval protocol is defined as follows:
|
||||||
|
//
|
||||||
|
// CLA | INS | P1 | P2 | Lc | Le
|
||||||
|
// ----+-----+----+----+----+---
|
||||||
|
// E0 | 06 | 00 | 00 | 00 | 04
|
||||||
|
//
|
||||||
|
// With no input data, and the output data being:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// ---------------------------------------------------+--------
|
||||||
|
// Flags 01: arbitrary data signature enabled by user | 1 byte
|
||||||
|
// Application major version | 1 byte
|
||||||
|
// Application minor version | 1 byte
|
||||||
|
// Application patch version | 1 byte
|
||||||
|
func (w *ledgerDriver) ledgerVersion() ([3]byte, error) {
|
||||||
|
// Send the request and wait for the response
|
||||||
|
reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil)
|
||||||
|
if err != nil {
|
||||||
|
return [3]byte{}, err
|
||||||
|
}
|
||||||
|
if len(reply) != 4 {
|
||||||
|
return [3]byte{}, errLedgerInvalidVersionReply
|
||||||
|
}
|
||||||
|
// Cache the version for future reference
|
||||||
|
var version [3]byte
|
||||||
|
copy(version[:], reply[1:])
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ledgerDerive retrieves the currently active Ethereum address from a Ledger
|
||||||
|
// wallet at the specified derivation path.
|
||||||
|
//
|
||||||
|
// The address derivation protocol is defined as follows:
|
||||||
|
//
|
||||||
|
// CLA | INS | P1 | P2 | Lc | Le
|
||||||
|
// ----+-----+----+----+-----+---
|
||||||
|
// E0 | 02 | 00 return address
|
||||||
|
// 01 display address and confirm before returning
|
||||||
|
// | 00: do not return the chain code
|
||||||
|
// | 01: return the chain code
|
||||||
|
// | var | 00
|
||||||
|
//
|
||||||
|
// Where the input data is:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// -------------------------------------------------+--------
|
||||||
|
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
||||||
|
// First derivation index (big endian) | 4 bytes
|
||||||
|
// ... | 4 bytes
|
||||||
|
// Last derivation index (big endian) | 4 bytes
|
||||||
|
//
|
||||||
|
// And the output data is:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// ------------------------+-------------------
|
||||||
|
// Public Key length | 1 byte
|
||||||
|
// Uncompressed Public Key | arbitrary
|
||||||
|
// Ethereum address length | 1 byte
|
||||||
|
// Ethereum address | 40 bytes hex ascii
|
||||||
|
// Chain code if requested | 32 bytes
|
||||||
|
func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) {
|
||||||
|
// Flatten the derivation path into the Ledger request
|
||||||
|
path := make([]byte, 1+4*len(derivationPath))
|
||||||
|
path[0] = byte(len(derivationPath))
|
||||||
|
for i, component := range derivationPath {
|
||||||
|
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
||||||
|
}
|
||||||
|
// Send the request and wait for the response
|
||||||
|
reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
// Discard the public key, we don't need that for now
|
||||||
|
if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
|
||||||
|
return common.Address{}, errors.New("reply lacks public key entry")
|
||||||
|
}
|
||||||
|
reply = reply[1+int(reply[0]):]
|
||||||
|
|
||||||
|
// Extract the Ethereum hex address string
|
||||||
|
if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
|
||||||
|
return common.Address{}, errors.New("reply lacks address entry")
|
||||||
|
}
|
||||||
|
hexstr := reply[1 : 1+int(reply[0])]
|
||||||
|
|
||||||
|
// Decode the hex sting into an Ethereum address and return
|
||||||
|
var address common.Address
|
||||||
|
hex.Decode(address[:], hexstr)
|
||||||
|
return address, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ledgerSign sends the transaction to the Ledger wallet, and waits for the user
|
||||||
|
// to confirm or deny the transaction.
|
||||||
|
//
|
||||||
|
// The transaction signing protocol is defined as follows:
|
||||||
|
//
|
||||||
|
// CLA | INS | P1 | P2 | Lc | Le
|
||||||
|
// ----+-----+----+----+-----+---
|
||||||
|
// E0 | 04 | 00: first transaction data block
|
||||||
|
// 80: subsequent transaction data block
|
||||||
|
// | 00 | variable | variable
|
||||||
|
//
|
||||||
|
// Where the input for the first transaction block (first 255 bytes) is:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// -------------------------------------------------+----------
|
||||||
|
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
||||||
|
// First derivation index (big endian) | 4 bytes
|
||||||
|
// ... | 4 bytes
|
||||||
|
// Last derivation index (big endian) | 4 bytes
|
||||||
|
// RLP transaction chunk | arbitrary
|
||||||
|
//
|
||||||
|
// And the input for subsequent transaction blocks (first 255 bytes) are:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// ----------------------+----------
|
||||||
|
// RLP transaction chunk | arbitrary
|
||||||
|
//
|
||||||
|
// And the output data is:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// ------------+---------
|
||||||
|
// signature V | 1 byte
|
||||||
|
// signature R | 32 bytes
|
||||||
|
// signature S | 32 bytes
|
||||||
|
func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||||
|
// Flatten the derivation path into the Ledger request
|
||||||
|
path := make([]byte, 1+4*len(derivationPath))
|
||||||
|
path[0] = byte(len(derivationPath))
|
||||||
|
for i, component := range derivationPath {
|
||||||
|
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
||||||
|
}
|
||||||
|
// Create the transaction RLP based on whether legacy or EIP155 signing was requeste
|
||||||
|
var (
|
||||||
|
txrlp []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if chainID == nil {
|
||||||
|
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
payload := append(path, txrlp...)
|
||||||
|
|
||||||
|
// Send the request and wait for the response
|
||||||
|
var (
|
||||||
|
op = ledgerP1InitTransactionData
|
||||||
|
reply []byte
|
||||||
|
)
|
||||||
|
for len(payload) > 0 {
|
||||||
|
// Calculate the size of the next data chunk
|
||||||
|
chunk := 255
|
||||||
|
if chunk > len(payload) {
|
||||||
|
chunk = len(payload)
|
||||||
|
}
|
||||||
|
// Send the chunk over, ensuring it's processed correctly
|
||||||
|
reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk])
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
// Shift the payload and ensure subsequent chunks are marked as such
|
||||||
|
payload = payload[chunk:]
|
||||||
|
op = ledgerP1ContTransactionData
|
||||||
|
}
|
||||||
|
// Extract the Ethereum signature and do a sanity validation
|
||||||
|
if len(reply) != 65 {
|
||||||
|
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||||
|
}
|
||||||
|
signature := append(reply[1:], reply[0])
|
||||||
|
|
||||||
|
// Create the correct signer and signature transform based on the chain ID
|
||||||
|
var signer types.Signer
|
||||||
|
if chainID == nil {
|
||||||
|
signer = new(types.HomesteadSigner)
|
||||||
|
} else {
|
||||||
|
signer = types.NewEIP155Signer(chainID)
|
||||||
|
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
||||||
|
}
|
||||||
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
sender, err := types.Sender(signer, signed)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
return sender, signed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
||||||
|
// message and retrieving the response.
|
||||||
|
//
|
||||||
|
// The common transport header is defined as follows:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// --------------------------------------+----------
|
||||||
|
// Communication channel ID (big endian) | 2 bytes
|
||||||
|
// Command tag | 1 byte
|
||||||
|
// Packet sequence index (big endian) | 2 bytes
|
||||||
|
// Payload | arbitrary
|
||||||
|
//
|
||||||
|
// The Communication channel ID allows commands multiplexing over the same
|
||||||
|
// physical link. It is not used for the time being, and should be set to 0101
|
||||||
|
// to avoid compatibility issues with implementations ignoring a leading 00 byte.
|
||||||
|
//
|
||||||
|
// The Command tag describes the message content. Use TAG_APDU (0x05) for standard
|
||||||
|
// APDU payloads, or TAG_PING (0x02) for a simple link test.
|
||||||
|
//
|
||||||
|
// The Packet sequence index describes the current sequence for fragmented payloads.
|
||||||
|
// The first fragment index is 0x00.
|
||||||
|
//
|
||||||
|
// APDU Command payloads are encoded as follows:
|
||||||
|
//
|
||||||
|
// Description | Length
|
||||||
|
// -----------------------------------
|
||||||
|
// APDU length (big endian) | 2 bytes
|
||||||
|
// APDU CLA | 1 byte
|
||||||
|
// APDU INS | 1 byte
|
||||||
|
// APDU P1 | 1 byte
|
||||||
|
// APDU P2 | 1 byte
|
||||||
|
// APDU length | 1 byte
|
||||||
|
// Optional APDU data | arbitrary
|
||||||
|
func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
|
||||||
|
// Construct the message payload, possibly split into multiple chunks
|
||||||
|
apdu := make([]byte, 2, 7+len(data))
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(apdu, uint16(5+len(data)))
|
||||||
|
apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...)
|
||||||
|
apdu = append(apdu, data...)
|
||||||
|
|
||||||
|
// Stream all the chunks to the device
|
||||||
|
header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended
|
||||||
|
chunk := make([]byte, 64)
|
||||||
|
space := len(chunk) - len(header)
|
||||||
|
|
||||||
|
for i := 0; len(apdu) > 0; i++ {
|
||||||
|
// Construct the new message to stream
|
||||||
|
chunk = append(chunk[:0], header...)
|
||||||
|
binary.BigEndian.PutUint16(chunk[3:], uint16(i))
|
||||||
|
|
||||||
|
if len(apdu) > space {
|
||||||
|
chunk = append(chunk, apdu[:space]...)
|
||||||
|
apdu = apdu[space:]
|
||||||
|
} else {
|
||||||
|
chunk = append(chunk, apdu...)
|
||||||
|
apdu = nil
|
||||||
|
}
|
||||||
|
// Send over to the device
|
||||||
|
w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk))
|
||||||
|
if _, err := w.device.Write(chunk); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stream the reply back from the wallet in 64 byte chunks
|
||||||
|
var reply []byte
|
||||||
|
chunk = chunk[:64] // Yeah, we surely have enough space
|
||||||
|
for {
|
||||||
|
// Read the next chunk from the Ledger wallet
|
||||||
|
if _, err := io.ReadFull(w.device, chunk); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk))
|
||||||
|
|
||||||
|
// Make sure the transport header matches
|
||||||
|
if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 {
|
||||||
|
return nil, errLedgerReplyInvalidHeader
|
||||||
|
}
|
||||||
|
// If it's the first chunk, retrieve the total message length
|
||||||
|
var payload []byte
|
||||||
|
|
||||||
|
if chunk[3] == 0x00 && chunk[4] == 0x00 {
|
||||||
|
reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7])))
|
||||||
|
payload = chunk[7:]
|
||||||
|
} else {
|
||||||
|
payload = chunk[5:]
|
||||||
|
}
|
||||||
|
// Append to the reply and stop when filled up
|
||||||
|
if left := cap(reply) - len(reply); left > len(payload) {
|
||||||
|
reply = append(reply, payload...)
|
||||||
|
} else {
|
||||||
|
reply = append(reply, payload[:left]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return reply[:len(reply)-2], nil
|
||||||
|
}
|
@ -1,903 +0,0 @@
|
|||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// This file contains the implementation for interacting with the Ledger hardware
|
|
||||||
// wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo:
|
|
||||||
// https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc
|
|
||||||
|
|
||||||
package usbwallet
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
ethereum "github.com/ethereum/go-ethereum"
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
|
||||||
"github.com/karalabe/hid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Maximum time between wallet health checks to detect USB unplugs.
|
|
||||||
const ledgerHeartbeatCycle = time.Second
|
|
||||||
|
|
||||||
// Minimum time to wait between self derivation attempts, even it the user is
|
|
||||||
// requesting accounts like crazy.
|
|
||||||
const ledgerSelfDeriveThrottling = time.Second
|
|
||||||
|
|
||||||
// ledgerOpcode is an enumeration encoding the supported Ledger opcodes.
|
|
||||||
type ledgerOpcode byte
|
|
||||||
|
|
||||||
// ledgerParam1 is an enumeration encoding the supported Ledger parameters for
|
|
||||||
// specific opcodes. The same parameter values may be reused between opcodes.
|
|
||||||
type ledgerParam1 byte
|
|
||||||
|
|
||||||
// ledgerParam2 is an enumeration encoding the supported Ledger parameters for
|
|
||||||
// specific opcodes. The same parameter values may be reused between opcodes.
|
|
||||||
type ledgerParam2 byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
|
|
||||||
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
|
|
||||||
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
|
|
||||||
|
|
||||||
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
|
|
||||||
ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address
|
|
||||||
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
|
|
||||||
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
|
|
||||||
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
|
|
||||||
ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address
|
|
||||||
)
|
|
||||||
|
|
||||||
// errReplyInvalidHeader is the error message returned by a Ledger data exchange
|
|
||||||
// if the device replies with a mismatching header. This usually means the device
|
|
||||||
// is in browser mode.
|
|
||||||
var errReplyInvalidHeader = errors.New("invalid reply header")
|
|
||||||
|
|
||||||
// errInvalidVersionReply is the error message returned by a Ledger version retrieval
|
|
||||||
// when a response does arrive, but it does not contain the expected data.
|
|
||||||
var errInvalidVersionReply = errors.New("invalid version reply")
|
|
||||||
|
|
||||||
// ledgerWallet represents a live USB Ledger hardware wallet.
|
|
||||||
type ledgerWallet struct {
|
|
||||||
hub *LedgerHub // USB hub the device originates from (TODO(karalabe): remove if hotplug lands on Windows)
|
|
||||||
url *accounts.URL // Textual URL uniquely identifying this wallet
|
|
||||||
|
|
||||||
info hid.DeviceInfo // Known USB device infos about the wallet
|
|
||||||
device *hid.Device // USB device advertising itself as a Ledger wallet
|
|
||||||
failure error // Any failure that would make the device unusable
|
|
||||||
|
|
||||||
version [3]byte // Current version of the Ledger Ethereum app (zero if app is offline)
|
|
||||||
browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch)
|
|
||||||
accounts []accounts.Account // List of derive accounts pinned on the Ledger
|
|
||||||
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
|
||||||
|
|
||||||
deriveNextPath accounts.DerivationPath // Next derivation path for account auto-discovery
|
|
||||||
deriveNextAddr common.Address // Next derived account address for auto-discovery
|
|
||||||
deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with
|
|
||||||
deriveReq chan chan struct{} // Channel to request a self-derivation on
|
|
||||||
deriveQuit chan chan error // Channel to terminate the self-deriver with
|
|
||||||
|
|
||||||
healthQuit chan chan error
|
|
||||||
|
|
||||||
// Locking a hardware wallet is a bit special. Since hardware devices are lower
|
|
||||||
// performing, any communication with them might take a non negligible amount of
|
|
||||||
// time. Worse still, waiting for user confirmation can take arbitrarily long,
|
|
||||||
// but exclusive communication must be upheld during. Locking the entire wallet
|
|
||||||
// in the mean time however would stall any parts of the system that don't want
|
|
||||||
// to communicate, just read some state (e.g. list the accounts).
|
|
||||||
//
|
|
||||||
// As such, a hardware wallet needs two locks to function correctly. A state
|
|
||||||
// lock can be used to protect the wallet's software-side internal state, which
|
|
||||||
// must not be held exlusively during hardware communication. A communication
|
|
||||||
// lock can be used to achieve exclusive access to the device itself, this one
|
|
||||||
// however should allow "skipping" waiting for operations that might want to
|
|
||||||
// use the device, but can live without too (e.g. account self-derivation).
|
|
||||||
//
|
|
||||||
// Since we have two locks, it's important to know how to properly use them:
|
|
||||||
// - Communication requires the `device` to not change, so obtaining the
|
|
||||||
// commsLock should be done after having a stateLock.
|
|
||||||
// - Communication must not disable read access to the wallet state, so it
|
|
||||||
// must only ever hold a *read* lock to stateLock.
|
|
||||||
commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked
|
|
||||||
stateLock sync.RWMutex // Protects read and write access to the wallet struct fields
|
|
||||||
|
|
||||||
log log.Logger // Contextual logger to tag the ledger with its id
|
|
||||||
}
|
|
||||||
|
|
||||||
// URL implements accounts.Wallet, returning the URL of the Ledger device.
|
|
||||||
func (w *ledgerWallet) URL() accounts.URL {
|
|
||||||
return *w.url // Immutable, no need for a lock
|
|
||||||
}
|
|
||||||
|
|
||||||
// Status implements accounts.Wallet, always whether the Ledger is opened, closed
|
|
||||||
// or whether the Ethereum app was not started on it.
|
|
||||||
func (w *ledgerWallet) Status() string {
|
|
||||||
w.stateLock.RLock() // No device communication, state lock is enough
|
|
||||||
defer w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
if w.failure != nil {
|
|
||||||
return fmt.Sprintf("Failed: %v", w.failure)
|
|
||||||
}
|
|
||||||
if w.device == nil {
|
|
||||||
return "Closed"
|
|
||||||
}
|
|
||||||
if w.browser {
|
|
||||||
return "Ethereum app in browser mode"
|
|
||||||
}
|
|
||||||
if w.offline() {
|
|
||||||
return "Ethereum app offline"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2])
|
|
||||||
}
|
|
||||||
|
|
||||||
// offline returns whether the wallet and the Ethereum app is offline or not.
|
|
||||||
//
|
|
||||||
// The method assumes that the state lock is held!
|
|
||||||
func (w *ledgerWallet) offline() bool {
|
|
||||||
return w.version == [3]byte{0, 0, 0}
|
|
||||||
}
|
|
||||||
|
|
||||||
// failed returns if the USB device wrapped by the wallet failed for some reason.
|
|
||||||
// This is used by the device scanner to report failed wallets as departed.
|
|
||||||
//
|
|
||||||
// The method assumes that the state lock is *not* held!
|
|
||||||
func (w *ledgerWallet) failed() bool {
|
|
||||||
w.stateLock.RLock() // No device communication, state lock is enough
|
|
||||||
defer w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
return w.failure != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open implements accounts.Wallet, attempting to open a USB connection to the
|
|
||||||
// Ledger hardware wallet. The Ledger does not require a user passphrase, so that
|
|
||||||
// parameter is silently discarded.
|
|
||||||
func (w *ledgerWallet) Open(passphrase string) error {
|
|
||||||
w.stateLock.Lock() // State lock is enough since there's no connection yet at this point
|
|
||||||
defer w.stateLock.Unlock()
|
|
||||||
|
|
||||||
// If the wallet was already opened, don't try to open again
|
|
||||||
if w.device != nil {
|
|
||||||
return accounts.ErrWalletAlreadyOpen
|
|
||||||
}
|
|
||||||
// Otherwise iterate over all USB devices and find this again (no way to directly do this)
|
|
||||||
device, err := w.info.Open()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Wallet seems to be successfully opened, guess if the Ethereum app is running
|
|
||||||
w.device = device
|
|
||||||
w.commsLock = make(chan struct{}, 1)
|
|
||||||
w.commsLock <- struct{}{} // Enable lock
|
|
||||||
|
|
||||||
w.paths = make(map[common.Address]accounts.DerivationPath)
|
|
||||||
|
|
||||||
w.deriveReq = make(chan chan struct{})
|
|
||||||
w.deriveQuit = make(chan chan error)
|
|
||||||
w.healthQuit = make(chan chan error)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
go w.heartbeat()
|
|
||||||
go w.selfDerive()
|
|
||||||
}()
|
|
||||||
|
|
||||||
if _, err = w.ledgerDerive(accounts.DefaultBaseDerivationPath); err != nil {
|
|
||||||
// Ethereum app is not running or in browser mode, nothing more to do, return
|
|
||||||
if err == errReplyInvalidHeader {
|
|
||||||
w.browser = true
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Try to resolve the Ethereum app's version, will fail prior to v1.0.2
|
|
||||||
if w.version, err = w.ledgerVersion(); err != nil {
|
|
||||||
w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// heartbeat is a health check loop for the Ledger wallets to periodically verify
|
|
||||||
// whether they are still present or if they malfunctioned. It is needed because:
|
|
||||||
// - libusb on Windows doesn't support hotplug, so we can't detect USB unplugs
|
|
||||||
// - communication timeout on the Ledger requires a device power cycle to fix
|
|
||||||
func (w *ledgerWallet) heartbeat() {
|
|
||||||
w.log.Debug("Ledger health-check started")
|
|
||||||
defer w.log.Debug("Ledger health-check stopped")
|
|
||||||
|
|
||||||
// Execute heartbeat checks until termination or error
|
|
||||||
var (
|
|
||||||
errc chan error
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for errc == nil && err == nil {
|
|
||||||
// Wait until termination is requested or the heartbeat cycle arrives
|
|
||||||
select {
|
|
||||||
case errc = <-w.healthQuit:
|
|
||||||
// Termination requested
|
|
||||||
continue
|
|
||||||
case <-time.After(ledgerHeartbeatCycle):
|
|
||||||
// Heartbeat time
|
|
||||||
}
|
|
||||||
// Execute a tiny data exchange to see responsiveness
|
|
||||||
w.stateLock.RLock()
|
|
||||||
if w.device == nil {
|
|
||||||
// Terminated while waiting for the lock
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
<-w.commsLock // Don't lock state while resolving version
|
|
||||||
_, err = w.ledgerVersion()
|
|
||||||
w.commsLock <- struct{}{}
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
if err != nil && err != errInvalidVersionReply {
|
|
||||||
w.stateLock.Lock() // Lock state to tear the wallet down
|
|
||||||
w.failure = err
|
|
||||||
w.close()
|
|
||||||
w.stateLock.Unlock()
|
|
||||||
}
|
|
||||||
// Ignore non hardware related errors
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
// In case of error, wait for termination
|
|
||||||
if err != nil {
|
|
||||||
w.log.Debug("Ledger health-check failed", "err", err)
|
|
||||||
errc = <-w.healthQuit
|
|
||||||
}
|
|
||||||
errc <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close implements accounts.Wallet, closing the USB connection to the Ledger.
|
|
||||||
func (w *ledgerWallet) Close() error {
|
|
||||||
// Ensure the wallet was opened
|
|
||||||
w.stateLock.RLock()
|
|
||||||
hQuit, dQuit := w.healthQuit, w.deriveQuit
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
// Terminate the health checks
|
|
||||||
var herr error
|
|
||||||
if hQuit != nil {
|
|
||||||
errc := make(chan error)
|
|
||||||
hQuit <- errc
|
|
||||||
herr = <-errc // Save for later, we *must* close the USB
|
|
||||||
}
|
|
||||||
// Terminate the self-derivations
|
|
||||||
var derr error
|
|
||||||
if dQuit != nil {
|
|
||||||
errc := make(chan error)
|
|
||||||
dQuit <- errc
|
|
||||||
derr = <-errc // Save for later, we *must* close the USB
|
|
||||||
}
|
|
||||||
// Terminate the device connection
|
|
||||||
w.stateLock.Lock()
|
|
||||||
defer w.stateLock.Unlock()
|
|
||||||
|
|
||||||
w.healthQuit = nil
|
|
||||||
w.deriveQuit = nil
|
|
||||||
w.deriveReq = nil
|
|
||||||
|
|
||||||
if err := w.close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if herr != nil {
|
|
||||||
return herr
|
|
||||||
}
|
|
||||||
return derr
|
|
||||||
}
|
|
||||||
|
|
||||||
// close is the internal wallet closer that terminates the USB connection and
|
|
||||||
// resets all the fields to their defaults.
|
|
||||||
//
|
|
||||||
// Note, close assumes the state lock is held!
|
|
||||||
func (w *ledgerWallet) close() error {
|
|
||||||
// Allow duplicate closes, especially for health-check failures
|
|
||||||
if w.device == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Close the device, clear everything, then return
|
|
||||||
w.device.Close()
|
|
||||||
w.device = nil
|
|
||||||
|
|
||||||
w.browser, w.version = false, [3]byte{}
|
|
||||||
w.accounts, w.paths = nil, nil
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accounts implements accounts.Wallet, returning the list of accounts pinned to
|
|
||||||
// the Ledger hardware wallet. If self-derivation was enabled, the account list
|
|
||||||
// is periodically expanded based on current chain state.
|
|
||||||
func (w *ledgerWallet) Accounts() []accounts.Account {
|
|
||||||
// Attempt self-derivation if it's running
|
|
||||||
reqc := make(chan struct{}, 1)
|
|
||||||
select {
|
|
||||||
case w.deriveReq <- reqc:
|
|
||||||
// Self-derivation request accepted, wait for it
|
|
||||||
<-reqc
|
|
||||||
default:
|
|
||||||
// Self-derivation offline, throttled or busy, skip
|
|
||||||
}
|
|
||||||
// Return whatever account list we ended up with
|
|
||||||
w.stateLock.RLock()
|
|
||||||
defer w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
cpy := make([]accounts.Account, len(w.accounts))
|
|
||||||
copy(cpy, w.accounts)
|
|
||||||
return cpy
|
|
||||||
}
|
|
||||||
|
|
||||||
// selfDerive is an account derivation loop that upon request attempts to find
|
|
||||||
// new non-zero accounts.
|
|
||||||
func (w *ledgerWallet) selfDerive() {
|
|
||||||
w.log.Debug("Ledger self-derivation started")
|
|
||||||
defer w.log.Debug("Ledger self-derivation stopped")
|
|
||||||
|
|
||||||
// Execute self-derivations until termination or error
|
|
||||||
var (
|
|
||||||
reqc chan struct{}
|
|
||||||
errc chan error
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for errc == nil && err == nil {
|
|
||||||
// Wait until either derivation or termination is requested
|
|
||||||
select {
|
|
||||||
case errc = <-w.deriveQuit:
|
|
||||||
// Termination requested
|
|
||||||
continue
|
|
||||||
case reqc = <-w.deriveReq:
|
|
||||||
// Account discovery requested
|
|
||||||
}
|
|
||||||
// Derivation needs a chain and device access, skip if either unavailable
|
|
||||||
w.stateLock.RLock()
|
|
||||||
if w.device == nil || w.deriveChain == nil || w.offline() {
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
reqc <- struct{}{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-w.commsLock:
|
|
||||||
default:
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
reqc <- struct{}{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Device lock obtained, derive the next batch of accounts
|
|
||||||
var (
|
|
||||||
accs []accounts.Account
|
|
||||||
paths []accounts.DerivationPath
|
|
||||||
|
|
||||||
nextAddr = w.deriveNextAddr
|
|
||||||
nextPath = w.deriveNextPath
|
|
||||||
|
|
||||||
context = context.Background()
|
|
||||||
)
|
|
||||||
for empty := false; !empty; {
|
|
||||||
// Retrieve the next derived Ethereum account
|
|
||||||
if nextAddr == (common.Address{}) {
|
|
||||||
if nextAddr, err = w.ledgerDerive(nextPath); err != nil {
|
|
||||||
w.log.Warn("Ledger account derivation failed", "err", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check the account's status against the current chain state
|
|
||||||
var (
|
|
||||||
balance *big.Int
|
|
||||||
nonce uint64
|
|
||||||
)
|
|
||||||
balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil)
|
|
||||||
if err != nil {
|
|
||||||
w.log.Warn("Ledger balance retrieval failed", "err", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil)
|
|
||||||
if err != nil {
|
|
||||||
w.log.Warn("Ledger nonce retrieval failed", "err", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// If the next account is empty, stop self-derivation, but add it nonetheless
|
|
||||||
if balance.Sign() == 0 && nonce == 0 {
|
|
||||||
empty = true
|
|
||||||
}
|
|
||||||
// We've just self-derived a new account, start tracking it locally
|
|
||||||
path := make(accounts.DerivationPath, len(nextPath))
|
|
||||||
copy(path[:], nextPath[:])
|
|
||||||
paths = append(paths, path)
|
|
||||||
|
|
||||||
account := accounts.Account{
|
|
||||||
Address: nextAddr,
|
|
||||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
|
||||||
}
|
|
||||||
accs = append(accs, account)
|
|
||||||
|
|
||||||
// Display a log message to the user for new (or previously empty accounts)
|
|
||||||
if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) {
|
|
||||||
w.log.Info("Ledger discovered new account", "address", nextAddr, "path", path, "balance", balance, "nonce", nonce)
|
|
||||||
}
|
|
||||||
// Fetch the next potential account
|
|
||||||
if !empty {
|
|
||||||
nextAddr = common.Address{}
|
|
||||||
nextPath[len(nextPath)-1]++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Self derivation complete, release device lock
|
|
||||||
w.commsLock <- struct{}{}
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
// Insert any accounts successfully derived
|
|
||||||
w.stateLock.Lock()
|
|
||||||
for i := 0; i < len(accs); i++ {
|
|
||||||
if _, ok := w.paths[accs[i].Address]; !ok {
|
|
||||||
w.accounts = append(w.accounts, accs[i])
|
|
||||||
w.paths[accs[i].Address] = paths[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Shift the self-derivation forward
|
|
||||||
// TODO(karalabe): don't overwrite changes from wallet.SelfDerive
|
|
||||||
w.deriveNextAddr = nextAddr
|
|
||||||
w.deriveNextPath = nextPath
|
|
||||||
w.stateLock.Unlock()
|
|
||||||
|
|
||||||
// Notify the user of termination and loop after a bit of time (to avoid trashing)
|
|
||||||
reqc <- struct{}{}
|
|
||||||
if err == nil {
|
|
||||||
select {
|
|
||||||
case errc = <-w.deriveQuit:
|
|
||||||
// Termination requested, abort
|
|
||||||
case <-time.After(ledgerSelfDeriveThrottling):
|
|
||||||
// Waited enough, willing to self-derive again
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// In case of error, wait for termination
|
|
||||||
if err != nil {
|
|
||||||
w.log.Debug("Ledger self-derivation failed", "err", err)
|
|
||||||
errc = <-w.deriveQuit
|
|
||||||
}
|
|
||||||
errc <- err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Contains implements accounts.Wallet, returning whether a particular account is
|
|
||||||
// or is not pinned into this Ledger instance. Although we could attempt to resolve
|
|
||||||
// unpinned accounts, that would be an non-negligible hardware operation.
|
|
||||||
func (w *ledgerWallet) Contains(account accounts.Account) bool {
|
|
||||||
w.stateLock.RLock()
|
|
||||||
defer w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
_, exists := w.paths[account.Address]
|
|
||||||
return exists
|
|
||||||
}
|
|
||||||
|
|
||||||
// Derive implements accounts.Wallet, deriving a new account at the specific
|
|
||||||
// derivation path. If pin is set to true, the account will be added to the list
|
|
||||||
// of tracked accounts.
|
|
||||||
func (w *ledgerWallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
|
||||||
// Try to derive the actual account and update its URL if successful
|
|
||||||
w.stateLock.RLock() // Avoid device disappearing during derivation
|
|
||||||
|
|
||||||
if w.device == nil || w.offline() {
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
return accounts.Account{}, accounts.ErrWalletClosed
|
|
||||||
}
|
|
||||||
<-w.commsLock // Avoid concurrent hardware access
|
|
||||||
address, err := w.ledgerDerive(path)
|
|
||||||
w.commsLock <- struct{}{}
|
|
||||||
|
|
||||||
w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
// If an error occurred or no pinning was requested, return
|
|
||||||
if err != nil {
|
|
||||||
return accounts.Account{}, err
|
|
||||||
}
|
|
||||||
account := accounts.Account{
|
|
||||||
Address: address,
|
|
||||||
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
|
||||||
}
|
|
||||||
if !pin {
|
|
||||||
return account, nil
|
|
||||||
}
|
|
||||||
// Pinning needs to modify the state
|
|
||||||
w.stateLock.Lock()
|
|
||||||
defer w.stateLock.Unlock()
|
|
||||||
|
|
||||||
if _, ok := w.paths[address]; !ok {
|
|
||||||
w.accounts = append(w.accounts, account)
|
|
||||||
w.paths[address] = path
|
|
||||||
}
|
|
||||||
return account, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SelfDerive implements accounts.Wallet, trying to discover accounts that the
|
|
||||||
// user used previously (based on the chain state), but ones that he/she did not
|
|
||||||
// explicitly pin to the wallet manually. To avoid chain head monitoring, self
|
|
||||||
// derivation only runs during account listing (and even then throttled).
|
|
||||||
func (w *ledgerWallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
|
||||||
w.stateLock.Lock()
|
|
||||||
defer w.stateLock.Unlock()
|
|
||||||
|
|
||||||
w.deriveNextPath = make(accounts.DerivationPath, len(base))
|
|
||||||
copy(w.deriveNextPath[:], base[:])
|
|
||||||
|
|
||||||
w.deriveNextAddr = common.Address{}
|
|
||||||
w.deriveChain = chain
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignHash implements accounts.Wallet, however signing arbitrary data is not
|
|
||||||
// supported for Ledger wallets, so this method will always return an error.
|
|
||||||
func (w *ledgerWallet) SignHash(acc accounts.Account, hash []byte) ([]byte, error) {
|
|
||||||
return nil, accounts.ErrNotSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
|
||||||
// wallet to request a confirmation from the user. It returns either the signed
|
|
||||||
// transaction or a failure if the user denied the transaction.
|
|
||||||
//
|
|
||||||
// Note, if the version of the Ethereum application running on the Ledger wallet is
|
|
||||||
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
|
|
||||||
// will be returned opposed to silently signing in Homestead mode.
|
|
||||||
func (w *ledgerWallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
|
||||||
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
|
|
||||||
defer w.stateLock.RUnlock()
|
|
||||||
|
|
||||||
// If the wallet is closed, or the Ethereum app doesn't run, abort
|
|
||||||
if w.device == nil || w.offline() {
|
|
||||||
return nil, accounts.ErrWalletClosed
|
|
||||||
}
|
|
||||||
// Make sure the requested account is contained within
|
|
||||||
path, ok := w.paths[account.Address]
|
|
||||||
if !ok {
|
|
||||||
return nil, accounts.ErrUnknownAccount
|
|
||||||
}
|
|
||||||
// Ensure the wallet is capable of signing the given transaction
|
|
||||||
if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 {
|
|
||||||
return nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
|
|
||||||
}
|
|
||||||
// All infos gathered and metadata checks out, request signing
|
|
||||||
<-w.commsLock
|
|
||||||
defer func() { w.commsLock <- struct{}{} }()
|
|
||||||
|
|
||||||
// Ensure the device isn't screwed with while user confirmation is pending
|
|
||||||
// TODO(karalabe): remove if hotplug lands on Windows
|
|
||||||
w.hub.commsLock.Lock()
|
|
||||||
w.hub.commsPend++
|
|
||||||
w.hub.commsLock.Unlock()
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
w.hub.commsLock.Lock()
|
|
||||||
w.hub.commsPend--
|
|
||||||
w.hub.commsLock.Unlock()
|
|
||||||
}()
|
|
||||||
return w.ledgerSign(path, account.Address, tx, chainID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
|
||||||
// data is not supported for Ledger wallets, so this method will always return
|
|
||||||
// an error.
|
|
||||||
func (w *ledgerWallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
|
||||||
return nil, accounts.ErrNotSupported
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
|
||||||
// transaction with the given account using passphrase as extra authentication.
|
|
||||||
// Since the Ledger does not support extra passphrases, it is silently ignored.
|
|
||||||
func (w *ledgerWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
|
||||||
return w.SignTx(account, tx, chainID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ledgerVersion retrieves the current version of the Ethereum wallet app running
|
|
||||||
// on the Ledger wallet.
|
|
||||||
//
|
|
||||||
// The version retrieval protocol is defined as follows:
|
|
||||||
//
|
|
||||||
// CLA | INS | P1 | P2 | Lc | Le
|
|
||||||
// ----+-----+----+----+----+---
|
|
||||||
// E0 | 06 | 00 | 00 | 00 | 04
|
|
||||||
//
|
|
||||||
// With no input data, and the output data being:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// ---------------------------------------------------+--------
|
|
||||||
// Flags 01: arbitrary data signature enabled by user | 1 byte
|
|
||||||
// Application major version | 1 byte
|
|
||||||
// Application minor version | 1 byte
|
|
||||||
// Application patch version | 1 byte
|
|
||||||
func (w *ledgerWallet) ledgerVersion() ([3]byte, error) {
|
|
||||||
// Send the request and wait for the response
|
|
||||||
reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil)
|
|
||||||
if err != nil {
|
|
||||||
return [3]byte{}, err
|
|
||||||
}
|
|
||||||
if len(reply) != 4 {
|
|
||||||
return [3]byte{}, errInvalidVersionReply
|
|
||||||
}
|
|
||||||
// Cache the version for future reference
|
|
||||||
var version [3]byte
|
|
||||||
copy(version[:], reply[1:])
|
|
||||||
return version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ledgerDerive retrieves the currently active Ethereum address from a Ledger
|
|
||||||
// wallet at the specified derivation path.
|
|
||||||
//
|
|
||||||
// The address derivation protocol is defined as follows:
|
|
||||||
//
|
|
||||||
// CLA | INS | P1 | P2 | Lc | Le
|
|
||||||
// ----+-----+----+----+-----+---
|
|
||||||
// E0 | 02 | 00 return address
|
|
||||||
// 01 display address and confirm before returning
|
|
||||||
// | 00: do not return the chain code
|
|
||||||
// | 01: return the chain code
|
|
||||||
// | var | 00
|
|
||||||
//
|
|
||||||
// Where the input data is:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// -------------------------------------------------+--------
|
|
||||||
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
|
||||||
// First derivation index (big endian) | 4 bytes
|
|
||||||
// ... | 4 bytes
|
|
||||||
// Last derivation index (big endian) | 4 bytes
|
|
||||||
//
|
|
||||||
// And the output data is:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// ------------------------+-------------------
|
|
||||||
// Public Key length | 1 byte
|
|
||||||
// Uncompressed Public Key | arbitrary
|
|
||||||
// Ethereum address length | 1 byte
|
|
||||||
// Ethereum address | 40 bytes hex ascii
|
|
||||||
// Chain code if requested | 32 bytes
|
|
||||||
func (w *ledgerWallet) ledgerDerive(derivationPath []uint32) (common.Address, error) {
|
|
||||||
// Flatten the derivation path into the Ledger request
|
|
||||||
path := make([]byte, 1+4*len(derivationPath))
|
|
||||||
path[0] = byte(len(derivationPath))
|
|
||||||
for i, component := range derivationPath {
|
|
||||||
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
|
||||||
}
|
|
||||||
// Send the request and wait for the response
|
|
||||||
reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path)
|
|
||||||
if err != nil {
|
|
||||||
return common.Address{}, err
|
|
||||||
}
|
|
||||||
// Discard the public key, we don't need that for now
|
|
||||||
if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
|
|
||||||
return common.Address{}, errors.New("reply lacks public key entry")
|
|
||||||
}
|
|
||||||
reply = reply[1+int(reply[0]):]
|
|
||||||
|
|
||||||
// Extract the Ethereum hex address string
|
|
||||||
if len(reply) < 1 || len(reply) < 1+int(reply[0]) {
|
|
||||||
return common.Address{}, errors.New("reply lacks address entry")
|
|
||||||
}
|
|
||||||
hexstr := reply[1 : 1+int(reply[0])]
|
|
||||||
|
|
||||||
// Decode the hex sting into an Ethereum address and return
|
|
||||||
var address common.Address
|
|
||||||
hex.Decode(address[:], hexstr)
|
|
||||||
return address, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ledgerSign sends the transaction to the Ledger wallet, and waits for the user
|
|
||||||
// to confirm or deny the transaction.
|
|
||||||
//
|
|
||||||
// The transaction signing protocol is defined as follows:
|
|
||||||
//
|
|
||||||
// CLA | INS | P1 | P2 | Lc | Le
|
|
||||||
// ----+-----+----+----+-----+---
|
|
||||||
// E0 | 04 | 00: first transaction data block
|
|
||||||
// 80: subsequent transaction data block
|
|
||||||
// | 00 | variable | variable
|
|
||||||
//
|
|
||||||
// Where the input for the first transaction block (first 255 bytes) is:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// -------------------------------------------------+----------
|
|
||||||
// Number of BIP 32 derivations to perform (max 10) | 1 byte
|
|
||||||
// First derivation index (big endian) | 4 bytes
|
|
||||||
// ... | 4 bytes
|
|
||||||
// Last derivation index (big endian) | 4 bytes
|
|
||||||
// RLP transaction chunk | arbitrary
|
|
||||||
//
|
|
||||||
// And the input for subsequent transaction blocks (first 255 bytes) are:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// ----------------------+----------
|
|
||||||
// RLP transaction chunk | arbitrary
|
|
||||||
//
|
|
||||||
// And the output data is:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// ------------+---------
|
|
||||||
// signature V | 1 byte
|
|
||||||
// signature R | 32 bytes
|
|
||||||
// signature S | 32 bytes
|
|
||||||
func (w *ledgerWallet) ledgerSign(derivationPath []uint32, address common.Address, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
|
||||||
// Flatten the derivation path into the Ledger request
|
|
||||||
path := make([]byte, 1+4*len(derivationPath))
|
|
||||||
path[0] = byte(len(derivationPath))
|
|
||||||
for i, component := range derivationPath {
|
|
||||||
binary.BigEndian.PutUint32(path[1+4*i:], component)
|
|
||||||
}
|
|
||||||
// Create the transaction RLP based on whether legacy or EIP155 signing was requeste
|
|
||||||
var (
|
|
||||||
txrlp []byte
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
if chainID == nil {
|
|
||||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
payload := append(path, txrlp...)
|
|
||||||
|
|
||||||
// Send the request and wait for the response
|
|
||||||
var (
|
|
||||||
op = ledgerP1InitTransactionData
|
|
||||||
reply []byte
|
|
||||||
)
|
|
||||||
for len(payload) > 0 {
|
|
||||||
// Calculate the size of the next data chunk
|
|
||||||
chunk := 255
|
|
||||||
if chunk > len(payload) {
|
|
||||||
chunk = len(payload)
|
|
||||||
}
|
|
||||||
// Send the chunk over, ensuring it's processed correctly
|
|
||||||
reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Shift the payload and ensure subsequent chunks are marked as such
|
|
||||||
payload = payload[chunk:]
|
|
||||||
op = ledgerP1ContTransactionData
|
|
||||||
}
|
|
||||||
// Extract the Ethereum signature and do a sanity validation
|
|
||||||
if len(reply) != 65 {
|
|
||||||
return nil, errors.New("reply lacks signature")
|
|
||||||
}
|
|
||||||
signature := append(reply[1:], reply[0])
|
|
||||||
|
|
||||||
// Create the correct signer and signature transform based on the chain ID
|
|
||||||
var signer types.Signer
|
|
||||||
if chainID == nil {
|
|
||||||
signer = new(types.HomesteadSigner)
|
|
||||||
} else {
|
|
||||||
signer = types.NewEIP155Signer(chainID)
|
|
||||||
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
|
||||||
}
|
|
||||||
// Inject the final signature into the transaction and sanity check the sender
|
|
||||||
signed, err := tx.WithSignature(signer, signature)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
sender, err := types.Sender(signer, signed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if sender != address {
|
|
||||||
return nil, fmt.Errorf("signer mismatch: expected %s, got %s", address.Hex(), sender.Hex())
|
|
||||||
}
|
|
||||||
return signed, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a
|
|
||||||
// message and retrieving the response.
|
|
||||||
//
|
|
||||||
// The common transport header is defined as follows:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// --------------------------------------+----------
|
|
||||||
// Communication channel ID (big endian) | 2 bytes
|
|
||||||
// Command tag | 1 byte
|
|
||||||
// Packet sequence index (big endian) | 2 bytes
|
|
||||||
// Payload | arbitrary
|
|
||||||
//
|
|
||||||
// The Communication channel ID allows commands multiplexing over the same
|
|
||||||
// physical link. It is not used for the time being, and should be set to 0101
|
|
||||||
// to avoid compatibility issues with implementations ignoring a leading 00 byte.
|
|
||||||
//
|
|
||||||
// The Command tag describes the message content. Use TAG_APDU (0x05) for standard
|
|
||||||
// APDU payloads, or TAG_PING (0x02) for a simple link test.
|
|
||||||
//
|
|
||||||
// The Packet sequence index describes the current sequence for fragmented payloads.
|
|
||||||
// The first fragment index is 0x00.
|
|
||||||
//
|
|
||||||
// APDU Command payloads are encoded as follows:
|
|
||||||
//
|
|
||||||
// Description | Length
|
|
||||||
// -----------------------------------
|
|
||||||
// APDU length (big endian) | 2 bytes
|
|
||||||
// APDU CLA | 1 byte
|
|
||||||
// APDU INS | 1 byte
|
|
||||||
// APDU P1 | 1 byte
|
|
||||||
// APDU P2 | 1 byte
|
|
||||||
// APDU length | 1 byte
|
|
||||||
// Optional APDU data | arbitrary
|
|
||||||
func (w *ledgerWallet) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
|
|
||||||
// Construct the message payload, possibly split into multiple chunks
|
|
||||||
apdu := make([]byte, 2, 7+len(data))
|
|
||||||
|
|
||||||
binary.BigEndian.PutUint16(apdu, uint16(5+len(data)))
|
|
||||||
apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...)
|
|
||||||
apdu = append(apdu, data...)
|
|
||||||
|
|
||||||
// Stream all the chunks to the device
|
|
||||||
header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended
|
|
||||||
chunk := make([]byte, 64)
|
|
||||||
space := len(chunk) - len(header)
|
|
||||||
|
|
||||||
for i := 0; len(apdu) > 0; i++ {
|
|
||||||
// Construct the new message to stream
|
|
||||||
chunk = append(chunk[:0], header...)
|
|
||||||
binary.BigEndian.PutUint16(chunk[3:], uint16(i))
|
|
||||||
|
|
||||||
if len(apdu) > space {
|
|
||||||
chunk = append(chunk, apdu[:space]...)
|
|
||||||
apdu = apdu[space:]
|
|
||||||
} else {
|
|
||||||
chunk = append(chunk, apdu...)
|
|
||||||
apdu = nil
|
|
||||||
}
|
|
||||||
// Send over to the device
|
|
||||||
w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk))
|
|
||||||
if _, err := w.device.Write(chunk); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Stream the reply back from the wallet in 64 byte chunks
|
|
||||||
var reply []byte
|
|
||||||
chunk = chunk[:64] // Yeah, we surely have enough space
|
|
||||||
for {
|
|
||||||
// Read the next chunk from the Ledger wallet
|
|
||||||
if _, err := io.ReadFull(w.device, chunk); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk))
|
|
||||||
|
|
||||||
// Make sure the transport header matches
|
|
||||||
if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 {
|
|
||||||
return nil, errReplyInvalidHeader
|
|
||||||
}
|
|
||||||
// If it's the first chunk, retrieve the total message length
|
|
||||||
var payload []byte
|
|
||||||
|
|
||||||
if chunk[3] == 0x00 && chunk[4] == 0x00 {
|
|
||||||
reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7])))
|
|
||||||
payload = chunk[7:]
|
|
||||||
} else {
|
|
||||||
payload = chunk[5:]
|
|
||||||
}
|
|
||||||
// Append to the reply and stop when filled up
|
|
||||||
if left := cap(reply) - len(reply); left > len(payload) {
|
|
||||||
reply = append(reply, payload...)
|
|
||||||
} else {
|
|
||||||
reply = append(reply, payload[:left]...)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reply[:len(reply)-2], nil
|
|
||||||
}
|
|
330
accounts/usbwallet/trezor.go
Normal file
330
accounts/usbwallet/trezor.go
Normal file
@ -0,0 +1,330 @@
|
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// This file contains the implementation for interacting with the Trezor hardware
|
||||||
|
// wallets. The wire protocol spec can be found on the SatoshiLabs website:
|
||||||
|
// https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
|
||||||
|
|
||||||
|
package usbwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/usbwallet/internal/trezor"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
|
||||||
|
// this case, the calling application should display a pinpad and send back the
|
||||||
|
// encoded passphrase.
|
||||||
|
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
|
||||||
|
|
||||||
|
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
|
||||||
|
// if the device replies with a mismatching header. This usually means the device
|
||||||
|
// is in browser mode.
|
||||||
|
var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
|
||||||
|
|
||||||
|
// trezorDriver implements the communication with a Trezor hardware wallet.
|
||||||
|
type trezorDriver struct {
|
||||||
|
device io.ReadWriter // USB device connection to communicate through
|
||||||
|
version [3]uint32 // Current version of the Trezor firmware
|
||||||
|
label string // Current textual label of the Trezor device
|
||||||
|
pinwait bool // Flags whether the device is waiting for PIN entry
|
||||||
|
failure error // Any failure that would make the device unusable
|
||||||
|
log log.Logger // Contextual logger to tag the trezor with its id
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTrezorDriver creates a new instance of a Trezor USB protocol driver.
|
||||||
|
func newTrezorDriver(logger log.Logger) driver {
|
||||||
|
return &trezorDriver{
|
||||||
|
log: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status implements accounts.Wallet, always whether the Trezor is opened, closed
|
||||||
|
// or whether the Ethereum app was not started on it.
|
||||||
|
func (w *trezorDriver) Status() (string, error) {
|
||||||
|
if w.failure != nil {
|
||||||
|
return fmt.Sprintf("Failed: %v", w.failure), w.failure
|
||||||
|
}
|
||||||
|
if w.device == nil {
|
||||||
|
return "Closed", w.failure
|
||||||
|
}
|
||||||
|
if w.pinwait {
|
||||||
|
return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements usbwallet.driver, attempting to initialize the connection to
|
||||||
|
// the Trezor hardware wallet. Initializing the Trezor is a two phase operation:
|
||||||
|
// * The first phase is to initialize the connection and read the wallet's
|
||||||
|
// features. This phase is invoked is the provided passphrase is empty. The
|
||||||
|
// device will display the pinpad as a result and will return an appropriate
|
||||||
|
// error to notify the user that a second open phase is needed.
|
||||||
|
// * The second phase is to unlock access to the Trezor, which is done by the
|
||||||
|
// user actually providing a passphrase mapping a keyboard keypad to the pin
|
||||||
|
// number of the user (shuffled according to the pinpad displayed).
|
||||||
|
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
|
||||||
|
w.device, w.failure = device, nil
|
||||||
|
|
||||||
|
// If phase 1 is requested, init the connection and wait for user callback
|
||||||
|
if passphrase == "" {
|
||||||
|
// If we're already waiting for a PIN entry, insta-return
|
||||||
|
if w.pinwait {
|
||||||
|
return ErrTrezorPINNeeded
|
||||||
|
}
|
||||||
|
// Initialize a connection to the device
|
||||||
|
features := new(trezor.Features)
|
||||||
|
if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
|
||||||
|
w.label = features.GetLabel()
|
||||||
|
|
||||||
|
// Do a manual ping, forcing the device to ask for its PIN
|
||||||
|
askPin := true
|
||||||
|
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin}, new(trezor.PinMatrixRequest), new(trezor.Success))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Only return the PIN request if the device wasn't unlocked until now
|
||||||
|
if res == 1 {
|
||||||
|
return nil // Device responded with trezor.Success
|
||||||
|
}
|
||||||
|
w.pinwait = true
|
||||||
|
return ErrTrezorPINNeeded
|
||||||
|
}
|
||||||
|
// Phase 2 requested with actual PIN entry
|
||||||
|
w.pinwait = false
|
||||||
|
|
||||||
|
if _, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success)); err != nil {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements usbwallet.driver, cleaning up and metadata maintained within
|
||||||
|
// the Trezor driver.
|
||||||
|
func (w *trezorDriver) Close() error {
|
||||||
|
w.version, w.label, w.pinwait = [3]uint32{}, "", false
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Heartbeat implements usbwallet.driver, performing a sanity check against the
|
||||||
|
// Trezor to see if it's still online.
|
||||||
|
func (w *trezorDriver) Heartbeat() error {
|
||||||
|
if _, err := w.trezorExchange(&trezor.Ping{}, new(trezor.Success)); err != nil {
|
||||||
|
w.failure = err
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive implements usbwallet.driver, sending a derivation request to the Trezor
|
||||||
|
// and returning the Ethereum address located on that derivation path.
|
||||||
|
func (w *trezorDriver) Derive(path accounts.DerivationPath) (common.Address, error) {
|
||||||
|
return w.trezorDerive(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx implements usbwallet.driver, sending the transaction to the Trezor and
|
||||||
|
// waiting for the user to confirm or deny the transaction.
|
||||||
|
func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||||
|
if w.device == nil {
|
||||||
|
return common.Address{}, nil, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
return w.trezorSign(path, tx, chainID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// trezorDerive sends a derivation request to the Trezor device and returns the
|
||||||
|
// Ethereum address located on that path.
|
||||||
|
func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) {
|
||||||
|
address := new(trezor.EthereumAddress)
|
||||||
|
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
|
||||||
|
return common.Address{}, err
|
||||||
|
}
|
||||||
|
return common.BytesToAddress(address.GetAddress()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// trezorSign sends the transaction to the Trezor wallet, and waits for the user
|
||||||
|
// to confirm or deny the transaction.
|
||||||
|
func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||||
|
// Create the transaction initiation message
|
||||||
|
data := tx.Data()
|
||||||
|
length := uint32(len(data))
|
||||||
|
|
||||||
|
request := &trezor.EthereumSignTx{
|
||||||
|
AddressN: derivationPath,
|
||||||
|
Nonce: new(big.Int).SetUint64(tx.Nonce()).Bytes(),
|
||||||
|
GasPrice: tx.GasPrice().Bytes(),
|
||||||
|
GasLimit: tx.Gas().Bytes(),
|
||||||
|
Value: tx.Value().Bytes(),
|
||||||
|
DataLength: &length,
|
||||||
|
}
|
||||||
|
if to := tx.To(); to != nil {
|
||||||
|
request.To = (*to)[:] // Non contract deploy, set recipient explicitly
|
||||||
|
}
|
||||||
|
if length > 1024 { // Send the data chunked if that was requested
|
||||||
|
request.DataInitialChunk, data = data[:1024], data[1024:]
|
||||||
|
} else {
|
||||||
|
request.DataInitialChunk, data = data, nil
|
||||||
|
}
|
||||||
|
if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?)
|
||||||
|
id := uint32(chainID.Int64())
|
||||||
|
request.ChainId = &id
|
||||||
|
}
|
||||||
|
// Send the initiation message and stream content until a signature is returned
|
||||||
|
response := new(trezor.EthereumTxRequest)
|
||||||
|
if _, err := w.trezorExchange(request, response); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
for response.DataLength != nil && int(*response.DataLength) <= len(data) {
|
||||||
|
chunk := data[:*response.DataLength]
|
||||||
|
data = data[*response.DataLength:]
|
||||||
|
|
||||||
|
if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Extract the Ethereum signature and do a sanity validation
|
||||||
|
if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 {
|
||||||
|
return common.Address{}, nil, errors.New("reply lacks signature")
|
||||||
|
}
|
||||||
|
signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV()))
|
||||||
|
|
||||||
|
// Create the correct signer and signature transform based on the chain ID
|
||||||
|
var signer types.Signer
|
||||||
|
if chainID == nil {
|
||||||
|
signer = new(types.HomesteadSigner)
|
||||||
|
} else {
|
||||||
|
signer = types.NewEIP155Signer(chainID)
|
||||||
|
signature[64] = signature[64] - byte(chainID.Uint64()*2+35)
|
||||||
|
}
|
||||||
|
// Inject the final signature into the transaction and sanity check the sender
|
||||||
|
signed, err := tx.WithSignature(signer, signature)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
sender, err := types.Sender(signer, signed)
|
||||||
|
if err != nil {
|
||||||
|
return common.Address{}, nil, err
|
||||||
|
}
|
||||||
|
return sender, signed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// trezorExchange performs a data exchange with the Trezor wallet, sending it a
|
||||||
|
// message and retrieving the response. If multiple responses are possible, the
|
||||||
|
// method will also return the index of the destination object used.
|
||||||
|
func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Message) (int, error) {
|
||||||
|
// Construct the original message payload to chunk up
|
||||||
|
data, err := proto.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
payload := make([]byte, 8+len(data))
|
||||||
|
copy(payload, []byte{0x23, 0x23})
|
||||||
|
binary.BigEndian.PutUint16(payload[2:], trezor.Type(req))
|
||||||
|
binary.BigEndian.PutUint32(payload[4:], uint32(len(data)))
|
||||||
|
copy(payload[8:], data)
|
||||||
|
|
||||||
|
// Stream all the chunks to the device
|
||||||
|
chunk := make([]byte, 64)
|
||||||
|
chunk[0] = 0x3f // Report ID magic number
|
||||||
|
|
||||||
|
for len(payload) > 0 {
|
||||||
|
// Construct the new message to stream, padding with zeroes if needed
|
||||||
|
if len(payload) > 63 {
|
||||||
|
copy(chunk[1:], payload[:63])
|
||||||
|
payload = payload[63:]
|
||||||
|
} else {
|
||||||
|
copy(chunk[1:], payload)
|
||||||
|
copy(chunk[1+len(payload):], make([]byte, 63-len(payload)))
|
||||||
|
payload = nil
|
||||||
|
}
|
||||||
|
// Send over to the device
|
||||||
|
w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk))
|
||||||
|
if _, err := w.device.Write(chunk); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stream the reply back from the wallet in 64 byte chunks
|
||||||
|
var (
|
||||||
|
kind uint16
|
||||||
|
reply []byte
|
||||||
|
)
|
||||||
|
for {
|
||||||
|
// Read the next chunk from the Trezor wallet
|
||||||
|
if _, err := io.ReadFull(w.device, chunk); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk))
|
||||||
|
|
||||||
|
// Make sure the transport header matches
|
||||||
|
if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) {
|
||||||
|
return 0, errTrezorReplyInvalidHeader
|
||||||
|
}
|
||||||
|
// If it's the first chunk, retrieve the reply message type and total message length
|
||||||
|
var payload []byte
|
||||||
|
|
||||||
|
if len(reply) == 0 {
|
||||||
|
kind = binary.BigEndian.Uint16(chunk[3:5])
|
||||||
|
reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9])))
|
||||||
|
payload = chunk[9:]
|
||||||
|
} else {
|
||||||
|
payload = chunk[1:]
|
||||||
|
}
|
||||||
|
// Append to the reply and stop when filled up
|
||||||
|
if left := cap(reply) - len(reply); left > len(payload) {
|
||||||
|
reply = append(reply, payload...)
|
||||||
|
} else {
|
||||||
|
reply = append(reply, payload[:left]...)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Try to parse the reply into the requested reply message
|
||||||
|
if kind == uint16(trezor.MessageType_MessageType_Failure) {
|
||||||
|
// Trezor returned a failure, extract and return the message
|
||||||
|
failure := new(trezor.Failure)
|
||||||
|
if err := proto.Unmarshal(reply, failure); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return 0, errors.New("trezor: " + failure.GetMessage())
|
||||||
|
}
|
||||||
|
if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) {
|
||||||
|
// Trezor is waiting for user confirmation, ack and wait for the next message
|
||||||
|
return w.trezorExchange(&trezor.ButtonAck{}, results...)
|
||||||
|
}
|
||||||
|
for i, res := range results {
|
||||||
|
if trezor.Type(res) == kind {
|
||||||
|
return i, proto.Unmarshal(reply, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expected := make([]string, len(results))
|
||||||
|
for i, res := range results {
|
||||||
|
expected[i] = trezor.Name(trezor.Type(res))
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind))
|
||||||
|
}
|
@ -1,25 +0,0 @@
|
|||||||
// Copyright 2017 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum 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 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
// Package usbwallet implements support for USB hardware wallets.
|
|
||||||
package usbwallet
|
|
||||||
|
|
||||||
// deviceID is a combined vendor/product identifier to uniquely identify a USB
|
|
||||||
// hardware device.
|
|
||||||
type deviceID struct {
|
|
||||||
Vendor uint16 // The Vendor identifer
|
|
||||||
Product uint16 // The Product identifier
|
|
||||||
}
|
|
562
accounts/usbwallet/wallet.go
Normal file
562
accounts/usbwallet/wallet.go
Normal file
@ -0,0 +1,562 @@
|
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum 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 the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package usbwallet implements support for USB hardware wallets.
|
||||||
|
package usbwallet
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
ethereum "github.com/ethereum/go-ethereum"
|
||||||
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/karalabe/hid"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Maximum time between wallet health checks to detect USB unplugs.
|
||||||
|
const heartbeatCycle = time.Second
|
||||||
|
|
||||||
|
// Minimum time to wait between self derivation attempts, even it the user is
|
||||||
|
// requesting accounts like crazy.
|
||||||
|
const selfDeriveThrottling = time.Second
|
||||||
|
|
||||||
|
// driver defines the vendor specific functionality hardware wallets instances
|
||||||
|
// must implement to allow using them with the wallet lifecycle management.
|
||||||
|
type driver interface {
|
||||||
|
// Status returns a textual status to aid the user in the current state of the
|
||||||
|
// wallet. It also returns an error indicating any failure the wallet might have
|
||||||
|
// encountered.
|
||||||
|
Status() (string, error)
|
||||||
|
|
||||||
|
// Open initializes access to a wallet instance. The passphrase parameter may
|
||||||
|
// or may not be used by the implementation of a particular wallet instance.
|
||||||
|
Open(device io.ReadWriter, passphrase string) error
|
||||||
|
|
||||||
|
// Close releases any resources held by an open wallet instance.
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// Heartbeat performs a sanity check against the hardware wallet to see if it
|
||||||
|
// is still online and healthy.
|
||||||
|
Heartbeat() error
|
||||||
|
|
||||||
|
// Derive sends a derivation request to the USB device and returns the Ethereum
|
||||||
|
// address located on that path.
|
||||||
|
Derive(path accounts.DerivationPath) (common.Address, error)
|
||||||
|
|
||||||
|
// SignTx sends the transaction to the USB device and waits for the user to confirm
|
||||||
|
// or deny the transaction.
|
||||||
|
SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// wallet represents the common functionality shared by all USB hardware
|
||||||
|
// wallets to prevent reimplementing the same complex maintenance mechanisms
|
||||||
|
// for different vendors.
|
||||||
|
type wallet struct {
|
||||||
|
hub *Hub // USB hub scanning
|
||||||
|
driver driver // Hardware implementation of the low level device operations
|
||||||
|
url *accounts.URL // Textual URL uniquely identifying this wallet
|
||||||
|
|
||||||
|
info hid.DeviceInfo // Known USB device infos about the wallet
|
||||||
|
device *hid.Device // USB device advertising itself as a hardware wallet
|
||||||
|
|
||||||
|
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
|
||||||
|
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
|
||||||
|
|
||||||
|
deriveNextPath accounts.DerivationPath // Next derivation path for account auto-discovery
|
||||||
|
deriveNextAddr common.Address // Next derived account address for auto-discovery
|
||||||
|
deriveChain ethereum.ChainStateReader // Blockchain state reader to discover used account with
|
||||||
|
deriveReq chan chan struct{} // Channel to request a self-derivation on
|
||||||
|
deriveQuit chan chan error // Channel to terminate the self-deriver with
|
||||||
|
|
||||||
|
healthQuit chan chan error
|
||||||
|
|
||||||
|
// Locking a hardware wallet is a bit special. Since hardware devices are lower
|
||||||
|
// performing, any communication with them might take a non negligible amount of
|
||||||
|
// time. Worse still, waiting for user confirmation can take arbitrarily long,
|
||||||
|
// but exclusive communication must be upheld during. Locking the entire wallet
|
||||||
|
// in the mean time however would stall any parts of the system that don't want
|
||||||
|
// to communicate, just read some state (e.g. list the accounts).
|
||||||
|
//
|
||||||
|
// As such, a hardware wallet needs two locks to function correctly. A state
|
||||||
|
// lock can be used to protect the wallet's software-side internal state, which
|
||||||
|
// must not be held exlusively during hardware communication. A communication
|
||||||
|
// lock can be used to achieve exclusive access to the device itself, this one
|
||||||
|
// however should allow "skipping" waiting for operations that might want to
|
||||||
|
// use the device, but can live without too (e.g. account self-derivation).
|
||||||
|
//
|
||||||
|
// Since we have two locks, it's important to know how to properly use them:
|
||||||
|
// - Communication requires the `device` to not change, so obtaining the
|
||||||
|
// commsLock should be done after having a stateLock.
|
||||||
|
// - Communication must not disable read access to the wallet state, so it
|
||||||
|
// must only ever hold a *read* lock to stateLock.
|
||||||
|
commsLock chan struct{} // Mutex (buf=1) for the USB comms without keeping the state locked
|
||||||
|
stateLock sync.RWMutex // Protects read and write access to the wallet struct fields
|
||||||
|
|
||||||
|
log log.Logger // Contextual logger to tag the base with its id
|
||||||
|
}
|
||||||
|
|
||||||
|
// URL implements accounts.Wallet, returning the URL of the USB hardware device.
|
||||||
|
func (w *wallet) URL() accounts.URL {
|
||||||
|
return *w.url // Immutable, no need for a lock
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status implements accounts.Wallet, returning a custom status message from the
|
||||||
|
// underlying vendor-specific hardware wallet implementation.
|
||||||
|
func (w *wallet) Status() (string, error) {
|
||||||
|
w.stateLock.RLock() // No device communication, state lock is enough
|
||||||
|
defer w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
status, failure := w.driver.Status()
|
||||||
|
if w.device == nil {
|
||||||
|
return "Closed", failure
|
||||||
|
}
|
||||||
|
return status, failure
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open implements accounts.Wallet, attempting to open a USB connection to the
|
||||||
|
// hardware wallet.
|
||||||
|
func (w *wallet) Open(passphrase string) error {
|
||||||
|
w.stateLock.Lock() // State lock is enough since there's no connection yet at this point
|
||||||
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
// If the device was already opened once, refuse to try again
|
||||||
|
if w.paths != nil {
|
||||||
|
return accounts.ErrWalletAlreadyOpen
|
||||||
|
}
|
||||||
|
// Make sure the actual device connection is done only once
|
||||||
|
if w.device == nil {
|
||||||
|
device, err := w.info.Open()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.device = device
|
||||||
|
w.commsLock = make(chan struct{}, 1)
|
||||||
|
w.commsLock <- struct{}{} // Enable lock
|
||||||
|
}
|
||||||
|
// Delegate device initialization to the underlying driver
|
||||||
|
if err := w.driver.Open(w.device, passphrase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Connection successful, start life-cycle management
|
||||||
|
w.paths = make(map[common.Address]accounts.DerivationPath)
|
||||||
|
|
||||||
|
w.deriveReq = make(chan chan struct{})
|
||||||
|
w.deriveQuit = make(chan chan error)
|
||||||
|
w.healthQuit = make(chan chan error)
|
||||||
|
|
||||||
|
go w.heartbeat()
|
||||||
|
go w.selfDerive()
|
||||||
|
|
||||||
|
// Notify anyone listening for wallet events that a new device is accessible
|
||||||
|
go w.hub.updateFeed.Send(accounts.WalletEvent{Wallet: w, Kind: accounts.WalletOpened})
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// heartbeat is a health check loop for the USB wallets to periodically verify
|
||||||
|
// whether they are still present or if they malfunctioned.
|
||||||
|
func (w *wallet) heartbeat() {
|
||||||
|
w.log.Debug("USB wallet health-check started")
|
||||||
|
defer w.log.Debug("USB wallet health-check stopped")
|
||||||
|
|
||||||
|
// Execute heartbeat checks until termination or error
|
||||||
|
var (
|
||||||
|
errc chan error
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for errc == nil && err == nil {
|
||||||
|
// Wait until termination is requested or the heartbeat cycle arrives
|
||||||
|
select {
|
||||||
|
case errc = <-w.healthQuit:
|
||||||
|
// Termination requested
|
||||||
|
continue
|
||||||
|
case <-time.After(heartbeatCycle):
|
||||||
|
// Heartbeat time
|
||||||
|
}
|
||||||
|
// Execute a tiny data exchange to see responsiveness
|
||||||
|
w.stateLock.RLock()
|
||||||
|
if w.device == nil {
|
||||||
|
// Terminated while waiting for the lock
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
<-w.commsLock // Don't lock state while resolving version
|
||||||
|
err = w.driver.Heartbeat()
|
||||||
|
w.commsLock <- struct{}{}
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
w.stateLock.Lock() // Lock state to tear the wallet down
|
||||||
|
w.close()
|
||||||
|
w.stateLock.Unlock()
|
||||||
|
}
|
||||||
|
// Ignore non hardware related errors
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
// In case of error, wait for termination
|
||||||
|
if err != nil {
|
||||||
|
w.log.Debug("USB wallet health-check failed", "err", err)
|
||||||
|
errc = <-w.healthQuit
|
||||||
|
}
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close implements accounts.Wallet, closing the USB connection to the device.
|
||||||
|
func (w *wallet) Close() error {
|
||||||
|
// Ensure the wallet was opened
|
||||||
|
w.stateLock.RLock()
|
||||||
|
hQuit, dQuit := w.healthQuit, w.deriveQuit
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
// Terminate the health checks
|
||||||
|
var herr error
|
||||||
|
if hQuit != nil {
|
||||||
|
errc := make(chan error)
|
||||||
|
hQuit <- errc
|
||||||
|
herr = <-errc // Save for later, we *must* close the USB
|
||||||
|
}
|
||||||
|
// Terminate the self-derivations
|
||||||
|
var derr error
|
||||||
|
if dQuit != nil {
|
||||||
|
errc := make(chan error)
|
||||||
|
dQuit <- errc
|
||||||
|
derr = <-errc // Save for later, we *must* close the USB
|
||||||
|
}
|
||||||
|
// Terminate the device connection
|
||||||
|
w.stateLock.Lock()
|
||||||
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
w.healthQuit = nil
|
||||||
|
w.deriveQuit = nil
|
||||||
|
w.deriveReq = nil
|
||||||
|
|
||||||
|
if err := w.close(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if herr != nil {
|
||||||
|
return herr
|
||||||
|
}
|
||||||
|
return derr
|
||||||
|
}
|
||||||
|
|
||||||
|
// close is the internal wallet closer that terminates the USB connection and
|
||||||
|
// resets all the fields to their defaults.
|
||||||
|
//
|
||||||
|
// Note, close assumes the state lock is held!
|
||||||
|
func (w *wallet) close() error {
|
||||||
|
// Allow duplicate closes, especially for health-check failures
|
||||||
|
if w.device == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Close the device, clear everything, then return
|
||||||
|
w.device.Close()
|
||||||
|
w.device = nil
|
||||||
|
|
||||||
|
w.accounts, w.paths = nil, nil
|
||||||
|
w.driver.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accounts implements accounts.Wallet, returning the list of accounts pinned to
|
||||||
|
// the USB hardware wallet. If self-derivation was enabled, the account list is
|
||||||
|
// periodically expanded based on current chain state.
|
||||||
|
func (w *wallet) Accounts() []accounts.Account {
|
||||||
|
// Attempt self-derivation if it's running
|
||||||
|
reqc := make(chan struct{}, 1)
|
||||||
|
select {
|
||||||
|
case w.deriveReq <- reqc:
|
||||||
|
// Self-derivation request accepted, wait for it
|
||||||
|
<-reqc
|
||||||
|
default:
|
||||||
|
// Self-derivation offline, throttled or busy, skip
|
||||||
|
}
|
||||||
|
// Return whatever account list we ended up with
|
||||||
|
w.stateLock.RLock()
|
||||||
|
defer w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
cpy := make([]accounts.Account, len(w.accounts))
|
||||||
|
copy(cpy, w.accounts)
|
||||||
|
return cpy
|
||||||
|
}
|
||||||
|
|
||||||
|
// selfDerive is an account derivation loop that upon request attempts to find
|
||||||
|
// new non-zero accounts.
|
||||||
|
func (w *wallet) selfDerive() {
|
||||||
|
w.log.Debug("USB wallet self-derivation started")
|
||||||
|
defer w.log.Debug("USB wallet self-derivation stopped")
|
||||||
|
|
||||||
|
// Execute self-derivations until termination or error
|
||||||
|
var (
|
||||||
|
reqc chan struct{}
|
||||||
|
errc chan error
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
for errc == nil && err == nil {
|
||||||
|
// Wait until either derivation or termination is requested
|
||||||
|
select {
|
||||||
|
case errc = <-w.deriveQuit:
|
||||||
|
// Termination requested
|
||||||
|
continue
|
||||||
|
case reqc = <-w.deriveReq:
|
||||||
|
// Account discovery requested
|
||||||
|
}
|
||||||
|
// Derivation needs a chain and device access, skip if either unavailable
|
||||||
|
w.stateLock.RLock()
|
||||||
|
if w.device == nil || w.deriveChain == nil {
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
reqc <- struct{}{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-w.commsLock:
|
||||||
|
default:
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
reqc <- struct{}{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Device lock obtained, derive the next batch of accounts
|
||||||
|
var (
|
||||||
|
accs []accounts.Account
|
||||||
|
paths []accounts.DerivationPath
|
||||||
|
|
||||||
|
nextAddr = w.deriveNextAddr
|
||||||
|
nextPath = w.deriveNextPath
|
||||||
|
|
||||||
|
context = context.Background()
|
||||||
|
)
|
||||||
|
for empty := false; !empty; {
|
||||||
|
// Retrieve the next derived Ethereum account
|
||||||
|
if nextAddr == (common.Address{}) {
|
||||||
|
if nextAddr, err = w.driver.Derive(nextPath); err != nil {
|
||||||
|
w.log.Warn("USB wallet account derivation failed", "err", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check the account's status against the current chain state
|
||||||
|
var (
|
||||||
|
balance *big.Int
|
||||||
|
nonce uint64
|
||||||
|
)
|
||||||
|
balance, err = w.deriveChain.BalanceAt(context, nextAddr, nil)
|
||||||
|
if err != nil {
|
||||||
|
w.log.Warn("USB wallet balance retrieval failed", "err", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
nonce, err = w.deriveChain.NonceAt(context, nextAddr, nil)
|
||||||
|
if err != nil {
|
||||||
|
w.log.Warn("USB wallet nonce retrieval failed", "err", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// If the next account is empty, stop self-derivation, but add it nonetheless
|
||||||
|
if balance.Sign() == 0 && nonce == 0 {
|
||||||
|
empty = true
|
||||||
|
}
|
||||||
|
// We've just self-derived a new account, start tracking it locally
|
||||||
|
path := make(accounts.DerivationPath, len(nextPath))
|
||||||
|
copy(path[:], nextPath[:])
|
||||||
|
paths = append(paths, path)
|
||||||
|
|
||||||
|
account := accounts.Account{
|
||||||
|
Address: nextAddr,
|
||||||
|
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
||||||
|
}
|
||||||
|
accs = append(accs, account)
|
||||||
|
|
||||||
|
// Display a log message to the user for new (or previously empty accounts)
|
||||||
|
if _, known := w.paths[nextAddr]; !known || (!empty && nextAddr == w.deriveNextAddr) {
|
||||||
|
w.log.Info("USB wallet discovered new account", "address", nextAddr, "path", path, "balance", balance, "nonce", nonce)
|
||||||
|
}
|
||||||
|
// Fetch the next potential account
|
||||||
|
if !empty {
|
||||||
|
nextAddr = common.Address{}
|
||||||
|
nextPath[len(nextPath)-1]++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Self derivation complete, release device lock
|
||||||
|
w.commsLock <- struct{}{}
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
// Insert any accounts successfully derived
|
||||||
|
w.stateLock.Lock()
|
||||||
|
for i := 0; i < len(accs); i++ {
|
||||||
|
if _, ok := w.paths[accs[i].Address]; !ok {
|
||||||
|
w.accounts = append(w.accounts, accs[i])
|
||||||
|
w.paths[accs[i].Address] = paths[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Shift the self-derivation forward
|
||||||
|
// TODO(karalabe): don't overwrite changes from wallet.SelfDerive
|
||||||
|
w.deriveNextAddr = nextAddr
|
||||||
|
w.deriveNextPath = nextPath
|
||||||
|
w.stateLock.Unlock()
|
||||||
|
|
||||||
|
// Notify the user of termination and loop after a bit of time (to avoid trashing)
|
||||||
|
reqc <- struct{}{}
|
||||||
|
if err == nil {
|
||||||
|
select {
|
||||||
|
case errc = <-w.deriveQuit:
|
||||||
|
// Termination requested, abort
|
||||||
|
case <-time.After(selfDeriveThrottling):
|
||||||
|
// Waited enough, willing to self-derive again
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// In case of error, wait for termination
|
||||||
|
if err != nil {
|
||||||
|
w.log.Debug("USB wallet self-derivation failed", "err", err)
|
||||||
|
errc = <-w.deriveQuit
|
||||||
|
}
|
||||||
|
errc <- err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains implements accounts.Wallet, returning whether a particular account is
|
||||||
|
// or is not pinned into this wallet instance. Although we could attempt to resolve
|
||||||
|
// unpinned accounts, that would be an non-negligible hardware operation.
|
||||||
|
func (w *wallet) Contains(account accounts.Account) bool {
|
||||||
|
w.stateLock.RLock()
|
||||||
|
defer w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
_, exists := w.paths[account.Address]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive implements accounts.Wallet, deriving a new account at the specific
|
||||||
|
// derivation path. If pin is set to true, the account will be added to the list
|
||||||
|
// of tracked accounts.
|
||||||
|
func (w *wallet) Derive(path accounts.DerivationPath, pin bool) (accounts.Account, error) {
|
||||||
|
// Try to derive the actual account and update its URL if successful
|
||||||
|
w.stateLock.RLock() // Avoid device disappearing during derivation
|
||||||
|
|
||||||
|
if w.device == nil {
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
return accounts.Account{}, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
<-w.commsLock // Avoid concurrent hardware access
|
||||||
|
address, err := w.driver.Derive(path)
|
||||||
|
w.commsLock <- struct{}{}
|
||||||
|
|
||||||
|
w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
// If an error occurred or no pinning was requested, return
|
||||||
|
if err != nil {
|
||||||
|
return accounts.Account{}, err
|
||||||
|
}
|
||||||
|
account := accounts.Account{
|
||||||
|
Address: address,
|
||||||
|
URL: accounts.URL{Scheme: w.url.Scheme, Path: fmt.Sprintf("%s/%s", w.url.Path, path)},
|
||||||
|
}
|
||||||
|
if !pin {
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
// Pinning needs to modify the state
|
||||||
|
w.stateLock.Lock()
|
||||||
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
if _, ok := w.paths[address]; !ok {
|
||||||
|
w.accounts = append(w.accounts, account)
|
||||||
|
w.paths[address] = path
|
||||||
|
}
|
||||||
|
return account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelfDerive implements accounts.Wallet, trying to discover accounts that the
|
||||||
|
// user used previously (based on the chain state), but ones that he/she did not
|
||||||
|
// explicitly pin to the wallet manually. To avoid chain head monitoring, self
|
||||||
|
// derivation only runs during account listing (and even then throttled).
|
||||||
|
func (w *wallet) SelfDerive(base accounts.DerivationPath, chain ethereum.ChainStateReader) {
|
||||||
|
w.stateLock.Lock()
|
||||||
|
defer w.stateLock.Unlock()
|
||||||
|
|
||||||
|
w.deriveNextPath = make(accounts.DerivationPath, len(base))
|
||||||
|
copy(w.deriveNextPath[:], base[:])
|
||||||
|
|
||||||
|
w.deriveNextAddr = common.Address{}
|
||||||
|
w.deriveChain = chain
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHash implements accounts.Wallet, however signing arbitrary data is not
|
||||||
|
// supported for hardware wallets, so this method will always return an error.
|
||||||
|
func (w *wallet) SignHash(account accounts.Account, hash []byte) ([]byte, error) {
|
||||||
|
return nil, accounts.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
|
||||||
|
// wallet to request a confirmation from the user. It returns either the signed
|
||||||
|
// transaction or a failure if the user denied the transaction.
|
||||||
|
//
|
||||||
|
// Note, if the version of the Ethereum application running on the Ledger wallet is
|
||||||
|
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
|
||||||
|
// will be returned opposed to silently signing in Homestead mode.
|
||||||
|
func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
|
||||||
|
defer w.stateLock.RUnlock()
|
||||||
|
|
||||||
|
// If the wallet is closed, abort
|
||||||
|
if w.device == nil {
|
||||||
|
return nil, accounts.ErrWalletClosed
|
||||||
|
}
|
||||||
|
// Make sure the requested account is contained within
|
||||||
|
path, ok := w.paths[account.Address]
|
||||||
|
if !ok {
|
||||||
|
return nil, accounts.ErrUnknownAccount
|
||||||
|
}
|
||||||
|
// All infos gathered and metadata checks out, request signing
|
||||||
|
<-w.commsLock
|
||||||
|
defer func() { w.commsLock <- struct{}{} }()
|
||||||
|
|
||||||
|
// Ensure the device isn't screwed with while user confirmation is pending
|
||||||
|
// TODO(karalabe): remove if hotplug lands on Windows
|
||||||
|
w.hub.commsLock.Lock()
|
||||||
|
w.hub.commsPend++
|
||||||
|
w.hub.commsLock.Unlock()
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
w.hub.commsLock.Lock()
|
||||||
|
w.hub.commsPend--
|
||||||
|
w.hub.commsLock.Unlock()
|
||||||
|
}()
|
||||||
|
// Sign the transaction and verify the sender to avoid hardware fault surprises
|
||||||
|
sender, signed, err := w.driver.SignTx(path, tx, chainID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if sender != account.Address {
|
||||||
|
return nil, fmt.Errorf("signer mismatch: expected %s, got %s", account.Address.Hex(), sender.Hex())
|
||||||
|
}
|
||||||
|
return signed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignHashWithPassphrase implements accounts.Wallet, however signing arbitrary
|
||||||
|
// data is not supported for Ledger wallets, so this method will always return
|
||||||
|
// an error.
|
||||||
|
func (w *wallet) SignHashWithPassphrase(account accounts.Account, passphrase string, hash []byte) ([]byte, error) {
|
||||||
|
return w.SignHash(account, hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
|
||||||
|
// transaction with the given account using passphrase as extra authentication.
|
||||||
|
// Since USB wallets don't rely on passphrases, these are silently ignored.
|
||||||
|
func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
|
||||||
|
return w.SignTx(account, tx, chainID)
|
||||||
|
}
|
@ -239,24 +239,30 @@ func startNode(ctx *cli.Context, stack *node.Node) {
|
|||||||
}
|
}
|
||||||
stateReader := ethclient.NewClient(rpcClient)
|
stateReader := ethclient.NewClient(rpcClient)
|
||||||
|
|
||||||
// Open and self derive any wallets already attached
|
// Open any wallets already attached
|
||||||
for _, wallet := range stack.AccountManager().Wallets() {
|
for _, wallet := range stack.AccountManager().Wallets() {
|
||||||
if err := wallet.Open(""); err != nil {
|
if err := wallet.Open(""); err != nil {
|
||||||
log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
|
log.Warn("Failed to open wallet", "url", wallet.URL(), "err", err)
|
||||||
} else {
|
|
||||||
wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Listen for wallet event till termination
|
// Listen for wallet event till termination
|
||||||
for event := range events {
|
for event := range events {
|
||||||
if event.Arrive {
|
switch event.Kind {
|
||||||
|
case accounts.WalletArrived:
|
||||||
if err := event.Wallet.Open(""); err != nil {
|
if err := event.Wallet.Open(""); err != nil {
|
||||||
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
|
log.Warn("New wallet appeared, failed to open", "url", event.Wallet.URL(), "err", err)
|
||||||
|
}
|
||||||
|
case accounts.WalletOpened:
|
||||||
|
status, _ := event.Wallet.Status()
|
||||||
|
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", status)
|
||||||
|
|
||||||
|
if event.Wallet.URL().Scheme == "ledger" {
|
||||||
|
event.Wallet.SelfDerive(accounts.DefaultLedgerBaseDerivationPath, stateReader)
|
||||||
} else {
|
} else {
|
||||||
log.Info("New wallet appeared", "url", event.Wallet.URL(), "status", event.Wallet.Status())
|
|
||||||
event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
|
event.Wallet.SelfDerive(accounts.DefaultBaseDerivationPath, stateReader)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
|
case accounts.WalletDropped:
|
||||||
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
log.Info("Old wallet dropped", "url", event.Wallet.URL())
|
||||||
event.Wallet.Close()
|
event.Wallet.Close()
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/accounts/usbwallet"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
@ -83,6 +84,49 @@ func (b *bridge) NewAccount(call otto.FunctionCall) (response otto.Value) {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenWallet is a wrapper around personal.openWallet which can interpret and
|
||||||
|
// react to certain error messages, such as the Trezor PIN matrix request.
|
||||||
|
func (b *bridge) OpenWallet(call otto.FunctionCall) (response otto.Value) {
|
||||||
|
// Make sure we have an wallet specified to open
|
||||||
|
if !call.Argument(0).IsString() {
|
||||||
|
throwJSException("first argument must be the wallet URL to open")
|
||||||
|
}
|
||||||
|
wallet := call.Argument(0)
|
||||||
|
|
||||||
|
var passwd otto.Value
|
||||||
|
if call.Argument(1).IsUndefined() || call.Argument(1).IsNull() {
|
||||||
|
passwd, _ = otto.ToValue("")
|
||||||
|
} else {
|
||||||
|
passwd = call.Argument(1)
|
||||||
|
}
|
||||||
|
// Open the wallet and return if successful in itself
|
||||||
|
val, err := call.Otto.Call("jeth.openWallet", nil, wallet, passwd)
|
||||||
|
if err == nil {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
// Wallet open failed, report error unless it's a PIN entry
|
||||||
|
if !strings.HasSuffix(err.Error(), usbwallet.ErrTrezorPINNeeded.Error()) {
|
||||||
|
throwJSException(err.Error())
|
||||||
|
}
|
||||||
|
// Trezor PIN matrix input requested, display the matrix to the user and fetch the data
|
||||||
|
fmt.Fprintf(b.printer, "Look at the device for number positions\n\n")
|
||||||
|
fmt.Fprintf(b.printer, "7 | 8 | 9\n")
|
||||||
|
fmt.Fprintf(b.printer, "--+---+--\n")
|
||||||
|
fmt.Fprintf(b.printer, "4 | 5 | 6\n")
|
||||||
|
fmt.Fprintf(b.printer, "--+---+--\n")
|
||||||
|
fmt.Fprintf(b.printer, "1 | 2 | 3\n\n")
|
||||||
|
|
||||||
|
if input, err := b.prompter.PromptPassword("Please enter current PIN: "); err != nil {
|
||||||
|
throwJSException(err.Error())
|
||||||
|
} else {
|
||||||
|
passwd, _ = otto.ToValue(input)
|
||||||
|
}
|
||||||
|
if val, err = call.Otto.Call("jeth.openWallet", nil, wallet, passwd); err != nil {
|
||||||
|
throwJSException(err.Error())
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that
|
// UnlockAccount is a wrapper around the personal.unlockAccount RPC method that
|
||||||
// uses a non-echoing password prompt to acquire the passphrase and executes the
|
// uses a non-echoing password prompt to acquire the passphrase and executes the
|
||||||
// original RPC method (saved in jeth.unlockAccount) with it to actually execute
|
// original RPC method (saved in jeth.unlockAccount) with it to actually execute
|
||||||
|
@ -160,10 +160,15 @@ func (c *Console) init(preload []string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Override the unlockAccount, newAccount and sign methods since these require user interaction.
|
// Override the openWallet, unlockAccount, newAccount and sign methods since
|
||||||
// Assign these method in the Console the original web3 callbacks. These will be called by the jeth.*
|
// these require user interaction. Assign these method in the Console the
|
||||||
// methods after they got the password from the user and send the original web3 request to the backend.
|
// original web3 callbacks. These will be called by the jeth.* methods after
|
||||||
|
// they got the password from the user and send the original web3 request to
|
||||||
|
// the backend.
|
||||||
if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface
|
if obj := personal.Object(); obj != nil { // make sure the personal api is enabled over the interface
|
||||||
|
if _, err = c.jsre.Run(`jeth.openWallet = personal.openWallet;`); err != nil {
|
||||||
|
return fmt.Errorf("personal.openWallet: %v", err)
|
||||||
|
}
|
||||||
if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
|
if _, err = c.jsre.Run(`jeth.unlockAccount = personal.unlockAccount;`); err != nil {
|
||||||
return fmt.Errorf("personal.unlockAccount: %v", err)
|
return fmt.Errorf("personal.unlockAccount: %v", err)
|
||||||
}
|
}
|
||||||
@ -173,6 +178,7 @@ func (c *Console) init(preload []string) error {
|
|||||||
if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil {
|
if _, err = c.jsre.Run(`jeth.sign = personal.sign;`); err != nil {
|
||||||
return fmt.Errorf("personal.sign: %v", err)
|
return fmt.Errorf("personal.sign: %v", err)
|
||||||
}
|
}
|
||||||
|
obj.Set("openWallet", bridge.OpenWallet)
|
||||||
obj.Set("unlockAccount", bridge.UnlockAccount)
|
obj.Set("unlockAccount", bridge.UnlockAccount)
|
||||||
obj.Set("newAccount", bridge.NewAccount)
|
obj.Set("newAccount", bridge.NewAccount)
|
||||||
obj.Set("sign", bridge.Sign)
|
obj.Set("sign", bridge.Sign)
|
||||||
|
@ -230,22 +230,45 @@ func (s *PrivateAccountAPI) ListAccounts() []common.Address {
|
|||||||
type rawWallet struct {
|
type rawWallet struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Accounts []accounts.Account `json:"accounts"`
|
Failure string `json:"failure,omitempty"`
|
||||||
|
Accounts []accounts.Account `json:"accounts,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListWallets will return a list of wallets this node manages.
|
// ListWallets will return a list of wallets this node manages.
|
||||||
func (s *PrivateAccountAPI) ListWallets() []rawWallet {
|
func (s *PrivateAccountAPI) ListWallets() []rawWallet {
|
||||||
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
|
wallets := make([]rawWallet, 0) // return [] instead of nil if empty
|
||||||
for _, wallet := range s.am.Wallets() {
|
for _, wallet := range s.am.Wallets() {
|
||||||
wallets = append(wallets, rawWallet{
|
status, failure := wallet.Status()
|
||||||
|
|
||||||
|
raw := rawWallet{
|
||||||
URL: wallet.URL().String(),
|
URL: wallet.URL().String(),
|
||||||
Status: wallet.Status(),
|
Status: status,
|
||||||
Accounts: wallet.Accounts(),
|
Accounts: wallet.Accounts(),
|
||||||
})
|
}
|
||||||
|
if failure != nil {
|
||||||
|
raw.Failure = failure.Error()
|
||||||
|
}
|
||||||
|
wallets = append(wallets, raw)
|
||||||
}
|
}
|
||||||
return wallets
|
return wallets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OpenWallet initiates a hardware wallet opening procedure, establishing a USB
|
||||||
|
// connection and attempting to authenticate via the provided passphrase. Note,
|
||||||
|
// the method may return an extra challenge requiring a second open (e.g. the
|
||||||
|
// Trezor PIN matrix challenge).
|
||||||
|
func (s *PrivateAccountAPI) OpenWallet(url string, passphrase *string) error {
|
||||||
|
wallet, err := s.am.Wallet(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pass := ""
|
||||||
|
if passphrase != nil {
|
||||||
|
pass = *passphrase
|
||||||
|
}
|
||||||
|
return wallet.Open(pass)
|
||||||
|
}
|
||||||
|
|
||||||
// DeriveAccount requests a HD wallet to derive a new account, optionally pinning
|
// DeriveAccount requests a HD wallet to derive a new account, optionally pinning
|
||||||
// it for later reuse.
|
// it for later reuse.
|
||||||
func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
|
func (s *PrivateAccountAPI) DeriveAccount(url string, path string, pin *bool) (accounts.Account, error) {
|
||||||
|
@ -492,6 +492,11 @@ web3._extend({
|
|||||||
call: 'personal_ecRecover',
|
call: 'personal_ecRecover',
|
||||||
params: 2
|
params: 2
|
||||||
}),
|
}),
|
||||||
|
new web3._extend.Method({
|
||||||
|
name: 'openWallet',
|
||||||
|
call: 'personal_openWallet',
|
||||||
|
params: 2
|
||||||
|
}),
|
||||||
new web3._extend.Method({
|
new web3._extend.Method({
|
||||||
name: 'deriveAccount',
|
name: 'deriveAccount',
|
||||||
call: 'personal_deriveAccount',
|
call: 'personal_deriveAccount',
|
||||||
|
@ -393,11 +393,18 @@ func makeAccountManager(conf *Config) (*accounts.Manager, string, error) {
|
|||||||
keystore.NewKeyStore(keydir, scryptN, scryptP),
|
keystore.NewKeyStore(keydir, scryptN, scryptP),
|
||||||
}
|
}
|
||||||
if !conf.NoUSB {
|
if !conf.NoUSB {
|
||||||
|
// Start a USB hub for Ledger hardware wallets
|
||||||
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
|
if ledgerhub, err := usbwallet.NewLedgerHub(); err != nil {
|
||||||
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
|
log.Warn(fmt.Sprintf("Failed to start Ledger hub, disabling: %v", err))
|
||||||
} else {
|
} else {
|
||||||
backends = append(backends, ledgerhub)
|
backends = append(backends, ledgerhub)
|
||||||
}
|
}
|
||||||
|
// Start a USB hub for Trezor hardware wallets
|
||||||
|
if trezorhub, err := usbwallet.NewTrezorHub(); err != nil {
|
||||||
|
log.Warn(fmt.Sprintf("Failed to start Trezor hub, disabling: %v", err))
|
||||||
|
} else {
|
||||||
|
backends = append(backends, trezorhub)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return accounts.NewManager(backends...), ephemeral, nil
|
return accounts.NewManager(backends...), ephemeral, nil
|
||||||
}
|
}
|
||||||
|
31
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
31
vendor/github.com/golang/protobuf/LICENSE
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
|
||||||
|
Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
https://github.com/golang/protobuf
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* 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.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"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 COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
43
vendor/github.com/golang/protobuf/proto/Makefile
generated
vendored
Normal file
43
vendor/github.com/golang/protobuf/proto/Makefile
generated
vendored
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
#
|
||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# https://github.com/golang/protobuf
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * 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.
|
||||||
|
# * Neither the name of Google Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "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 COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
install:
|
||||||
|
go install
|
||||||
|
|
||||||
|
test: install generate-test-pbs
|
||||||
|
go test
|
||||||
|
|
||||||
|
|
||||||
|
generate-test-pbs:
|
||||||
|
make install
|
||||||
|
make -C testdata
|
||||||
|
protoc --go_out=Mtestdata/test.proto=github.com/golang/protobuf/proto/testdata,Mgoogle/protobuf/any.proto=github.com/golang/protobuf/ptypes/any:. proto3_proto/proto3.proto
|
||||||
|
make
|
229
vendor/github.com/golang/protobuf/proto/clone.go
generated
vendored
Normal file
229
vendor/github.com/golang/protobuf/proto/clone.go
generated
vendored
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
// Protocol buffer deep copy and merge.
|
||||||
|
// TODO: RawMessage.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Clone returns a deep copy of a protocol buffer.
|
||||||
|
func Clone(pb Message) Message {
|
||||||
|
in := reflect.ValueOf(pb)
|
||||||
|
if in.IsNil() {
|
||||||
|
return pb
|
||||||
|
}
|
||||||
|
|
||||||
|
out := reflect.New(in.Type().Elem())
|
||||||
|
// out is empty so a merge is a deep copy.
|
||||||
|
mergeStruct(out.Elem(), in.Elem())
|
||||||
|
return out.Interface().(Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge merges src into dst.
|
||||||
|
// Required and optional fields that are set in src will be set to that value in dst.
|
||||||
|
// Elements of repeated fields will be appended.
|
||||||
|
// Merge panics if src and dst are not the same type, or if dst is nil.
|
||||||
|
func Merge(dst, src Message) {
|
||||||
|
in := reflect.ValueOf(src)
|
||||||
|
out := reflect.ValueOf(dst)
|
||||||
|
if out.IsNil() {
|
||||||
|
panic("proto: nil destination")
|
||||||
|
}
|
||||||
|
if in.Type() != out.Type() {
|
||||||
|
// Explicit test prior to mergeStruct so that mistyped nils will fail
|
||||||
|
panic("proto: type mismatch")
|
||||||
|
}
|
||||||
|
if in.IsNil() {
|
||||||
|
// Merging nil into non-nil is a quiet no-op
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mergeStruct(out.Elem(), in.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeStruct(out, in reflect.Value) {
|
||||||
|
sprop := GetProperties(in.Type())
|
||||||
|
for i := 0; i < in.NumField(); i++ {
|
||||||
|
f := in.Type().Field(i)
|
||||||
|
if strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
mergeAny(out.Field(i), in.Field(i), false, sprop.Prop[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if emIn, ok := extendable(in.Addr().Interface()); ok {
|
||||||
|
emOut, _ := extendable(out.Addr().Interface())
|
||||||
|
mIn, muIn := emIn.extensionsRead()
|
||||||
|
if mIn != nil {
|
||||||
|
mOut := emOut.extensionsWrite()
|
||||||
|
muIn.Lock()
|
||||||
|
mergeExtension(mOut, mIn)
|
||||||
|
muIn.Unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uf := in.FieldByName("XXX_unrecognized")
|
||||||
|
if !uf.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
uin := uf.Bytes()
|
||||||
|
if len(uin) > 0 {
|
||||||
|
out.FieldByName("XXX_unrecognized").SetBytes(append([]byte(nil), uin...))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeAny performs a merge between two values of the same type.
|
||||||
|
// viaPtr indicates whether the values were indirected through a pointer (implying proto2).
|
||||||
|
// prop is set if this is a struct field (it may be nil).
|
||||||
|
func mergeAny(out, in reflect.Value, viaPtr bool, prop *Properties) {
|
||||||
|
if in.Type() == protoMessageType {
|
||||||
|
if !in.IsNil() {
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.ValueOf(Clone(in.Interface().(Message))))
|
||||||
|
} else {
|
||||||
|
Merge(out.Interface().(Message), in.Interface().(Message))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch in.Kind() {
|
||||||
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||||
|
if !viaPtr && isProto3Zero(in) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out.Set(in)
|
||||||
|
case reflect.Interface:
|
||||||
|
// Probably a oneof field; copy non-nil values.
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Allocate destination if it is not set, or set to a different type.
|
||||||
|
// Otherwise we will merge as normal.
|
||||||
|
if out.IsNil() || out.Elem().Type() != in.Elem().Type() {
|
||||||
|
out.Set(reflect.New(in.Elem().Elem().Type())) // interface -> *T -> T -> new(T)
|
||||||
|
}
|
||||||
|
mergeAny(out.Elem(), in.Elem(), false, nil)
|
||||||
|
case reflect.Map:
|
||||||
|
if in.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.MakeMap(in.Type()))
|
||||||
|
}
|
||||||
|
// For maps with value types of *T or []byte we need to deep copy each value.
|
||||||
|
elemKind := in.Type().Elem().Kind()
|
||||||
|
for _, key := range in.MapKeys() {
|
||||||
|
var val reflect.Value
|
||||||
|
switch elemKind {
|
||||||
|
case reflect.Ptr:
|
||||||
|
val = reflect.New(in.Type().Elem().Elem())
|
||||||
|
mergeAny(val, in.MapIndex(key), false, nil)
|
||||||
|
case reflect.Slice:
|
||||||
|
val = in.MapIndex(key)
|
||||||
|
val = reflect.ValueOf(append([]byte{}, val.Bytes()...))
|
||||||
|
default:
|
||||||
|
val = in.MapIndex(key)
|
||||||
|
}
|
||||||
|
out.SetMapIndex(key, val)
|
||||||
|
}
|
||||||
|
case reflect.Ptr:
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.New(in.Elem().Type()))
|
||||||
|
}
|
||||||
|
mergeAny(out.Elem(), in.Elem(), true, nil)
|
||||||
|
case reflect.Slice:
|
||||||
|
if in.IsNil() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if in.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
// []byte is a scalar bytes field, not a repeated field.
|
||||||
|
|
||||||
|
// Edge case: if this is in a proto3 message, a zero length
|
||||||
|
// bytes field is considered the zero value, and should not
|
||||||
|
// be merged.
|
||||||
|
if prop != nil && prop.proto3 && in.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make a deep copy.
|
||||||
|
// Append to []byte{} instead of []byte(nil) so that we never end up
|
||||||
|
// with a nil result.
|
||||||
|
out.SetBytes(append([]byte{}, in.Bytes()...))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n := in.Len()
|
||||||
|
if out.IsNil() {
|
||||||
|
out.Set(reflect.MakeSlice(in.Type(), 0, n))
|
||||||
|
}
|
||||||
|
switch in.Type().Elem().Kind() {
|
||||||
|
case reflect.Bool, reflect.Float32, reflect.Float64, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.String, reflect.Uint32, reflect.Uint64:
|
||||||
|
out.Set(reflect.AppendSlice(out, in))
|
||||||
|
default:
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
x := reflect.Indirect(reflect.New(in.Type().Elem()))
|
||||||
|
mergeAny(x, in.Index(i), false, nil)
|
||||||
|
out.Set(reflect.Append(out, x))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
mergeStruct(out, in)
|
||||||
|
default:
|
||||||
|
// unknown type, so not a protocol buffer
|
||||||
|
log.Printf("proto: don't know how to copy %v", in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergeExtension(out, in map[int32]Extension) {
|
||||||
|
for extNum, eIn := range in {
|
||||||
|
eOut := Extension{desc: eIn.desc}
|
||||||
|
if eIn.value != nil {
|
||||||
|
v := reflect.New(reflect.TypeOf(eIn.value)).Elem()
|
||||||
|
mergeAny(v, reflect.ValueOf(eIn.value), false, nil)
|
||||||
|
eOut.value = v.Interface()
|
||||||
|
}
|
||||||
|
if eIn.enc != nil {
|
||||||
|
eOut.enc = make([]byte, len(eIn.enc))
|
||||||
|
copy(eOut.enc, eIn.enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
out[extNum] = eOut
|
||||||
|
}
|
||||||
|
}
|
970
vendor/github.com/golang/protobuf/proto/decode.go
generated
vendored
Normal file
970
vendor/github.com/golang/protobuf/proto/decode.go
generated
vendored
Normal file
@ -0,0 +1,970 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Routines for decoding protocol buffer data to construct in-memory representations.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errOverflow is returned when an integer is too large to be represented.
|
||||||
|
var errOverflow = errors.New("proto: integer overflow")
|
||||||
|
|
||||||
|
// ErrInternalBadWireType is returned by generated code when an incorrect
|
||||||
|
// wire type is encountered. It does not get returned to user code.
|
||||||
|
var ErrInternalBadWireType = errors.New("proto: internal error: bad wiretype for oneof")
|
||||||
|
|
||||||
|
// The fundamental decoders that interpret bytes on the wire.
|
||||||
|
// Those that take integer types all return uint64 and are
|
||||||
|
// therefore of type valueDecoder.
|
||||||
|
|
||||||
|
// DecodeVarint reads a varint-encoded integer from the slice.
|
||||||
|
// It returns the integer and the number of bytes consumed, or
|
||||||
|
// zero if there is not enough.
|
||||||
|
// This is the format for the
|
||||||
|
// int32, int64, uint32, uint64, bool, and enum
|
||||||
|
// protocol buffer types.
|
||||||
|
func DecodeVarint(buf []byte) (x uint64, n int) {
|
||||||
|
for shift := uint(0); shift < 64; shift += 7 {
|
||||||
|
if n >= len(buf) {
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
b := uint64(buf[n])
|
||||||
|
n++
|
||||||
|
x |= (b & 0x7F) << shift
|
||||||
|
if (b & 0x80) == 0 {
|
||||||
|
return x, n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number is too large to represent in a 64-bit value.
|
||||||
|
return 0, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Buffer) decodeVarintSlow() (x uint64, err error) {
|
||||||
|
i := p.index
|
||||||
|
l := len(p.buf)
|
||||||
|
|
||||||
|
for shift := uint(0); shift < 64; shift += 7 {
|
||||||
|
if i >= l {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := p.buf[i]
|
||||||
|
i++
|
||||||
|
x |= (uint64(b) & 0x7F) << shift
|
||||||
|
if b < 0x80 {
|
||||||
|
p.index = i
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The number is too large to represent in a 64-bit value.
|
||||||
|
err = errOverflow
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeVarint reads a varint-encoded integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// int32, int64, uint32, uint64, bool, and enum
|
||||||
|
// protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeVarint() (x uint64, err error) {
|
||||||
|
i := p.index
|
||||||
|
buf := p.buf
|
||||||
|
|
||||||
|
if i >= len(buf) {
|
||||||
|
return 0, io.ErrUnexpectedEOF
|
||||||
|
} else if buf[i] < 0x80 {
|
||||||
|
p.index++
|
||||||
|
return uint64(buf[i]), nil
|
||||||
|
} else if len(buf)-i < 10 {
|
||||||
|
return p.decodeVarintSlow()
|
||||||
|
}
|
||||||
|
|
||||||
|
var b uint64
|
||||||
|
// we already checked the first byte
|
||||||
|
x = uint64(buf[i]) - 0x80
|
||||||
|
i++
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 7
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 7
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 14
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 14
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 21
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 21
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 28
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 28
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 35
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 35
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 42
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 42
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 49
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 49
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 56
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
x -= 0x80 << 56
|
||||||
|
|
||||||
|
b = uint64(buf[i])
|
||||||
|
i++
|
||||||
|
x += b << 63
|
||||||
|
if b&0x80 == 0 {
|
||||||
|
goto done
|
||||||
|
}
|
||||||
|
// x -= 0x80 << 63 // Always zero.
|
||||||
|
|
||||||
|
return 0, errOverflow
|
||||||
|
|
||||||
|
done:
|
||||||
|
p.index = i
|
||||||
|
return x, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed64 reads a 64-bit integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// fixed64, sfixed64, and double protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeFixed64() (x uint64, err error) {
|
||||||
|
// x, err already 0
|
||||||
|
i := p.index + 8
|
||||||
|
if i < 0 || i > len(p.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.index = i
|
||||||
|
|
||||||
|
x = uint64(p.buf[i-8])
|
||||||
|
x |= uint64(p.buf[i-7]) << 8
|
||||||
|
x |= uint64(p.buf[i-6]) << 16
|
||||||
|
x |= uint64(p.buf[i-5]) << 24
|
||||||
|
x |= uint64(p.buf[i-4]) << 32
|
||||||
|
x |= uint64(p.buf[i-3]) << 40
|
||||||
|
x |= uint64(p.buf[i-2]) << 48
|
||||||
|
x |= uint64(p.buf[i-1]) << 56
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeFixed32 reads a 32-bit integer from the Buffer.
|
||||||
|
// This is the format for the
|
||||||
|
// fixed32, sfixed32, and float protocol buffer types.
|
||||||
|
func (p *Buffer) DecodeFixed32() (x uint64, err error) {
|
||||||
|
// x, err already 0
|
||||||
|
i := p.index + 4
|
||||||
|
if i < 0 || i > len(p.buf) {
|
||||||
|
err = io.ErrUnexpectedEOF
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.index = i
|
||||||
|
|
||||||
|
x = uint64(p.buf[i-4])
|
||||||
|
x |= uint64(p.buf[i-3]) << 8
|
||||||
|
x |= uint64(p.buf[i-2]) << 16
|
||||||
|
x |= uint64(p.buf[i-1]) << 24
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag64 reads a zigzag-encoded 64-bit integer
|
||||||
|
// from the Buffer.
|
||||||
|
// This is the format used for the sint64 protocol buffer type.
|
||||||
|
func (p *Buffer) DecodeZigzag64() (x uint64, err error) {
|
||||||
|
x, err = p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x = (x >> 1) ^ uint64((int64(x&1)<<63)>>63)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeZigzag32 reads a zigzag-encoded 32-bit integer
|
||||||
|
// from the Buffer.
|
||||||
|
// This is the format used for the sint32 protocol buffer type.
|
||||||
|
func (p *Buffer) DecodeZigzag32() (x uint64, err error) {
|
||||||
|
x, err = p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x = uint64((uint32(x) >> 1) ^ uint32((int32(x&1)<<31)>>31))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// These are not ValueDecoders: they produce an array of bytes or a string.
|
||||||
|
// bytes, embedded messages
|
||||||
|
|
||||||
|
// DecodeRawBytes reads a count-delimited byte buffer from the Buffer.
|
||||||
|
// This is the format used for the bytes protocol buffer
|
||||||
|
// type and for embedded messages.
|
||||||
|
func (p *Buffer) DecodeRawBytes(alloc bool) (buf []byte, err error) {
|
||||||
|
n, err := p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nb := int(n)
|
||||||
|
if nb < 0 {
|
||||||
|
return nil, fmt.Errorf("proto: bad byte length %d", nb)
|
||||||
|
}
|
||||||
|
end := p.index + nb
|
||||||
|
if end < p.index || end > len(p.buf) {
|
||||||
|
return nil, io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
|
||||||
|
if !alloc {
|
||||||
|
// todo: check if can get more uses of alloc=false
|
||||||
|
buf = p.buf[p.index:end]
|
||||||
|
p.index += nb
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = make([]byte, nb)
|
||||||
|
copy(buf, p.buf[p.index:])
|
||||||
|
p.index += nb
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeStringBytes reads an encoded string from the Buffer.
|
||||||
|
// This is the format used for the proto2 string type.
|
||||||
|
func (p *Buffer) DecodeStringBytes() (s string, err error) {
|
||||||
|
buf, err := p.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||||
|
// If the protocol buffer has extensions, and the field matches, add it as an extension.
|
||||||
|
// Otherwise, if the XXX_unrecognized field exists, append the skipped data there.
|
||||||
|
func (o *Buffer) skipAndSave(t reflect.Type, tag, wire int, base structPointer, unrecField field) error {
|
||||||
|
oi := o.index
|
||||||
|
|
||||||
|
err := o.skip(t, tag, wire)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !unrecField.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr := structPointer_Bytes(base, unrecField)
|
||||||
|
|
||||||
|
// Add the skipped field to struct field
|
||||||
|
obuf := o.buf
|
||||||
|
|
||||||
|
o.buf = *ptr
|
||||||
|
o.EncodeVarint(uint64(tag<<3 | wire))
|
||||||
|
*ptr = append(o.buf, obuf[oi:o.index]...)
|
||||||
|
|
||||||
|
o.buf = obuf
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip the next item in the buffer. Its wire type is decoded and presented as an argument.
|
||||||
|
func (o *Buffer) skip(t reflect.Type, tag, wire int) error {
|
||||||
|
|
||||||
|
var u uint64
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch wire {
|
||||||
|
case WireVarint:
|
||||||
|
_, err = o.DecodeVarint()
|
||||||
|
case WireFixed64:
|
||||||
|
_, err = o.DecodeFixed64()
|
||||||
|
case WireBytes:
|
||||||
|
_, err = o.DecodeRawBytes(false)
|
||||||
|
case WireFixed32:
|
||||||
|
_, err = o.DecodeFixed32()
|
||||||
|
case WireStartGroup:
|
||||||
|
for {
|
||||||
|
u, err = o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fwire := int(u & 0x7)
|
||||||
|
if fwire == WireEndGroup {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ftag := int(u >> 3)
|
||||||
|
err = o.skip(t, ftag, fwire)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("proto: can't skip unknown wire type %d for %s", wire, t)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshaler is the interface representing objects that can
|
||||||
|
// unmarshal themselves. The method should reset the receiver before
|
||||||
|
// decoding starts. The argument points to data that may be
|
||||||
|
// overwritten, so implementations should not keep references to the
|
||||||
|
// buffer.
|
||||||
|
type Unmarshaler interface {
|
||||||
|
Unmarshal([]byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the protocol buffer representation in buf and places the
|
||||||
|
// decoded result in pb. If the struct underlying pb does not match
|
||||||
|
// the data in buf, the results can be unpredictable.
|
||||||
|
//
|
||||||
|
// Unmarshal resets pb before starting to unmarshal, so any
|
||||||
|
// existing data in pb is always removed. Use UnmarshalMerge
|
||||||
|
// to preserve and append to existing data.
|
||||||
|
func Unmarshal(buf []byte, pb Message) error {
|
||||||
|
pb.Reset()
|
||||||
|
return UnmarshalMerge(buf, pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMerge parses the protocol buffer representation in buf and
|
||||||
|
// writes the decoded result to pb. If the struct underlying pb does not match
|
||||||
|
// the data in buf, the results can be unpredictable.
|
||||||
|
//
|
||||||
|
// UnmarshalMerge merges into existing data in pb.
|
||||||
|
// Most code should use Unmarshal instead.
|
||||||
|
func UnmarshalMerge(buf []byte, pb Message) error {
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if u, ok := pb.(Unmarshaler); ok {
|
||||||
|
return u.Unmarshal(buf)
|
||||||
|
}
|
||||||
|
return NewBuffer(buf).Unmarshal(pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeMessage reads a count-delimited message from the Buffer.
|
||||||
|
func (p *Buffer) DecodeMessage(pb Message) error {
|
||||||
|
enc, err := p.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return NewBuffer(enc).Unmarshal(pb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeGroup reads a tag-delimited group from the Buffer.
|
||||||
|
func (p *Buffer) DecodeGroup(pb Message) error {
|
||||||
|
typ, base, err := getbase(pb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), true, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal parses the protocol buffer representation in the
|
||||||
|
// Buffer and places the decoded result in pb. If the struct
|
||||||
|
// underlying pb does not match the data in the buffer, the results can be
|
||||||
|
// unpredictable.
|
||||||
|
//
|
||||||
|
// Unlike proto.Unmarshal, this does not reset pb before starting to unmarshal.
|
||||||
|
func (p *Buffer) Unmarshal(pb Message) error {
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if u, ok := pb.(Unmarshaler); ok {
|
||||||
|
err := u.Unmarshal(p.buf[p.index:])
|
||||||
|
p.index = len(p.buf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
typ, base, err := getbase(pb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = p.unmarshalType(typ.Elem(), GetProperties(typ.Elem()), false, base)
|
||||||
|
|
||||||
|
if collectStats {
|
||||||
|
stats.Decode++
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshalType does the work of unmarshaling a structure.
|
||||||
|
func (o *Buffer) unmarshalType(st reflect.Type, prop *StructProperties, is_group bool, base structPointer) error {
|
||||||
|
var state errorState
|
||||||
|
required, reqFields := prop.reqCount, uint64(0)
|
||||||
|
|
||||||
|
var err error
|
||||||
|
for err == nil && o.index < len(o.buf) {
|
||||||
|
oi := o.index
|
||||||
|
var u uint64
|
||||||
|
u, err = o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
wire := int(u & 0x7)
|
||||||
|
if wire == WireEndGroup {
|
||||||
|
if is_group {
|
||||||
|
if required > 0 {
|
||||||
|
// Not enough information to determine the exact field.
|
||||||
|
// (See below.)
|
||||||
|
return &RequiredNotSetError{"{Unknown}"}
|
||||||
|
}
|
||||||
|
return nil // input is satisfied
|
||||||
|
}
|
||||||
|
return fmt.Errorf("proto: %s: wiretype end group for non-group", st)
|
||||||
|
}
|
||||||
|
tag := int(u >> 3)
|
||||||
|
if tag <= 0 {
|
||||||
|
return fmt.Errorf("proto: %s: illegal tag %d (wire type %d)", st, tag, wire)
|
||||||
|
}
|
||||||
|
fieldnum, ok := prop.decoderTags.get(tag)
|
||||||
|
if !ok {
|
||||||
|
// Maybe it's an extension?
|
||||||
|
if prop.extendable {
|
||||||
|
if e, _ := extendable(structPointer_Interface(base, st)); isExtensionField(e, int32(tag)) {
|
||||||
|
if err = o.skip(st, tag, wire); err == nil {
|
||||||
|
extmap := e.extensionsWrite()
|
||||||
|
ext := extmap[int32(tag)] // may be missing
|
||||||
|
ext.enc = append(ext.enc, o.buf[oi:o.index]...)
|
||||||
|
extmap[int32(tag)] = ext
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Maybe it's a oneof?
|
||||||
|
if prop.oneofUnmarshaler != nil {
|
||||||
|
m := structPointer_Interface(base, st).(Message)
|
||||||
|
// First return value indicates whether tag is a oneof field.
|
||||||
|
ok, err = prop.oneofUnmarshaler(m, tag, wire, o)
|
||||||
|
if err == ErrInternalBadWireType {
|
||||||
|
// Map the error to something more descriptive.
|
||||||
|
// Do the formatting here to save generated code space.
|
||||||
|
err = fmt.Errorf("bad wiretype for oneof field in %T", m)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = o.skipAndSave(st, tag, wire, base, prop.unrecField)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := prop.Prop[fieldnum]
|
||||||
|
|
||||||
|
if p.dec == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no protobuf decoder for %s.%s\n", st, st.Field(fieldnum).Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dec := p.dec
|
||||||
|
if wire != WireStartGroup && wire != p.WireType {
|
||||||
|
if wire == WireBytes && p.packedDec != nil {
|
||||||
|
// a packable field
|
||||||
|
dec = p.packedDec
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("proto: bad wiretype for field %s.%s: got wiretype %d, want %d", st, st.Field(fieldnum).Name, wire, p.WireType)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decErr := dec(o, p, base)
|
||||||
|
if decErr != nil && !state.shouldContinue(decErr, p) {
|
||||||
|
err = decErr
|
||||||
|
}
|
||||||
|
if err == nil && p.Required {
|
||||||
|
// Successfully decoded a required field.
|
||||||
|
if tag <= 64 {
|
||||||
|
// use bitmap for fields 1-64 to catch field reuse.
|
||||||
|
var mask uint64 = 1 << uint64(tag-1)
|
||||||
|
if reqFields&mask == 0 {
|
||||||
|
// new required field
|
||||||
|
reqFields |= mask
|
||||||
|
required--
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is imprecise. It can be fooled by a required field
|
||||||
|
// with a tag > 64 that is encoded twice; that's very rare.
|
||||||
|
// A fully correct implementation would require allocating
|
||||||
|
// a data structure, which we would like to avoid.
|
||||||
|
required--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if is_group {
|
||||||
|
return io.ErrUnexpectedEOF
|
||||||
|
}
|
||||||
|
if state.err != nil {
|
||||||
|
return state.err
|
||||||
|
}
|
||||||
|
if required > 0 {
|
||||||
|
// Not enough information to determine the exact field. If we use extra
|
||||||
|
// CPU, we could determine the field only if the missing required field
|
||||||
|
// has a tag <= 64 and we check reqFields.
|
||||||
|
return &RequiredNotSetError{"{Unknown}"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Individual type decoders
|
||||||
|
// For each,
|
||||||
|
// u is the decoded value,
|
||||||
|
// v is a pointer to the field (pointer) in the struct
|
||||||
|
|
||||||
|
// Sizes of the pools to allocate inside the Buffer.
|
||||||
|
// The goal is modest amortization and allocation
|
||||||
|
// on at least 16-byte boundaries.
|
||||||
|
const (
|
||||||
|
boolPoolSize = 16
|
||||||
|
uint32PoolSize = 8
|
||||||
|
uint64PoolSize = 4
|
||||||
|
)
|
||||||
|
|
||||||
|
// Decode a bool.
|
||||||
|
func (o *Buffer) dec_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(o.bools) == 0 {
|
||||||
|
o.bools = make([]bool, boolPoolSize)
|
||||||
|
}
|
||||||
|
o.bools[0] = u != 0
|
||||||
|
*structPointer_Bool(base, p.field) = &o.bools[0]
|
||||||
|
o.bools = o.bools[1:]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_BoolVal(base, p.field) = u != 0
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an int32.
|
||||||
|
func (o *Buffer) dec_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word32_Set(structPointer_Word32(base, p.field), o, uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word32Val_Set(structPointer_Word32Val(base, p.field), uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an int64.
|
||||||
|
func (o *Buffer) dec_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word64_Set(structPointer_Word64(base, p.field), o, u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
word64Val_Set(structPointer_Word64Val(base, p.field), o, u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a string.
|
||||||
|
func (o *Buffer) dec_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_String(base, p.field) = &s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Buffer) dec_proto3_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_StringVal(base, p.field) = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bytes ([]byte).
|
||||||
|
func (o *Buffer) dec_slice_byte(p *Properties, base structPointer) error {
|
||||||
|
b, err := o.DecodeRawBytes(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*structPointer_Bytes(base, p.field) = b
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bools ([]bool).
|
||||||
|
func (o *Buffer) dec_slice_bool(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_BoolSlice(base, p.field)
|
||||||
|
*v = append(*v, u != 0)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of bools ([]bool) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_bool(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_BoolSlice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded bools
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
|
||||||
|
y := *v
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
y = append(y, u != 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
*v = y
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int32s ([]int32).
|
||||||
|
func (o *Buffer) dec_slice_int32(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
structPointer_Word32Slice(base, p.field).Append(uint32(u))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int32s ([]int32) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_int32(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_Word32Slice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded int32s
|
||||||
|
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Append(uint32(u))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int64s ([]int64).
|
||||||
|
func (o *Buffer) dec_slice_int64(p *Properties, base structPointer) error {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
structPointer_Word64Slice(base, p.field).Append(u)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of int64s ([]int64) in packed format.
|
||||||
|
func (o *Buffer) dec_slice_packed_int64(p *Properties, base structPointer) error {
|
||||||
|
v := structPointer_Word64Slice(base, p.field)
|
||||||
|
|
||||||
|
nn, err := o.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
nb := int(nn) // number of bytes of encoded int64s
|
||||||
|
|
||||||
|
fin := o.index + nb
|
||||||
|
if fin < o.index {
|
||||||
|
return errOverflow
|
||||||
|
}
|
||||||
|
for o.index < fin {
|
||||||
|
u, err := p.valDec(o)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v.Append(u)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of strings ([]string).
|
||||||
|
func (o *Buffer) dec_slice_string(p *Properties, base structPointer) error {
|
||||||
|
s, err := o.DecodeStringBytes()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_StringSlice(base, p.field)
|
||||||
|
*v = append(*v, s)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of slice of bytes ([][]byte).
|
||||||
|
func (o *Buffer) dec_slice_slice_byte(p *Properties, base structPointer) error {
|
||||||
|
b, err := o.DecodeRawBytes(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
v := structPointer_BytesSlice(base, p.field)
|
||||||
|
*v = append(*v, b)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a map field.
|
||||||
|
func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
|
||||||
|
raw, err := o.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
oi := o.index // index at the end of this map entry
|
||||||
|
o.index -= len(raw) // move buffer back to start of map entry
|
||||||
|
|
||||||
|
mptr := structPointer_NewAt(base, p.field, p.mtype) // *map[K]V
|
||||||
|
if mptr.Elem().IsNil() {
|
||||||
|
mptr.Elem().Set(reflect.MakeMap(mptr.Type().Elem()))
|
||||||
|
}
|
||||||
|
v := mptr.Elem() // map[K]V
|
||||||
|
|
||||||
|
// Prepare addressable doubly-indirect placeholders for the key and value types.
|
||||||
|
// See enc_new_map for why.
|
||||||
|
keyptr := reflect.New(reflect.PtrTo(p.mtype.Key())).Elem() // addressable *K
|
||||||
|
keybase := toStructPointer(keyptr.Addr()) // **K
|
||||||
|
|
||||||
|
var valbase structPointer
|
||||||
|
var valptr reflect.Value
|
||||||
|
switch p.mtype.Elem().Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// []byte
|
||||||
|
var dummy []byte
|
||||||
|
valptr = reflect.ValueOf(&dummy) // *[]byte
|
||||||
|
valbase = toStructPointer(valptr) // *[]byte
|
||||||
|
case reflect.Ptr:
|
||||||
|
// message; valptr is **Msg; need to allocate the intermediate pointer
|
||||||
|
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||||
|
valptr.Set(reflect.New(valptr.Type().Elem()))
|
||||||
|
valbase = toStructPointer(valptr)
|
||||||
|
default:
|
||||||
|
// everything else
|
||||||
|
valptr = reflect.New(reflect.PtrTo(p.mtype.Elem())).Elem() // addressable *V
|
||||||
|
valbase = toStructPointer(valptr.Addr()) // **V
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode.
|
||||||
|
// This parses a restricted wire format, namely the encoding of a message
|
||||||
|
// with two fields. See enc_new_map for the format.
|
||||||
|
for o.index < oi {
|
||||||
|
// tagcode for key and value properties are always a single byte
|
||||||
|
// because they have tags 1 and 2.
|
||||||
|
tagcode := o.buf[o.index]
|
||||||
|
o.index++
|
||||||
|
switch tagcode {
|
||||||
|
case p.mkeyprop.tagcode[0]:
|
||||||
|
if err := p.mkeyprop.dec(o, p.mkeyprop, keybase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case p.mvalprop.tagcode[0]:
|
||||||
|
if err := p.mvalprop.dec(o, p.mvalprop, valbase); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// TODO: Should we silently skip this instead?
|
||||||
|
return fmt.Errorf("proto: bad map data tag %d", raw[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
keyelem, valelem := keyptr.Elem(), valptr.Elem()
|
||||||
|
if !keyelem.IsValid() {
|
||||||
|
keyelem = reflect.Zero(p.mtype.Key())
|
||||||
|
}
|
||||||
|
if !valelem.IsValid() {
|
||||||
|
valelem = reflect.Zero(p.mtype.Elem())
|
||||||
|
}
|
||||||
|
|
||||||
|
v.SetMapIndex(keyelem, valelem)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a group.
|
||||||
|
func (o *Buffer) dec_struct_group(p *Properties, base structPointer) error {
|
||||||
|
bas := structPointer_GetStructPointer(base, p.field)
|
||||||
|
if structPointer_IsNil(bas) {
|
||||||
|
// allocate new nested message
|
||||||
|
bas = toStructPointer(reflect.New(p.stype))
|
||||||
|
structPointer_SetStructPointer(base, p.field, bas)
|
||||||
|
}
|
||||||
|
return o.unmarshalType(p.stype, p.sprop, true, bas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode an embedded message.
|
||||||
|
func (o *Buffer) dec_struct_message(p *Properties, base structPointer) (err error) {
|
||||||
|
raw, e := o.DecodeRawBytes(false)
|
||||||
|
if e != nil {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
bas := structPointer_GetStructPointer(base, p.field)
|
||||||
|
if structPointer_IsNil(bas) {
|
||||||
|
// allocate new nested message
|
||||||
|
bas = toStructPointer(reflect.New(p.stype))
|
||||||
|
structPointer_SetStructPointer(base, p.field, bas)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if p.isUnmarshaler {
|
||||||
|
iv := structPointer_Interface(bas, p.stype)
|
||||||
|
return iv.(Unmarshaler).Unmarshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
obuf := o.buf
|
||||||
|
oi := o.index
|
||||||
|
o.buf = raw
|
||||||
|
o.index = 0
|
||||||
|
|
||||||
|
err = o.unmarshalType(p.stype, p.sprop, false, bas)
|
||||||
|
o.buf = obuf
|
||||||
|
o.index = oi
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of embedded messages.
|
||||||
|
func (o *Buffer) dec_slice_struct_message(p *Properties, base structPointer) error {
|
||||||
|
return o.dec_slice_struct(p, false, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of embedded groups.
|
||||||
|
func (o *Buffer) dec_slice_struct_group(p *Properties, base structPointer) error {
|
||||||
|
return o.dec_slice_struct(p, true, base)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode a slice of structs ([]*struct).
|
||||||
|
func (o *Buffer) dec_slice_struct(p *Properties, is_group bool, base structPointer) error {
|
||||||
|
v := reflect.New(p.stype)
|
||||||
|
bas := toStructPointer(v)
|
||||||
|
structPointer_StructPointerSlice(base, p.field).Append(bas)
|
||||||
|
|
||||||
|
if is_group {
|
||||||
|
err := o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := o.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the object can unmarshal itself, let it.
|
||||||
|
if p.isUnmarshaler {
|
||||||
|
iv := v.Interface()
|
||||||
|
return iv.(Unmarshaler).Unmarshal(raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
obuf := o.buf
|
||||||
|
oi := o.index
|
||||||
|
o.buf = raw
|
||||||
|
o.index = 0
|
||||||
|
|
||||||
|
err = o.unmarshalType(p.stype, p.sprop, is_group, bas)
|
||||||
|
|
||||||
|
o.buf = obuf
|
||||||
|
o.index = oi
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
1362
vendor/github.com/golang/protobuf/proto/encode.go
generated
vendored
Normal file
1362
vendor/github.com/golang/protobuf/proto/encode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
300
vendor/github.com/golang/protobuf/proto/equal.go
generated
vendored
Normal file
300
vendor/github.com/golang/protobuf/proto/equal.go
generated
vendored
Normal file
@ -0,0 +1,300 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
// Protocol buffer comparison.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
Equal returns true iff protocol buffers a and b are equal.
|
||||||
|
The arguments must both be pointers to protocol buffer structs.
|
||||||
|
|
||||||
|
Equality is defined in this way:
|
||||||
|
- Two messages are equal iff they are the same type,
|
||||||
|
corresponding fields are equal, unknown field sets
|
||||||
|
are equal, and extensions sets are equal.
|
||||||
|
- Two set scalar fields are equal iff their values are equal.
|
||||||
|
If the fields are of a floating-point type, remember that
|
||||||
|
NaN != x for all x, including NaN. If the message is defined
|
||||||
|
in a proto3 .proto file, fields are not "set"; specifically,
|
||||||
|
zero length proto3 "bytes" fields are equal (nil == {}).
|
||||||
|
- Two repeated fields are equal iff their lengths are the same,
|
||||||
|
and their corresponding elements are equal. Note a "bytes" field,
|
||||||
|
although represented by []byte, is not a repeated field and the
|
||||||
|
rule for the scalar fields described above applies.
|
||||||
|
- Two unset fields are equal.
|
||||||
|
- Two unknown field sets are equal if their current
|
||||||
|
encoded state is equal.
|
||||||
|
- Two extension sets are equal iff they have corresponding
|
||||||
|
elements that are pairwise equal.
|
||||||
|
- Two map fields are equal iff their lengths are the same,
|
||||||
|
and they contain the same set of elements. Zero-length map
|
||||||
|
fields are equal.
|
||||||
|
- Every other combination of things are not equal.
|
||||||
|
|
||||||
|
The return value is undefined if a and b are not protocol buffers.
|
||||||
|
*/
|
||||||
|
func Equal(a, b Message) bool {
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
v1, v2 := reflect.ValueOf(a), reflect.ValueOf(b)
|
||||||
|
if v1.Type() != v2.Type() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if v1.Kind() == reflect.Ptr {
|
||||||
|
if v1.IsNil() {
|
||||||
|
return v2.IsNil()
|
||||||
|
}
|
||||||
|
if v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
v1, v2 = v1.Elem(), v2.Elem()
|
||||||
|
}
|
||||||
|
if v1.Kind() != reflect.Struct {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalStruct(v1, v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1 and v2 are known to have the same type.
|
||||||
|
func equalStruct(v1, v2 reflect.Value) bool {
|
||||||
|
sprop := GetProperties(v1.Type())
|
||||||
|
for i := 0; i < v1.NumField(); i++ {
|
||||||
|
f := v1.Type().Field(i)
|
||||||
|
if strings.HasPrefix(f.Name, "XXX_") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f1, f2 := v1.Field(i), v2.Field(i)
|
||||||
|
if f.Type.Kind() == reflect.Ptr {
|
||||||
|
if n1, n2 := f1.IsNil(), f2.IsNil(); n1 && n2 {
|
||||||
|
// both unset
|
||||||
|
continue
|
||||||
|
} else if n1 != n2 {
|
||||||
|
// set/unset mismatch
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b1, ok := f1.Interface().(raw)
|
||||||
|
if ok {
|
||||||
|
b2 := f2.Interface().(raw)
|
||||||
|
// RawMessage
|
||||||
|
if !bytes.Equal(b1.Bytes(), b2.Bytes()) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f1, f2 = f1.Elem(), f2.Elem()
|
||||||
|
}
|
||||||
|
if !equalAny(f1, f2, sprop.Prop[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if em1 := v1.FieldByName("XXX_InternalExtensions"); em1.IsValid() {
|
||||||
|
em2 := v2.FieldByName("XXX_InternalExtensions")
|
||||||
|
if !equalExtensions(v1.Type(), em1.Interface().(XXX_InternalExtensions), em2.Interface().(XXX_InternalExtensions)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if em1 := v1.FieldByName("XXX_extensions"); em1.IsValid() {
|
||||||
|
em2 := v2.FieldByName("XXX_extensions")
|
||||||
|
if !equalExtMap(v1.Type(), em1.Interface().(map[int32]Extension), em2.Interface().(map[int32]Extension)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uf := v1.FieldByName("XXX_unrecognized")
|
||||||
|
if !uf.IsValid() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
u1 := uf.Bytes()
|
||||||
|
u2 := v2.FieldByName("XXX_unrecognized").Bytes()
|
||||||
|
if !bytes.Equal(u1, u2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// v1 and v2 are known to have the same type.
|
||||||
|
// prop may be nil.
|
||||||
|
func equalAny(v1, v2 reflect.Value, prop *Properties) bool {
|
||||||
|
if v1.Type() == protoMessageType {
|
||||||
|
m1, _ := v1.Interface().(Message)
|
||||||
|
m2, _ := v2.Interface().(Message)
|
||||||
|
return Equal(m1, m2)
|
||||||
|
}
|
||||||
|
switch v1.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return v1.Bool() == v2.Bool()
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v1.Float() == v2.Float()
|
||||||
|
case reflect.Int32, reflect.Int64:
|
||||||
|
return v1.Int() == v2.Int()
|
||||||
|
case reflect.Interface:
|
||||||
|
// Probably a oneof field; compare the inner values.
|
||||||
|
n1, n2 := v1.IsNil(), v2.IsNil()
|
||||||
|
if n1 || n2 {
|
||||||
|
return n1 == n2
|
||||||
|
}
|
||||||
|
e1, e2 := v1.Elem(), v2.Elem()
|
||||||
|
if e1.Type() != e2.Type() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalAny(e1, e2, nil)
|
||||||
|
case reflect.Map:
|
||||||
|
if v1.Len() != v2.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, key := range v1.MapKeys() {
|
||||||
|
val2 := v2.MapIndex(key)
|
||||||
|
if !val2.IsValid() {
|
||||||
|
// This key was not found in the second map.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !equalAny(v1.MapIndex(key), val2, nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case reflect.Ptr:
|
||||||
|
// Maps may have nil values in them, so check for nil.
|
||||||
|
if v1.IsNil() && v2.IsNil() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v1.IsNil() != v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return equalAny(v1.Elem(), v2.Elem(), prop)
|
||||||
|
case reflect.Slice:
|
||||||
|
if v1.Type().Elem().Kind() == reflect.Uint8 {
|
||||||
|
// short circuit: []byte
|
||||||
|
|
||||||
|
// Edge case: if this is in a proto3 message, a zero length
|
||||||
|
// bytes field is considered the zero value.
|
||||||
|
if prop != nil && prop.proto3 && v1.Len() == 0 && v2.Len() == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if v1.IsNil() != v2.IsNil() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return bytes.Equal(v1.Interface().([]byte), v2.Interface().([]byte))
|
||||||
|
}
|
||||||
|
|
||||||
|
if v1.Len() != v2.Len() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < v1.Len(); i++ {
|
||||||
|
if !equalAny(v1.Index(i), v2.Index(i), prop) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
case reflect.String:
|
||||||
|
return v1.Interface().(string) == v2.Interface().(string)
|
||||||
|
case reflect.Struct:
|
||||||
|
return equalStruct(v1, v2)
|
||||||
|
case reflect.Uint32, reflect.Uint64:
|
||||||
|
return v1.Uint() == v2.Uint()
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknown type, so not a protocol buffer
|
||||||
|
log.Printf("proto: don't know how to compare %v", v1)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// base is the struct type that the extensions are based on.
|
||||||
|
// x1 and x2 are InternalExtensions.
|
||||||
|
func equalExtensions(base reflect.Type, x1, x2 XXX_InternalExtensions) bool {
|
||||||
|
em1, _ := x1.extensionsRead()
|
||||||
|
em2, _ := x2.extensionsRead()
|
||||||
|
return equalExtMap(base, em1, em2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func equalExtMap(base reflect.Type, em1, em2 map[int32]Extension) bool {
|
||||||
|
if len(em1) != len(em2) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for extNum, e1 := range em1 {
|
||||||
|
e2, ok := em2[extNum]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
m1, m2 := e1.value, e2.value
|
||||||
|
|
||||||
|
if m1 != nil && m2 != nil {
|
||||||
|
// Both are unencoded.
|
||||||
|
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// At least one is encoded. To do a semantically correct comparison
|
||||||
|
// we need to unmarshal them first.
|
||||||
|
var desc *ExtensionDesc
|
||||||
|
if m := extensionMaps[base]; m != nil {
|
||||||
|
desc = m[extNum]
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
log.Printf("proto: don't know how to compare extension %d of %v", extNum, base)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if m1 == nil {
|
||||||
|
m1, err = decodeExtension(e1.enc, desc)
|
||||||
|
}
|
||||||
|
if m2 == nil && err == nil {
|
||||||
|
m2, err = decodeExtension(e2.enc, desc)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
// The encoded form is invalid.
|
||||||
|
log.Printf("proto: badly encoded extension %d of %v: %v", extNum, base, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !equalAny(reflect.ValueOf(m1), reflect.ValueOf(m2), nil) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
587
vendor/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
587
vendor/github.com/golang/protobuf/proto/extensions.go
generated
vendored
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Types and routines for supporting protocol buffer extensions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrMissingExtension is the error returned by GetExtension if the named extension is not in the message.
|
||||||
|
var ErrMissingExtension = errors.New("proto: missing extension")
|
||||||
|
|
||||||
|
// ExtensionRange represents a range of message extensions for a protocol buffer.
|
||||||
|
// Used in code generated by the protocol compiler.
|
||||||
|
type ExtensionRange struct {
|
||||||
|
Start, End int32 // both inclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
// extendableProto is an interface implemented by any protocol buffer generated by the current
|
||||||
|
// proto compiler that may be extended.
|
||||||
|
type extendableProto interface {
|
||||||
|
Message
|
||||||
|
ExtensionRangeArray() []ExtensionRange
|
||||||
|
extensionsWrite() map[int32]Extension
|
||||||
|
extensionsRead() (map[int32]Extension, sync.Locker)
|
||||||
|
}
|
||||||
|
|
||||||
|
// extendableProtoV1 is an interface implemented by a protocol buffer generated by the previous
|
||||||
|
// version of the proto compiler that may be extended.
|
||||||
|
type extendableProtoV1 interface {
|
||||||
|
Message
|
||||||
|
ExtensionRangeArray() []ExtensionRange
|
||||||
|
ExtensionMap() map[int32]Extension
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionAdapter is a wrapper around extendableProtoV1 that implements extendableProto.
|
||||||
|
type extensionAdapter struct {
|
||||||
|
extendableProtoV1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extensionAdapter) extensionsWrite() map[int32]Extension {
|
||||||
|
return e.ExtensionMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e extensionAdapter) extensionsRead() (map[int32]Extension, sync.Locker) {
|
||||||
|
return e.ExtensionMap(), notLocker{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// notLocker is a sync.Locker whose Lock and Unlock methods are nops.
|
||||||
|
type notLocker struct{}
|
||||||
|
|
||||||
|
func (n notLocker) Lock() {}
|
||||||
|
func (n notLocker) Unlock() {}
|
||||||
|
|
||||||
|
// extendable returns the extendableProto interface for the given generated proto message.
|
||||||
|
// If the proto message has the old extension format, it returns a wrapper that implements
|
||||||
|
// the extendableProto interface.
|
||||||
|
func extendable(p interface{}) (extendableProto, bool) {
|
||||||
|
if ep, ok := p.(extendableProto); ok {
|
||||||
|
return ep, ok
|
||||||
|
}
|
||||||
|
if ep, ok := p.(extendableProtoV1); ok {
|
||||||
|
return extensionAdapter{ep}, ok
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX_InternalExtensions is an internal representation of proto extensions.
|
||||||
|
//
|
||||||
|
// Each generated message struct type embeds an anonymous XXX_InternalExtensions field,
|
||||||
|
// thus gaining the unexported 'extensions' method, which can be called only from the proto package.
|
||||||
|
//
|
||||||
|
// The methods of XXX_InternalExtensions are not concurrency safe in general,
|
||||||
|
// but calls to logically read-only methods such as has and get may be executed concurrently.
|
||||||
|
type XXX_InternalExtensions struct {
|
||||||
|
// The struct must be indirect so that if a user inadvertently copies a
|
||||||
|
// generated message and its embedded XXX_InternalExtensions, they
|
||||||
|
// avoid the mayhem of a copied mutex.
|
||||||
|
//
|
||||||
|
// The mutex serializes all logically read-only operations to p.extensionMap.
|
||||||
|
// It is up to the client to ensure that write operations to p.extensionMap are
|
||||||
|
// mutually exclusive with other accesses.
|
||||||
|
p *struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
extensionMap map[int32]Extension
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionsWrite returns the extension map, creating it on first use.
|
||||||
|
func (e *XXX_InternalExtensions) extensionsWrite() map[int32]Extension {
|
||||||
|
if e.p == nil {
|
||||||
|
e.p = new(struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
extensionMap map[int32]Extension
|
||||||
|
})
|
||||||
|
e.p.extensionMap = make(map[int32]Extension)
|
||||||
|
}
|
||||||
|
return e.p.extensionMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// extensionsRead returns the extensions map for read-only use. It may be nil.
|
||||||
|
// The caller must hold the returned mutex's lock when accessing Elements within the map.
|
||||||
|
func (e *XXX_InternalExtensions) extensionsRead() (map[int32]Extension, sync.Locker) {
|
||||||
|
if e.p == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return e.p.extensionMap, &e.p.mu
|
||||||
|
}
|
||||||
|
|
||||||
|
var extendableProtoType = reflect.TypeOf((*extendableProto)(nil)).Elem()
|
||||||
|
var extendableProtoV1Type = reflect.TypeOf((*extendableProtoV1)(nil)).Elem()
|
||||||
|
|
||||||
|
// ExtensionDesc represents an extension specification.
|
||||||
|
// Used in generated code from the protocol compiler.
|
||||||
|
type ExtensionDesc struct {
|
||||||
|
ExtendedType Message // nil pointer to the type that is being extended
|
||||||
|
ExtensionType interface{} // nil pointer to the extension type
|
||||||
|
Field int32 // field number
|
||||||
|
Name string // fully-qualified name of extension, for text formatting
|
||||||
|
Tag string // protobuf tag style
|
||||||
|
Filename string // name of the file in which the extension is defined
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ed *ExtensionDesc) repeated() bool {
|
||||||
|
t := reflect.TypeOf(ed.ExtensionType)
|
||||||
|
return t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension represents an extension in a message.
|
||||||
|
type Extension struct {
|
||||||
|
// When an extension is stored in a message using SetExtension
|
||||||
|
// only desc and value are set. When the message is marshaled
|
||||||
|
// enc will be set to the encoded form of the message.
|
||||||
|
//
|
||||||
|
// When a message is unmarshaled and contains extensions, each
|
||||||
|
// extension will have only enc set. When such an extension is
|
||||||
|
// accessed using GetExtension (or GetExtensions) desc and value
|
||||||
|
// will be set.
|
||||||
|
desc *ExtensionDesc
|
||||||
|
value interface{}
|
||||||
|
enc []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRawExtension is for testing only.
|
||||||
|
func SetRawExtension(base Message, id int32, b []byte) {
|
||||||
|
epb, ok := extendable(base)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
extmap := epb.extensionsWrite()
|
||||||
|
extmap[id] = Extension{enc: b}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isExtensionField returns true iff the given field number is in an extension range.
|
||||||
|
func isExtensionField(pb extendableProto, field int32) bool {
|
||||||
|
for _, er := range pb.ExtensionRangeArray() {
|
||||||
|
if er.Start <= field && field <= er.End {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkExtensionTypes checks that the given extension is valid for pb.
|
||||||
|
func checkExtensionTypes(pb extendableProto, extension *ExtensionDesc) error {
|
||||||
|
var pbi interface{} = pb
|
||||||
|
// Check the extended type.
|
||||||
|
if ea, ok := pbi.(extensionAdapter); ok {
|
||||||
|
pbi = ea.extendableProtoV1
|
||||||
|
}
|
||||||
|
if a, b := reflect.TypeOf(pbi), reflect.TypeOf(extension.ExtendedType); a != b {
|
||||||
|
return errors.New("proto: bad extended type; " + b.String() + " does not extend " + a.String())
|
||||||
|
}
|
||||||
|
// Check the range.
|
||||||
|
if !isExtensionField(pb, extension.Field) {
|
||||||
|
return errors.New("proto: bad extension number; not in declared ranges")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// extPropKey is sufficient to uniquely identify an extension.
|
||||||
|
type extPropKey struct {
|
||||||
|
base reflect.Type
|
||||||
|
field int32
|
||||||
|
}
|
||||||
|
|
||||||
|
var extProp = struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[extPropKey]*Properties
|
||||||
|
}{
|
||||||
|
m: make(map[extPropKey]*Properties),
|
||||||
|
}
|
||||||
|
|
||||||
|
func extensionProperties(ed *ExtensionDesc) *Properties {
|
||||||
|
key := extPropKey{base: reflect.TypeOf(ed.ExtendedType), field: ed.Field}
|
||||||
|
|
||||||
|
extProp.RLock()
|
||||||
|
if prop, ok := extProp.m[key]; ok {
|
||||||
|
extProp.RUnlock()
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
extProp.RUnlock()
|
||||||
|
|
||||||
|
extProp.Lock()
|
||||||
|
defer extProp.Unlock()
|
||||||
|
// Check again.
|
||||||
|
if prop, ok := extProp.m[key]; ok {
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
prop := new(Properties)
|
||||||
|
prop.Init(reflect.TypeOf(ed.ExtensionType), "unknown_name", ed.Tag, nil)
|
||||||
|
extProp.m[key] = prop
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||||
|
func encodeExtensions(e *XXX_InternalExtensions) error {
|
||||||
|
m, mu := e.extensionsRead()
|
||||||
|
if m == nil {
|
||||||
|
return nil // fast path
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
return encodeExtensionsMap(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encode encodes any unmarshaled (unencoded) extensions in e.
|
||||||
|
func encodeExtensionsMap(m map[int32]Extension) error {
|
||||||
|
for k, e := range m {
|
||||||
|
if e.value == nil || e.desc == nil {
|
||||||
|
// Extension is only in its encoded form.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't skip extensions that have an encoded form set,
|
||||||
|
// because the extension value may have been mutated after
|
||||||
|
// the last time this function was called.
|
||||||
|
|
||||||
|
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||||
|
props := extensionProperties(e.desc)
|
||||||
|
|
||||||
|
p := NewBuffer(nil)
|
||||||
|
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||||
|
// Pass a *T with a zero field and hope it all works out.
|
||||||
|
x := reflect.New(et)
|
||||||
|
x.Elem().Set(reflect.ValueOf(e.value))
|
||||||
|
if err := props.enc(p, props, toStructPointer(x)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
e.enc = p.buf
|
||||||
|
m[k] = e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extensionsSize(e *XXX_InternalExtensions) (n int) {
|
||||||
|
m, mu := e.extensionsRead()
|
||||||
|
if m == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
return extensionsMapSize(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extensionsMapSize(m map[int32]Extension) (n int) {
|
||||||
|
for _, e := range m {
|
||||||
|
if e.value == nil || e.desc == nil {
|
||||||
|
// Extension is only in its encoded form.
|
||||||
|
n += len(e.enc)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't skip extensions that have an encoded form set,
|
||||||
|
// because the extension value may have been mutated after
|
||||||
|
// the last time this function was called.
|
||||||
|
|
||||||
|
et := reflect.TypeOf(e.desc.ExtensionType)
|
||||||
|
props := extensionProperties(e.desc)
|
||||||
|
|
||||||
|
// If e.value has type T, the encoder expects a *struct{ X T }.
|
||||||
|
// Pass a *T with a zero field and hope it all works out.
|
||||||
|
x := reflect.New(et)
|
||||||
|
x.Elem().Set(reflect.ValueOf(e.value))
|
||||||
|
n += props.size(props, toStructPointer(x))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasExtension returns whether the given extension is present in pb.
|
||||||
|
func HasExtension(pb Message, extension *ExtensionDesc) bool {
|
||||||
|
// TODO: Check types, field numbers, etc.?
|
||||||
|
epb, ok := extendable(pb)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
extmap, mu := epb.extensionsRead()
|
||||||
|
if extmap == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
_, ok = extmap[extension.Field]
|
||||||
|
mu.Unlock()
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearExtension removes the given extension from pb.
|
||||||
|
func ClearExtension(pb Message, extension *ExtensionDesc) {
|
||||||
|
epb, ok := extendable(pb)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// TODO: Check types, field numbers, etc.?
|
||||||
|
extmap := epb.extensionsWrite()
|
||||||
|
delete(extmap, extension.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtension parses and returns the given extension of pb.
|
||||||
|
// If the extension is not present and has no default value it returns ErrMissingExtension.
|
||||||
|
func GetExtension(pb Message, extension *ExtensionDesc) (interface{}, error) {
|
||||||
|
epb, ok := extendable(pb)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("proto: not an extendable proto")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
emap, mu := epb.extensionsRead()
|
||||||
|
if emap == nil {
|
||||||
|
return defaultExtensionValue(extension)
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
e, ok := emap[extension.Field]
|
||||||
|
if !ok {
|
||||||
|
// defaultExtensionValue returns the default value or
|
||||||
|
// ErrMissingExtension if there is no default.
|
||||||
|
return defaultExtensionValue(extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
if e.value != nil {
|
||||||
|
// Already decoded. Check the descriptor, though.
|
||||||
|
if e.desc != extension {
|
||||||
|
// This shouldn't happen. If it does, it means that
|
||||||
|
// GetExtension was called twice with two different
|
||||||
|
// descriptors with the same field number.
|
||||||
|
return nil, errors.New("proto: descriptor conflict")
|
||||||
|
}
|
||||||
|
return e.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v, err := decodeExtension(e.enc, extension)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remember the decoded version and drop the encoded version.
|
||||||
|
// That way it is safe to mutate what we return.
|
||||||
|
e.value = v
|
||||||
|
e.desc = extension
|
||||||
|
e.enc = nil
|
||||||
|
emap[extension.Field] = e
|
||||||
|
return e.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// defaultExtensionValue returns the default value for extension.
|
||||||
|
// If no default for an extension is defined ErrMissingExtension is returned.
|
||||||
|
func defaultExtensionValue(extension *ExtensionDesc) (interface{}, error) {
|
||||||
|
t := reflect.TypeOf(extension.ExtensionType)
|
||||||
|
props := extensionProperties(extension)
|
||||||
|
|
||||||
|
sf, _, err := fieldDefault(t, props)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if sf == nil || sf.value == nil {
|
||||||
|
// There is no default value.
|
||||||
|
return nil, ErrMissingExtension
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Ptr {
|
||||||
|
// We do not need to return a Ptr, we can directly return sf.value.
|
||||||
|
return sf.value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to return an interface{} that is a pointer to sf.value.
|
||||||
|
value := reflect.New(t).Elem()
|
||||||
|
value.Set(reflect.New(value.Type().Elem()))
|
||||||
|
if sf.kind == reflect.Int32 {
|
||||||
|
// We may have an int32 or an enum, but the underlying data is int32.
|
||||||
|
// Since we can't set an int32 into a non int32 reflect.value directly
|
||||||
|
// set it as a int32.
|
||||||
|
value.Elem().SetInt(int64(sf.value.(int32)))
|
||||||
|
} else {
|
||||||
|
value.Elem().Set(reflect.ValueOf(sf.value))
|
||||||
|
}
|
||||||
|
return value.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeExtension decodes an extension encoded in b.
|
||||||
|
func decodeExtension(b []byte, extension *ExtensionDesc) (interface{}, error) {
|
||||||
|
o := NewBuffer(b)
|
||||||
|
|
||||||
|
t := reflect.TypeOf(extension.ExtensionType)
|
||||||
|
|
||||||
|
props := extensionProperties(extension)
|
||||||
|
|
||||||
|
// t is a pointer to a struct, pointer to basic type or a slice.
|
||||||
|
// Allocate a "field" to store the pointer/slice itself; the
|
||||||
|
// pointer/slice will be stored here. We pass
|
||||||
|
// the address of this field to props.dec.
|
||||||
|
// This passes a zero field and a *t and lets props.dec
|
||||||
|
// interpret it as a *struct{ x t }.
|
||||||
|
value := reflect.New(t).Elem()
|
||||||
|
|
||||||
|
for {
|
||||||
|
// Discard wire type and field number varint. It isn't needed.
|
||||||
|
if _, err := o.DecodeVarint(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := props.dec(o, props, toStructPointer(value.Addr())); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if o.index >= len(o.buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value.Interface(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetExtensions returns a slice of the extensions present in pb that are also listed in es.
|
||||||
|
// The returned slice has the same length as es; missing extensions will appear as nil elements.
|
||||||
|
func GetExtensions(pb Message, es []*ExtensionDesc) (extensions []interface{}, err error) {
|
||||||
|
epb, ok := extendable(pb)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("proto: not an extendable proto")
|
||||||
|
}
|
||||||
|
extensions = make([]interface{}, len(es))
|
||||||
|
for i, e := range es {
|
||||||
|
extensions[i], err = GetExtension(epb, e)
|
||||||
|
if err == ErrMissingExtension {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtensionDescs returns a new slice containing pb's extension descriptors, in undefined order.
|
||||||
|
// For non-registered extensions, ExtensionDescs returns an incomplete descriptor containing
|
||||||
|
// just the Field field, which defines the extension's field number.
|
||||||
|
func ExtensionDescs(pb Message) ([]*ExtensionDesc, error) {
|
||||||
|
epb, ok := extendable(pb)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("proto: %T is not an extendable proto.Message", pb)
|
||||||
|
}
|
||||||
|
registeredExtensions := RegisteredExtensions(pb)
|
||||||
|
|
||||||
|
emap, mu := epb.extensionsRead()
|
||||||
|
if emap == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
extensions := make([]*ExtensionDesc, 0, len(emap))
|
||||||
|
for extid, e := range emap {
|
||||||
|
desc := e.desc
|
||||||
|
if desc == nil {
|
||||||
|
desc = registeredExtensions[extid]
|
||||||
|
if desc == nil {
|
||||||
|
desc = &ExtensionDesc{Field: extid}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions = append(extensions, desc)
|
||||||
|
}
|
||||||
|
return extensions, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetExtension sets the specified extension of pb to the specified value.
|
||||||
|
func SetExtension(pb Message, extension *ExtensionDesc, value interface{}) error {
|
||||||
|
epb, ok := extendable(pb)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("proto: not an extendable proto")
|
||||||
|
}
|
||||||
|
if err := checkExtensionTypes(epb, extension); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
typ := reflect.TypeOf(extension.ExtensionType)
|
||||||
|
if typ != reflect.TypeOf(value) {
|
||||||
|
return errors.New("proto: bad extension value type")
|
||||||
|
}
|
||||||
|
// nil extension values need to be caught early, because the
|
||||||
|
// encoder can't distinguish an ErrNil due to a nil extension
|
||||||
|
// from an ErrNil due to a missing field. Extensions are
|
||||||
|
// always optional, so the encoder would just swallow the error
|
||||||
|
// and drop all the extensions from the encoded message.
|
||||||
|
if reflect.ValueOf(value).IsNil() {
|
||||||
|
return fmt.Errorf("proto: SetExtension called with nil value of type %T", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
extmap := epb.extensionsWrite()
|
||||||
|
extmap[extension.Field] = Extension{desc: extension, value: value}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearAllExtensions clears all extensions from pb.
|
||||||
|
func ClearAllExtensions(pb Message) {
|
||||||
|
epb, ok := extendable(pb)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m := epb.extensionsWrite()
|
||||||
|
for k := range m {
|
||||||
|
delete(m, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A global registry of extensions.
|
||||||
|
// The generated code will register the generated descriptors by calling RegisterExtension.
|
||||||
|
|
||||||
|
var extensionMaps = make(map[reflect.Type]map[int32]*ExtensionDesc)
|
||||||
|
|
||||||
|
// RegisterExtension is called from the generated code.
|
||||||
|
func RegisterExtension(desc *ExtensionDesc) {
|
||||||
|
st := reflect.TypeOf(desc.ExtendedType).Elem()
|
||||||
|
m := extensionMaps[st]
|
||||||
|
if m == nil {
|
||||||
|
m = make(map[int32]*ExtensionDesc)
|
||||||
|
extensionMaps[st] = m
|
||||||
|
}
|
||||||
|
if _, ok := m[desc.Field]; ok {
|
||||||
|
panic("proto: duplicate extension registered: " + st.String() + " " + strconv.Itoa(int(desc.Field)))
|
||||||
|
}
|
||||||
|
m[desc.Field] = desc
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisteredExtensions returns a map of the registered extensions of a
|
||||||
|
// protocol buffer struct, indexed by the extension number.
|
||||||
|
// The argument pb should be a nil pointer to the struct type.
|
||||||
|
func RegisteredExtensions(pb Message) map[int32]*ExtensionDesc {
|
||||||
|
return extensionMaps[reflect.TypeOf(pb).Elem()]
|
||||||
|
}
|
897
vendor/github.com/golang/protobuf/proto/lib.go
generated
vendored
Normal file
897
vendor/github.com/golang/protobuf/proto/lib.go
generated
vendored
Normal file
@ -0,0 +1,897 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package proto converts data structures to and from the wire format of
|
||||||
|
protocol buffers. It works in concert with the Go source code generated
|
||||||
|
for .proto files by the protocol compiler.
|
||||||
|
|
||||||
|
A summary of the properties of the protocol buffer interface
|
||||||
|
for a protocol buffer variable v:
|
||||||
|
|
||||||
|
- Names are turned from camel_case to CamelCase for export.
|
||||||
|
- There are no methods on v to set fields; just treat
|
||||||
|
them as structure fields.
|
||||||
|
- There are getters that return a field's value if set,
|
||||||
|
and return the field's default value if unset.
|
||||||
|
The getters work even if the receiver is a nil message.
|
||||||
|
- The zero value for a struct is its correct initialization state.
|
||||||
|
All desired fields must be set before marshaling.
|
||||||
|
- A Reset() method will restore a protobuf struct to its zero state.
|
||||||
|
- Non-repeated fields are pointers to the values; nil means unset.
|
||||||
|
That is, optional or required field int32 f becomes F *int32.
|
||||||
|
- Repeated fields are slices.
|
||||||
|
- Helper functions are available to aid the setting of fields.
|
||||||
|
msg.Foo = proto.String("hello") // set field
|
||||||
|
- Constants are defined to hold the default values of all fields that
|
||||||
|
have them. They have the form Default_StructName_FieldName.
|
||||||
|
Because the getter methods handle defaulted values,
|
||||||
|
direct use of these constants should be rare.
|
||||||
|
- Enums are given type names and maps from names to values.
|
||||||
|
Enum values are prefixed by the enclosing message's name, or by the
|
||||||
|
enum's type name if it is a top-level enum. Enum types have a String
|
||||||
|
method, and a Enum method to assist in message construction.
|
||||||
|
- Nested messages, groups and enums have type names prefixed with the name of
|
||||||
|
the surrounding message type.
|
||||||
|
- Extensions are given descriptor names that start with E_,
|
||||||
|
followed by an underscore-delimited list of the nested messages
|
||||||
|
that contain it (if any) followed by the CamelCased name of the
|
||||||
|
extension field itself. HasExtension, ClearExtension, GetExtension
|
||||||
|
and SetExtension are functions for manipulating extensions.
|
||||||
|
- Oneof field sets are given a single field in their message,
|
||||||
|
with distinguished wrapper types for each possible field value.
|
||||||
|
- Marshal and Unmarshal are functions to encode and decode the wire format.
|
||||||
|
|
||||||
|
When the .proto file specifies `syntax="proto3"`, there are some differences:
|
||||||
|
|
||||||
|
- Non-repeated fields of non-message type are values instead of pointers.
|
||||||
|
- Enum types do not get an Enum method.
|
||||||
|
|
||||||
|
The simplest way to describe this is to see an example.
|
||||||
|
Given file test.proto, containing
|
||||||
|
|
||||||
|
package example;
|
||||||
|
|
||||||
|
enum FOO { X = 17; }
|
||||||
|
|
||||||
|
message Test {
|
||||||
|
required string label = 1;
|
||||||
|
optional int32 type = 2 [default=77];
|
||||||
|
repeated int64 reps = 3;
|
||||||
|
optional group OptionalGroup = 4 {
|
||||||
|
required string RequiredField = 5;
|
||||||
|
}
|
||||||
|
oneof union {
|
||||||
|
int32 number = 6;
|
||||||
|
string name = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The resulting file, test.pb.go, is:
|
||||||
|
|
||||||
|
package example
|
||||||
|
|
||||||
|
import proto "github.com/golang/protobuf/proto"
|
||||||
|
import math "math"
|
||||||
|
|
||||||
|
type FOO int32
|
||||||
|
const (
|
||||||
|
FOO_X FOO = 17
|
||||||
|
)
|
||||||
|
var FOO_name = map[int32]string{
|
||||||
|
17: "X",
|
||||||
|
}
|
||||||
|
var FOO_value = map[string]int32{
|
||||||
|
"X": 17,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x FOO) Enum() *FOO {
|
||||||
|
p := new(FOO)
|
||||||
|
*p = x
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
func (x FOO) String() string {
|
||||||
|
return proto.EnumName(FOO_name, int32(x))
|
||||||
|
}
|
||||||
|
func (x *FOO) UnmarshalJSON(data []byte) error {
|
||||||
|
value, err := proto.UnmarshalJSONEnum(FOO_value, data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*x = FOO(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Test struct {
|
||||||
|
Label *string `protobuf:"bytes,1,req,name=label" json:"label,omitempty"`
|
||||||
|
Type *int32 `protobuf:"varint,2,opt,name=type,def=77" json:"type,omitempty"`
|
||||||
|
Reps []int64 `protobuf:"varint,3,rep,name=reps" json:"reps,omitempty"`
|
||||||
|
Optionalgroup *Test_OptionalGroup `protobuf:"group,4,opt,name=OptionalGroup" json:"optionalgroup,omitempty"`
|
||||||
|
// Types that are valid to be assigned to Union:
|
||||||
|
// *Test_Number
|
||||||
|
// *Test_Name
|
||||||
|
Union isTest_Union `protobuf_oneof:"union"`
|
||||||
|
XXX_unrecognized []byte `json:"-"`
|
||||||
|
}
|
||||||
|
func (m *Test) Reset() { *m = Test{} }
|
||||||
|
func (m *Test) String() string { return proto.CompactTextString(m) }
|
||||||
|
func (*Test) ProtoMessage() {}
|
||||||
|
|
||||||
|
type isTest_Union interface {
|
||||||
|
isTest_Union()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Test_Number struct {
|
||||||
|
Number int32 `protobuf:"varint,6,opt,name=number"`
|
||||||
|
}
|
||||||
|
type Test_Name struct {
|
||||||
|
Name string `protobuf:"bytes,7,opt,name=name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Test_Number) isTest_Union() {}
|
||||||
|
func (*Test_Name) isTest_Union() {}
|
||||||
|
|
||||||
|
func (m *Test) GetUnion() isTest_Union {
|
||||||
|
if m != nil {
|
||||||
|
return m.Union
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
const Default_Test_Type int32 = 77
|
||||||
|
|
||||||
|
func (m *Test) GetLabel() string {
|
||||||
|
if m != nil && m.Label != nil {
|
||||||
|
return *m.Label
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Test) GetType() int32 {
|
||||||
|
if m != nil && m.Type != nil {
|
||||||
|
return *m.Type
|
||||||
|
}
|
||||||
|
return Default_Test_Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Test) GetOptionalgroup() *Test_OptionalGroup {
|
||||||
|
if m != nil {
|
||||||
|
return m.Optionalgroup
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Test_OptionalGroup struct {
|
||||||
|
RequiredField *string `protobuf:"bytes,5,req" json:"RequiredField,omitempty"`
|
||||||
|
}
|
||||||
|
func (m *Test_OptionalGroup) Reset() { *m = Test_OptionalGroup{} }
|
||||||
|
func (m *Test_OptionalGroup) String() string { return proto.CompactTextString(m) }
|
||||||
|
|
||||||
|
func (m *Test_OptionalGroup) GetRequiredField() string {
|
||||||
|
if m != nil && m.RequiredField != nil {
|
||||||
|
return *m.RequiredField
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Test) GetNumber() int32 {
|
||||||
|
if x, ok := m.GetUnion().(*Test_Number); ok {
|
||||||
|
return x.Number
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Test) GetName() string {
|
||||||
|
if x, ok := m.GetUnion().(*Test_Name); ok {
|
||||||
|
return x.Name
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterEnum("example.FOO", FOO_name, FOO_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
To create and play with a Test object:
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/golang/protobuf/proto"
|
||||||
|
pb "./example.pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
test := &pb.Test{
|
||||||
|
Label: proto.String("hello"),
|
||||||
|
Type: proto.Int32(17),
|
||||||
|
Reps: []int64{1, 2, 3},
|
||||||
|
Optionalgroup: &pb.Test_OptionalGroup{
|
||||||
|
RequiredField: proto.String("good bye"),
|
||||||
|
},
|
||||||
|
Union: &pb.Test_Name{"fred"},
|
||||||
|
}
|
||||||
|
data, err := proto.Marshal(test)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("marshaling error: ", err)
|
||||||
|
}
|
||||||
|
newTest := &pb.Test{}
|
||||||
|
err = proto.Unmarshal(data, newTest)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("unmarshaling error: ", err)
|
||||||
|
}
|
||||||
|
// Now test and newTest contain the same data.
|
||||||
|
if test.GetLabel() != newTest.GetLabel() {
|
||||||
|
log.Fatalf("data mismatch %q != %q", test.GetLabel(), newTest.GetLabel())
|
||||||
|
}
|
||||||
|
// Use a type switch to determine which oneof was set.
|
||||||
|
switch u := test.Union.(type) {
|
||||||
|
case *pb.Test_Number: // u.Number contains the number.
|
||||||
|
case *pb.Test_Name: // u.Name contains the string.
|
||||||
|
}
|
||||||
|
// etc.
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message is implemented by generated protocol buffer messages.
|
||||||
|
type Message interface {
|
||||||
|
Reset()
|
||||||
|
String() string
|
||||||
|
ProtoMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats records allocation details about the protocol buffer encoders
|
||||||
|
// and decoders. Useful for tuning the library itself.
|
||||||
|
type Stats struct {
|
||||||
|
Emalloc uint64 // mallocs in encode
|
||||||
|
Dmalloc uint64 // mallocs in decode
|
||||||
|
Encode uint64 // number of encodes
|
||||||
|
Decode uint64 // number of decodes
|
||||||
|
Chit uint64 // number of cache hits
|
||||||
|
Cmiss uint64 // number of cache misses
|
||||||
|
Size uint64 // number of sizes
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set to true to enable stats collection.
|
||||||
|
const collectStats = false
|
||||||
|
|
||||||
|
var stats Stats
|
||||||
|
|
||||||
|
// GetStats returns a copy of the global Stats structure.
|
||||||
|
func GetStats() Stats { return stats }
|
||||||
|
|
||||||
|
// A Buffer is a buffer manager for marshaling and unmarshaling
|
||||||
|
// protocol buffers. It may be reused between invocations to
|
||||||
|
// reduce memory usage. It is not necessary to use a Buffer;
|
||||||
|
// the global functions Marshal and Unmarshal create a
|
||||||
|
// temporary Buffer and are fine for most applications.
|
||||||
|
type Buffer struct {
|
||||||
|
buf []byte // encode/decode byte stream
|
||||||
|
index int // read point
|
||||||
|
|
||||||
|
// pools of basic types to amortize allocation.
|
||||||
|
bools []bool
|
||||||
|
uint32s []uint32
|
||||||
|
uint64s []uint64
|
||||||
|
|
||||||
|
// extra pools, only used with pointer_reflect.go
|
||||||
|
int32s []int32
|
||||||
|
int64s []int64
|
||||||
|
float32s []float32
|
||||||
|
float64s []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuffer allocates a new Buffer and initializes its internal data to
|
||||||
|
// the contents of the argument slice.
|
||||||
|
func NewBuffer(e []byte) *Buffer {
|
||||||
|
return &Buffer{buf: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset resets the Buffer, ready for marshaling a new protocol buffer.
|
||||||
|
func (p *Buffer) Reset() {
|
||||||
|
p.buf = p.buf[0:0] // for reading/writing
|
||||||
|
p.index = 0 // for reading
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBuf replaces the internal buffer with the slice,
|
||||||
|
// ready for unmarshaling the contents of the slice.
|
||||||
|
func (p *Buffer) SetBuf(s []byte) {
|
||||||
|
p.buf = s
|
||||||
|
p.index = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the contents of the Buffer.
|
||||||
|
func (p *Buffer) Bytes() []byte { return p.buf }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Helper routines for simplifying the creation of optional fields of basic type.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Bool is a helper routine that allocates a new bool value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Bool(v bool) *bool {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int32 is a helper routine that allocates a new int32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Int32(v int32) *int32 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int is a helper routine that allocates a new int32 value
|
||||||
|
// to store v and returns a pointer to it, but unlike Int32
|
||||||
|
// its argument value is an int.
|
||||||
|
func Int(v int) *int32 {
|
||||||
|
p := new(int32)
|
||||||
|
*p = int32(v)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 is a helper routine that allocates a new int64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Int64(v int64) *int64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float32 is a helper routine that allocates a new float32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Float32(v float32) *float32 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 is a helper routine that allocates a new float64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Float64(v float64) *float64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint32 is a helper routine that allocates a new uint32 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Uint32(v uint32) *uint32 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 is a helper routine that allocates a new uint64 value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func Uint64(v uint64) *uint64 {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is a helper routine that allocates a new string value
|
||||||
|
// to store v and returns a pointer to it.
|
||||||
|
func String(v string) *string {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumName is a helper function to simplify printing protocol buffer enums
|
||||||
|
// by name. Given an enum map and a value, it returns a useful string.
|
||||||
|
func EnumName(m map[int32]string, v int32) string {
|
||||||
|
s, ok := m[v]
|
||||||
|
if ok {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return strconv.Itoa(int(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSONEnum is a helper function to simplify recovering enum int values
|
||||||
|
// from their JSON-encoded representation. Given a map from the enum's symbolic
|
||||||
|
// names to its int values, and a byte buffer containing the JSON-encoded
|
||||||
|
// value, it returns an int32 that can be cast to the enum type by the caller.
|
||||||
|
//
|
||||||
|
// The function can deal with both JSON representations, numeric and symbolic.
|
||||||
|
func UnmarshalJSONEnum(m map[string]int32, data []byte, enumName string) (int32, error) {
|
||||||
|
if data[0] == '"' {
|
||||||
|
// New style: enums are strings.
|
||||||
|
var repr string
|
||||||
|
if err := json.Unmarshal(data, &repr); err != nil {
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
val, ok := m[repr]
|
||||||
|
if !ok {
|
||||||
|
return 0, fmt.Errorf("unrecognized enum %s value %q", enumName, repr)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
// Old style: enums are ints.
|
||||||
|
var val int32
|
||||||
|
if err := json.Unmarshal(data, &val); err != nil {
|
||||||
|
return 0, fmt.Errorf("cannot unmarshal %#q into enum %s", data, enumName)
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DebugPrint dumps the encoded data in b in a debugging format with a header
|
||||||
|
// including the string s. Used in testing but made available for general debugging.
|
||||||
|
func (p *Buffer) DebugPrint(s string, b []byte) {
|
||||||
|
var u uint64
|
||||||
|
|
||||||
|
obuf := p.buf
|
||||||
|
index := p.index
|
||||||
|
p.buf = b
|
||||||
|
p.index = 0
|
||||||
|
depth := 0
|
||||||
|
|
||||||
|
fmt.Printf("\n--- %s ---\n", s)
|
||||||
|
|
||||||
|
out:
|
||||||
|
for {
|
||||||
|
for i := 0; i < depth; i++ {
|
||||||
|
fmt.Print(" ")
|
||||||
|
}
|
||||||
|
|
||||||
|
index := p.index
|
||||||
|
if index == len(p.buf) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
op, err := p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: fetching op err %v\n", index, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
tag := op >> 3
|
||||||
|
wire := op & 7
|
||||||
|
|
||||||
|
switch wire {
|
||||||
|
default:
|
||||||
|
fmt.Printf("%3d: t=%3d unknown wire=%d\n",
|
||||||
|
index, tag, wire)
|
||||||
|
break out
|
||||||
|
|
||||||
|
case WireBytes:
|
||||||
|
var r []byte
|
||||||
|
|
||||||
|
r, err = p.DecodeRawBytes(false)
|
||||||
|
if err != nil {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d bytes [%d]", index, tag, len(r))
|
||||||
|
if len(r) <= 6 {
|
||||||
|
for i := 0; i < len(r); i++ {
|
||||||
|
fmt.Printf(" %.2x", r[i])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
fmt.Printf(" %.2x", r[i])
|
||||||
|
}
|
||||||
|
fmt.Printf(" ..")
|
||||||
|
for i := len(r) - 3; i < len(r); i++ {
|
||||||
|
fmt.Printf(" %.2x", r[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
|
||||||
|
case WireFixed32:
|
||||||
|
u, err = p.DecodeFixed32()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d fix32 err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d fix32 %d\n", index, tag, u)
|
||||||
|
|
||||||
|
case WireFixed64:
|
||||||
|
u, err = p.DecodeFixed64()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d fix64 err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d fix64 %d\n", index, tag, u)
|
||||||
|
|
||||||
|
case WireVarint:
|
||||||
|
u, err = p.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%3d: t=%3d varint err %v\n", index, tag, err)
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
fmt.Printf("%3d: t=%3d varint %d\n", index, tag, u)
|
||||||
|
|
||||||
|
case WireStartGroup:
|
||||||
|
fmt.Printf("%3d: t=%3d start\n", index, tag)
|
||||||
|
depth++
|
||||||
|
|
||||||
|
case WireEndGroup:
|
||||||
|
depth--
|
||||||
|
fmt.Printf("%3d: t=%3d end\n", index, tag)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if depth != 0 {
|
||||||
|
fmt.Printf("%3d: start-end not balanced %d\n", p.index, depth)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
|
||||||
|
p.buf = obuf
|
||||||
|
p.index = index
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDefaults sets unset protocol buffer fields to their default values.
|
||||||
|
// It only modifies fields that are both unset and have defined defaults.
|
||||||
|
// It recursively sets default values in any non-nil sub-messages.
|
||||||
|
func SetDefaults(pb Message) {
|
||||||
|
setDefaults(reflect.ValueOf(pb), true, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// v is a pointer to a struct.
|
||||||
|
func setDefaults(v reflect.Value, recur, zeros bool) {
|
||||||
|
v = v.Elem()
|
||||||
|
|
||||||
|
defaultMu.RLock()
|
||||||
|
dm, ok := defaults[v.Type()]
|
||||||
|
defaultMu.RUnlock()
|
||||||
|
if !ok {
|
||||||
|
dm = buildDefaultMessage(v.Type())
|
||||||
|
defaultMu.Lock()
|
||||||
|
defaults[v.Type()] = dm
|
||||||
|
defaultMu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, sf := range dm.scalars {
|
||||||
|
f := v.Field(sf.index)
|
||||||
|
if !f.IsNil() {
|
||||||
|
// field already set
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
dv := sf.value
|
||||||
|
if dv == nil && !zeros {
|
||||||
|
// no explicit default, and don't want to set zeros
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fptr := f.Addr().Interface() // **T
|
||||||
|
// TODO: Consider batching the allocations we do here.
|
||||||
|
switch sf.kind {
|
||||||
|
case reflect.Bool:
|
||||||
|
b := new(bool)
|
||||||
|
if dv != nil {
|
||||||
|
*b = dv.(bool)
|
||||||
|
}
|
||||||
|
*(fptr.(**bool)) = b
|
||||||
|
case reflect.Float32:
|
||||||
|
f := new(float32)
|
||||||
|
if dv != nil {
|
||||||
|
*f = dv.(float32)
|
||||||
|
}
|
||||||
|
*(fptr.(**float32)) = f
|
||||||
|
case reflect.Float64:
|
||||||
|
f := new(float64)
|
||||||
|
if dv != nil {
|
||||||
|
*f = dv.(float64)
|
||||||
|
}
|
||||||
|
*(fptr.(**float64)) = f
|
||||||
|
case reflect.Int32:
|
||||||
|
// might be an enum
|
||||||
|
if ft := f.Type(); ft != int32PtrType {
|
||||||
|
// enum
|
||||||
|
f.Set(reflect.New(ft.Elem()))
|
||||||
|
if dv != nil {
|
||||||
|
f.Elem().SetInt(int64(dv.(int32)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// int32 field
|
||||||
|
i := new(int32)
|
||||||
|
if dv != nil {
|
||||||
|
*i = dv.(int32)
|
||||||
|
}
|
||||||
|
*(fptr.(**int32)) = i
|
||||||
|
}
|
||||||
|
case reflect.Int64:
|
||||||
|
i := new(int64)
|
||||||
|
if dv != nil {
|
||||||
|
*i = dv.(int64)
|
||||||
|
}
|
||||||
|
*(fptr.(**int64)) = i
|
||||||
|
case reflect.String:
|
||||||
|
s := new(string)
|
||||||
|
if dv != nil {
|
||||||
|
*s = dv.(string)
|
||||||
|
}
|
||||||
|
*(fptr.(**string)) = s
|
||||||
|
case reflect.Uint8:
|
||||||
|
// exceptional case: []byte
|
||||||
|
var b []byte
|
||||||
|
if dv != nil {
|
||||||
|
db := dv.([]byte)
|
||||||
|
b = make([]byte, len(db))
|
||||||
|
copy(b, db)
|
||||||
|
} else {
|
||||||
|
b = []byte{}
|
||||||
|
}
|
||||||
|
*(fptr.(*[]byte)) = b
|
||||||
|
case reflect.Uint32:
|
||||||
|
u := new(uint32)
|
||||||
|
if dv != nil {
|
||||||
|
*u = dv.(uint32)
|
||||||
|
}
|
||||||
|
*(fptr.(**uint32)) = u
|
||||||
|
case reflect.Uint64:
|
||||||
|
u := new(uint64)
|
||||||
|
if dv != nil {
|
||||||
|
*u = dv.(uint64)
|
||||||
|
}
|
||||||
|
*(fptr.(**uint64)) = u
|
||||||
|
default:
|
||||||
|
log.Printf("proto: can't set default for field %v (sf.kind=%v)", f, sf.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, ni := range dm.nested {
|
||||||
|
f := v.Field(ni)
|
||||||
|
// f is *T or []*T or map[T]*T
|
||||||
|
switch f.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if f.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
setDefaults(f, recur, zeros)
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
for i := 0; i < f.Len(); i++ {
|
||||||
|
e := f.Index(i)
|
||||||
|
if e.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
setDefaults(e, recur, zeros)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
for _, k := range f.MapKeys() {
|
||||||
|
e := f.MapIndex(k)
|
||||||
|
if e.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
setDefaults(e, recur, zeros)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// defaults maps a protocol buffer struct type to a slice of the fields,
|
||||||
|
// with its scalar fields set to their proto-declared non-zero default values.
|
||||||
|
defaultMu sync.RWMutex
|
||||||
|
defaults = make(map[reflect.Type]defaultMessage)
|
||||||
|
|
||||||
|
int32PtrType = reflect.TypeOf((*int32)(nil))
|
||||||
|
)
|
||||||
|
|
||||||
|
// defaultMessage represents information about the default values of a message.
|
||||||
|
type defaultMessage struct {
|
||||||
|
scalars []scalarField
|
||||||
|
nested []int // struct field index of nested messages
|
||||||
|
}
|
||||||
|
|
||||||
|
type scalarField struct {
|
||||||
|
index int // struct field index
|
||||||
|
kind reflect.Kind // element type (the T in *T or []T)
|
||||||
|
value interface{} // the proto-declared default value, or nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// t is a struct type.
|
||||||
|
func buildDefaultMessage(t reflect.Type) (dm defaultMessage) {
|
||||||
|
sprop := GetProperties(t)
|
||||||
|
for _, prop := range sprop.Prop {
|
||||||
|
fi, ok := sprop.decoderTags.get(prop.Tag)
|
||||||
|
if !ok {
|
||||||
|
// XXX_unrecognized
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ft := t.Field(fi).Type
|
||||||
|
|
||||||
|
sf, nested, err := fieldDefault(ft, prop)
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
log.Print(err)
|
||||||
|
case nested:
|
||||||
|
dm.nested = append(dm.nested, fi)
|
||||||
|
case sf != nil:
|
||||||
|
sf.index = fi
|
||||||
|
dm.scalars = append(dm.scalars, *sf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dm
|
||||||
|
}
|
||||||
|
|
||||||
|
// fieldDefault returns the scalarField for field type ft.
|
||||||
|
// sf will be nil if the field can not have a default.
|
||||||
|
// nestedMessage will be true if this is a nested message.
|
||||||
|
// Note that sf.index is not set on return.
|
||||||
|
func fieldDefault(ft reflect.Type, prop *Properties) (sf *scalarField, nestedMessage bool, err error) {
|
||||||
|
var canHaveDefault bool
|
||||||
|
switch ft.Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
if ft.Elem().Kind() == reflect.Struct {
|
||||||
|
nestedMessage = true
|
||||||
|
} else {
|
||||||
|
canHaveDefault = true // proto2 scalar field
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
switch ft.Elem().Kind() {
|
||||||
|
case reflect.Ptr:
|
||||||
|
nestedMessage = true // repeated message
|
||||||
|
case reflect.Uint8:
|
||||||
|
canHaveDefault = true // bytes field
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
if ft.Elem().Kind() == reflect.Ptr {
|
||||||
|
nestedMessage = true // map with message values
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !canHaveDefault {
|
||||||
|
if nestedMessage {
|
||||||
|
return nil, true, nil
|
||||||
|
}
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now know that ft is a pointer or slice.
|
||||||
|
sf = &scalarField{kind: ft.Elem().Kind()}
|
||||||
|
|
||||||
|
// scalar fields without defaults
|
||||||
|
if !prop.HasDefault {
|
||||||
|
return sf, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// a scalar field: either *T or []byte
|
||||||
|
switch ft.Elem().Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
x, err := strconv.ParseBool(prop.Default)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("proto: bad default bool %q: %v", prop.Default, err)
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
case reflect.Float32:
|
||||||
|
x, err := strconv.ParseFloat(prop.Default, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("proto: bad default float32 %q: %v", prop.Default, err)
|
||||||
|
}
|
||||||
|
sf.value = float32(x)
|
||||||
|
case reflect.Float64:
|
||||||
|
x, err := strconv.ParseFloat(prop.Default, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("proto: bad default float64 %q: %v", prop.Default, err)
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
case reflect.Int32:
|
||||||
|
x, err := strconv.ParseInt(prop.Default, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("proto: bad default int32 %q: %v", prop.Default, err)
|
||||||
|
}
|
||||||
|
sf.value = int32(x)
|
||||||
|
case reflect.Int64:
|
||||||
|
x, err := strconv.ParseInt(prop.Default, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("proto: bad default int64 %q: %v", prop.Default, err)
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
case reflect.String:
|
||||||
|
sf.value = prop.Default
|
||||||
|
case reflect.Uint8:
|
||||||
|
// []byte (not *uint8)
|
||||||
|
sf.value = []byte(prop.Default)
|
||||||
|
case reflect.Uint32:
|
||||||
|
x, err := strconv.ParseUint(prop.Default, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("proto: bad default uint32 %q: %v", prop.Default, err)
|
||||||
|
}
|
||||||
|
sf.value = uint32(x)
|
||||||
|
case reflect.Uint64:
|
||||||
|
x, err := strconv.ParseUint(prop.Default, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("proto: bad default uint64 %q: %v", prop.Default, err)
|
||||||
|
}
|
||||||
|
sf.value = x
|
||||||
|
default:
|
||||||
|
return nil, false, fmt.Errorf("proto: unhandled def kind %v", ft.Elem().Kind())
|
||||||
|
}
|
||||||
|
|
||||||
|
return sf, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map fields may have key types of non-float scalars, strings and enums.
|
||||||
|
// The easiest way to sort them in some deterministic order is to use fmt.
|
||||||
|
// If this turns out to be inefficient we can always consider other options,
|
||||||
|
// such as doing a Schwartzian transform.
|
||||||
|
|
||||||
|
func mapKeys(vs []reflect.Value) sort.Interface {
|
||||||
|
s := mapKeySorter{
|
||||||
|
vs: vs,
|
||||||
|
// default Less function: textual comparison
|
||||||
|
less: func(a, b reflect.Value) bool {
|
||||||
|
return fmt.Sprint(a.Interface()) < fmt.Sprint(b.Interface())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type specialization per https://developers.google.com/protocol-buffers/docs/proto#maps;
|
||||||
|
// numeric keys are sorted numerically.
|
||||||
|
if len(vs) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
switch vs[0].Kind() {
|
||||||
|
case reflect.Int32, reflect.Int64:
|
||||||
|
s.less = func(a, b reflect.Value) bool { return a.Int() < b.Int() }
|
||||||
|
case reflect.Uint32, reflect.Uint64:
|
||||||
|
s.less = func(a, b reflect.Value) bool { return a.Uint() < b.Uint() }
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapKeySorter struct {
|
||||||
|
vs []reflect.Value
|
||||||
|
less func(a, b reflect.Value) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s mapKeySorter) Len() int { return len(s.vs) }
|
||||||
|
func (s mapKeySorter) Swap(i, j int) { s.vs[i], s.vs[j] = s.vs[j], s.vs[i] }
|
||||||
|
func (s mapKeySorter) Less(i, j int) bool {
|
||||||
|
return s.less(s.vs[i], s.vs[j])
|
||||||
|
}
|
||||||
|
|
||||||
|
// isProto3Zero reports whether v is a zero proto3 value.
|
||||||
|
func isProto3Zero(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint32, reflect.Uint64:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.String:
|
||||||
|
return v.String() == ""
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProtoPackageIsVersion2 is referenced from generated protocol buffer files
|
||||||
|
// to assert that that code is compatible with this version of the proto package.
|
||||||
|
const ProtoPackageIsVersion2 = true
|
||||||
|
|
||||||
|
// ProtoPackageIsVersion1 is referenced from generated protocol buffer files
|
||||||
|
// to assert that that code is compatible with this version of the proto package.
|
||||||
|
const ProtoPackageIsVersion1 = true
|
311
vendor/github.com/golang/protobuf/proto/message_set.go
generated
vendored
Normal file
311
vendor/github.com/golang/protobuf/proto/message_set.go
generated
vendored
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Support for message sets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
// errNoMessageTypeID occurs when a protocol buffer does not have a message type ID.
|
||||||
|
// A message type ID is required for storing a protocol buffer in a message set.
|
||||||
|
var errNoMessageTypeID = errors.New("proto does not have a message type ID")
|
||||||
|
|
||||||
|
// The first two types (_MessageSet_Item and messageSet)
|
||||||
|
// model what the protocol compiler produces for the following protocol message:
|
||||||
|
// message MessageSet {
|
||||||
|
// repeated group Item = 1 {
|
||||||
|
// required int32 type_id = 2;
|
||||||
|
// required string message = 3;
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// That is the MessageSet wire format. We can't use a proto to generate these
|
||||||
|
// because that would introduce a circular dependency between it and this package.
|
||||||
|
|
||||||
|
type _MessageSet_Item struct {
|
||||||
|
TypeId *int32 `protobuf:"varint,2,req,name=type_id"`
|
||||||
|
Message []byte `protobuf:"bytes,3,req,name=message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type messageSet struct {
|
||||||
|
Item []*_MessageSet_Item `protobuf:"group,1,rep"`
|
||||||
|
XXX_unrecognized []byte
|
||||||
|
// TODO: caching?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure messageSet is a Message.
|
||||||
|
var _ Message = (*messageSet)(nil)
|
||||||
|
|
||||||
|
// messageTypeIder is an interface satisfied by a protocol buffer type
|
||||||
|
// that may be stored in a MessageSet.
|
||||||
|
type messageTypeIder interface {
|
||||||
|
MessageTypeId() int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *messageSet) find(pb Message) *_MessageSet_Item {
|
||||||
|
mti, ok := pb.(messageTypeIder)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id := mti.MessageTypeId()
|
||||||
|
for _, item := range ms.Item {
|
||||||
|
if *item.TypeId == id {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *messageSet) Has(pb Message) bool {
|
||||||
|
if ms.find(pb) != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *messageSet) Unmarshal(pb Message) error {
|
||||||
|
if item := ms.find(pb); item != nil {
|
||||||
|
return Unmarshal(item.Message, pb)
|
||||||
|
}
|
||||||
|
if _, ok := pb.(messageTypeIder); !ok {
|
||||||
|
return errNoMessageTypeID
|
||||||
|
}
|
||||||
|
return nil // TODO: return error instead?
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *messageSet) Marshal(pb Message) error {
|
||||||
|
msg, err := Marshal(pb)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if item := ms.find(pb); item != nil {
|
||||||
|
// reuse existing item
|
||||||
|
item.Message = msg
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mti, ok := pb.(messageTypeIder)
|
||||||
|
if !ok {
|
||||||
|
return errNoMessageTypeID
|
||||||
|
}
|
||||||
|
|
||||||
|
mtid := mti.MessageTypeId()
|
||||||
|
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||||
|
TypeId: &mtid,
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ms *messageSet) Reset() { *ms = messageSet{} }
|
||||||
|
func (ms *messageSet) String() string { return CompactTextString(ms) }
|
||||||
|
func (*messageSet) ProtoMessage() {}
|
||||||
|
|
||||||
|
// Support for the message_set_wire_format message option.
|
||||||
|
|
||||||
|
func skipVarint(buf []byte) []byte {
|
||||||
|
i := 0
|
||||||
|
for ; buf[i]&0x80 != 0; i++ {
|
||||||
|
}
|
||||||
|
return buf[i+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMessageSet encodes the extension map represented by m in the message set wire format.
|
||||||
|
// It is called by generated Marshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func MarshalMessageSet(exts interface{}) ([]byte, error) {
|
||||||
|
var m map[int32]Extension
|
||||||
|
switch exts := exts.(type) {
|
||||||
|
case *XXX_InternalExtensions:
|
||||||
|
if err := encodeExtensions(exts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m, _ = exts.extensionsRead()
|
||||||
|
case map[int32]Extension:
|
||||||
|
if err := encodeExtensionsMap(exts); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m = exts
|
||||||
|
default:
|
||||||
|
return nil, errors.New("proto: not an extension map")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort extension IDs to provide a deterministic encoding.
|
||||||
|
// See also enc_map in encode.go.
|
||||||
|
ids := make([]int, 0, len(m))
|
||||||
|
for id := range m {
|
||||||
|
ids = append(ids, int(id))
|
||||||
|
}
|
||||||
|
sort.Ints(ids)
|
||||||
|
|
||||||
|
ms := &messageSet{Item: make([]*_MessageSet_Item, 0, len(m))}
|
||||||
|
for _, id := range ids {
|
||||||
|
e := m[int32(id)]
|
||||||
|
// Remove the wire type and field number varint, as well as the length varint.
|
||||||
|
msg := skipVarint(skipVarint(e.enc))
|
||||||
|
|
||||||
|
ms.Item = append(ms.Item, &_MessageSet_Item{
|
||||||
|
TypeId: Int32(int32(id)),
|
||||||
|
Message: msg,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return Marshal(ms)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMessageSet decodes the extension map encoded in buf in the message set wire format.
|
||||||
|
// It is called by generated Unmarshal methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func UnmarshalMessageSet(buf []byte, exts interface{}) error {
|
||||||
|
var m map[int32]Extension
|
||||||
|
switch exts := exts.(type) {
|
||||||
|
case *XXX_InternalExtensions:
|
||||||
|
m = exts.extensionsWrite()
|
||||||
|
case map[int32]Extension:
|
||||||
|
m = exts
|
||||||
|
default:
|
||||||
|
return errors.New("proto: not an extension map")
|
||||||
|
}
|
||||||
|
|
||||||
|
ms := new(messageSet)
|
||||||
|
if err := Unmarshal(buf, ms); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, item := range ms.Item {
|
||||||
|
id := *item.TypeId
|
||||||
|
msg := item.Message
|
||||||
|
|
||||||
|
// Restore wire type and field number varint, plus length varint.
|
||||||
|
// Be careful to preserve duplicate items.
|
||||||
|
b := EncodeVarint(uint64(id)<<3 | WireBytes)
|
||||||
|
if ext, ok := m[id]; ok {
|
||||||
|
// Existing data; rip off the tag and length varint
|
||||||
|
// so we join the new data correctly.
|
||||||
|
// We can assume that ext.enc is set because we are unmarshaling.
|
||||||
|
o := ext.enc[len(b):] // skip wire type and field number
|
||||||
|
_, n := DecodeVarint(o) // calculate length of length varint
|
||||||
|
o = o[n:] // skip length varint
|
||||||
|
msg = append(o, msg...) // join old data and new data
|
||||||
|
}
|
||||||
|
b = append(b, EncodeVarint(uint64(len(msg)))...)
|
||||||
|
b = append(b, msg...)
|
||||||
|
|
||||||
|
m[id] = Extension{enc: b}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalMessageSetJSON encodes the extension map represented by m in JSON format.
|
||||||
|
// It is called by generated MarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func MarshalMessageSetJSON(exts interface{}) ([]byte, error) {
|
||||||
|
var m map[int32]Extension
|
||||||
|
switch exts := exts.(type) {
|
||||||
|
case *XXX_InternalExtensions:
|
||||||
|
m, _ = exts.extensionsRead()
|
||||||
|
case map[int32]Extension:
|
||||||
|
m = exts
|
||||||
|
default:
|
||||||
|
return nil, errors.New("proto: not an extension map")
|
||||||
|
}
|
||||||
|
var b bytes.Buffer
|
||||||
|
b.WriteByte('{')
|
||||||
|
|
||||||
|
// Process the map in key order for deterministic output.
|
||||||
|
ids := make([]int32, 0, len(m))
|
||||||
|
for id := range m {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
sort.Sort(int32Slice(ids)) // int32Slice defined in text.go
|
||||||
|
|
||||||
|
for i, id := range ids {
|
||||||
|
ext := m[id]
|
||||||
|
if i > 0 {
|
||||||
|
b.WriteByte(',')
|
||||||
|
}
|
||||||
|
|
||||||
|
msd, ok := messageSetMap[id]
|
||||||
|
if !ok {
|
||||||
|
// Unknown type; we can't render it, so skip it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&b, `"[%s]":`, msd.name)
|
||||||
|
|
||||||
|
x := ext.value
|
||||||
|
if x == nil {
|
||||||
|
x = reflect.New(msd.t.Elem()).Interface()
|
||||||
|
if err := Unmarshal(ext.enc, x.(Message)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
d, err := json.Marshal(x)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
b.Write(d)
|
||||||
|
}
|
||||||
|
b.WriteByte('}')
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalMessageSetJSON decodes the extension map encoded in buf in JSON format.
|
||||||
|
// It is called by generated UnmarshalJSON methods on protocol buffer messages with the message_set_wire_format option.
|
||||||
|
func UnmarshalMessageSetJSON(buf []byte, exts interface{}) error {
|
||||||
|
// Common-case fast path.
|
||||||
|
if len(buf) == 0 || bytes.Equal(buf, []byte("{}")) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is fairly tricky, and it's not clear that it is needed.
|
||||||
|
return errors.New("TODO: UnmarshalMessageSetJSON not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A global registry of types that can be used in a MessageSet.
|
||||||
|
|
||||||
|
var messageSetMap = make(map[int32]messageSetDesc)
|
||||||
|
|
||||||
|
type messageSetDesc struct {
|
||||||
|
t reflect.Type // pointer to struct
|
||||||
|
name string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterMessageSetType is called from the generated code.
|
||||||
|
func RegisterMessageSetType(m Message, fieldNum int32, name string) {
|
||||||
|
messageSetMap[fieldNum] = messageSetDesc{
|
||||||
|
t: reflect.TypeOf(m),
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
}
|
484
vendor/github.com/golang/protobuf/proto/pointer_reflect.go
generated
vendored
Normal file
484
vendor/github.com/golang/protobuf/proto/pointer_reflect.go
generated
vendored
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
// +build appengine js
|
||||||
|
|
||||||
|
// This file contains an implementation of proto field accesses using package reflect.
|
||||||
|
// It is slower than the code in pointer_unsafe.go but it avoids package unsafe and can
|
||||||
|
// be used on App Engine.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A structPointer is a pointer to a struct.
|
||||||
|
type structPointer struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||||
|
// The reflect value must itself be a pointer to a struct.
|
||||||
|
func toStructPointer(v reflect.Value) structPointer {
|
||||||
|
return structPointer{v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether p is nil.
|
||||||
|
func structPointer_IsNil(p structPointer) bool {
|
||||||
|
return p.v.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface returns the struct pointer as an interface value.
|
||||||
|
func structPointer_Interface(p structPointer, _ reflect.Type) interface{} {
|
||||||
|
return p.v.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A field identifies a field in a struct, accessible from a structPointer.
|
||||||
|
// In this implementation, a field is identified by the sequence of field indices
|
||||||
|
// passed to reflect's FieldByIndex.
|
||||||
|
type field []int
|
||||||
|
|
||||||
|
// toField returns a field equivalent to the given reflect field.
|
||||||
|
func toField(f *reflect.StructField) field {
|
||||||
|
return f.Index
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidField is an invalid field identifier.
|
||||||
|
var invalidField = field(nil)
|
||||||
|
|
||||||
|
// IsValid reports whether the field identifier is valid.
|
||||||
|
func (f field) IsValid() bool { return f != nil }
|
||||||
|
|
||||||
|
// field returns the given field in the struct as a reflect value.
|
||||||
|
func structPointer_field(p structPointer, f field) reflect.Value {
|
||||||
|
// Special case: an extension map entry with a value of type T
|
||||||
|
// passes a *T to the struct-handling code with a zero field,
|
||||||
|
// expecting that it will be treated as equivalent to *struct{ X T },
|
||||||
|
// which has the same memory layout. We have to handle that case
|
||||||
|
// specially, because reflect will panic if we call FieldByIndex on a
|
||||||
|
// non-struct.
|
||||||
|
if f == nil {
|
||||||
|
return p.v.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.v.Elem().FieldByIndex(f)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ifield returns the given field in the struct as an interface value.
|
||||||
|
func structPointer_ifield(p structPointer, f field) interface{} {
|
||||||
|
return structPointer_field(p, f).Addr().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the address of a []byte field in the struct.
|
||||||
|
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||||
|
return structPointer_ifield(p, f).(*[]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||||
|
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||||
|
return structPointer_ifield(p, f).(*[][]byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the address of a *bool field in the struct.
|
||||||
|
func structPointer_Bool(p structPointer, f field) **bool {
|
||||||
|
return structPointer_ifield(p, f).(**bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVal returns the address of a bool field in the struct.
|
||||||
|
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||||
|
return structPointer_ifield(p, f).(*bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSlice returns the address of a []bool field in the struct.
|
||||||
|
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||||
|
return structPointer_ifield(p, f).(*[]bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the address of a *string field in the struct.
|
||||||
|
func structPointer_String(p structPointer, f field) **string {
|
||||||
|
return structPointer_ifield(p, f).(**string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringVal returns the address of a string field in the struct.
|
||||||
|
func structPointer_StringVal(p structPointer, f field) *string {
|
||||||
|
return structPointer_ifield(p, f).(*string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice returns the address of a []string field in the struct.
|
||||||
|
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||||
|
return structPointer_ifield(p, f).(*[]string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extensions returns the address of an extension map field in the struct.
|
||||||
|
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||||
|
return structPointer_ifield(p, f).(*XXX_InternalExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtMap returns the address of an extension map field in the struct.
|
||||||
|
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||||
|
return structPointer_ifield(p, f).(*map[int32]Extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||||
|
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||||
|
return structPointer_field(p, f).Addr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStructPointer writes a *struct field in the struct.
|
||||||
|
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||||
|
structPointer_field(p, f).Set(q.v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStructPointer reads a *struct field in the struct.
|
||||||
|
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||||
|
return structPointer{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructPointerSlice the address of a []*struct field in the struct.
|
||||||
|
func structPointer_StructPointerSlice(p structPointer, f field) structPointerSlice {
|
||||||
|
return structPointerSlice{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A structPointerSlice represents the address of a slice of pointers to structs
|
||||||
|
// (themselves messages or groups). That is, v.Type() is *[]*struct{...}.
|
||||||
|
type structPointerSlice struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p structPointerSlice) Len() int { return p.v.Len() }
|
||||||
|
func (p structPointerSlice) Index(i int) structPointer { return structPointer{p.v.Index(i)} }
|
||||||
|
func (p structPointerSlice) Append(q structPointer) {
|
||||||
|
p.v.Set(reflect.Append(p.v, q.v))
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
int32Type = reflect.TypeOf(int32(0))
|
||||||
|
uint32Type = reflect.TypeOf(uint32(0))
|
||||||
|
float32Type = reflect.TypeOf(float32(0))
|
||||||
|
int64Type = reflect.TypeOf(int64(0))
|
||||||
|
uint64Type = reflect.TypeOf(uint64(0))
|
||||||
|
float64Type = reflect.TypeOf(float64(0))
|
||||||
|
)
|
||||||
|
|
||||||
|
// A word32 represents a field of type *int32, *uint32, *float32, or *enum.
|
||||||
|
// That is, v.Type() is *int32, *uint32, *float32, or *enum and v is assignable.
|
||||||
|
type word32 struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether p is nil.
|
||||||
|
func word32_IsNil(p word32) bool {
|
||||||
|
return p.v.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets p to point at a newly allocated word with bits set to x.
|
||||||
|
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
switch t {
|
||||||
|
case int32Type:
|
||||||
|
if len(o.int32s) == 0 {
|
||||||
|
o.int32s = make([]int32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.int32s[0] = int32(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.int32s[0]))
|
||||||
|
o.int32s = o.int32s[1:]
|
||||||
|
return
|
||||||
|
case uint32Type:
|
||||||
|
if len(o.uint32s) == 0 {
|
||||||
|
o.uint32s = make([]uint32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.uint32s[0] = x
|
||||||
|
p.v.Set(reflect.ValueOf(&o.uint32s[0]))
|
||||||
|
o.uint32s = o.uint32s[1:]
|
||||||
|
return
|
||||||
|
case float32Type:
|
||||||
|
if len(o.float32s) == 0 {
|
||||||
|
o.float32s = make([]float32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.float32s[0] = math.Float32frombits(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.float32s[0]))
|
||||||
|
o.float32s = o.float32s[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be enum
|
||||||
|
p.v.Set(reflect.New(t))
|
||||||
|
p.v.Elem().SetInt(int64(int32(x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the bits pointed at by p, as a uint32.
|
||||||
|
func word32_Get(p word32) uint32 {
|
||||||
|
elem := p.v.Elem()
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
return uint32(elem.Int())
|
||||||
|
case reflect.Uint32:
|
||||||
|
return uint32(elem.Uint())
|
||||||
|
case reflect.Float32:
|
||||||
|
return math.Float32bits(float32(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32 returns a reference to a *int32, *uint32, *float32, or *enum field in the struct.
|
||||||
|
func structPointer_Word32(p structPointer, f field) word32 {
|
||||||
|
return word32{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Val represents a field of type int32, uint32, float32, or enum.
|
||||||
|
// That is, v.Type() is int32, uint32, float32, or enum and v is assignable.
|
||||||
|
type word32Val struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets *p to x.
|
||||||
|
func word32Val_Set(p word32Val, x uint32) {
|
||||||
|
switch p.v.Type() {
|
||||||
|
case int32Type:
|
||||||
|
p.v.SetInt(int64(x))
|
||||||
|
return
|
||||||
|
case uint32Type:
|
||||||
|
p.v.SetUint(uint64(x))
|
||||||
|
return
|
||||||
|
case float32Type:
|
||||||
|
p.v.SetFloat(float64(math.Float32frombits(x)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// must be enum
|
||||||
|
p.v.SetInt(int64(int32(x)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the bits pointed at by p, as a uint32.
|
||||||
|
func word32Val_Get(p word32Val) uint32 {
|
||||||
|
elem := p.v
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
return uint32(elem.Int())
|
||||||
|
case reflect.Uint32:
|
||||||
|
return uint32(elem.Uint())
|
||||||
|
case reflect.Float32:
|
||||||
|
return math.Float32bits(float32(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32Val returns a reference to a int32, uint32, float32, or enum field in the struct.
|
||||||
|
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||||
|
return word32Val{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Slice is a slice of 32-bit values.
|
||||||
|
// That is, v.Type() is []int32, []uint32, []float32, or []enum.
|
||||||
|
type word32Slice struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word32Slice) Append(x uint32) {
|
||||||
|
n, m := p.v.Len(), p.v.Cap()
|
||||||
|
if n < m {
|
||||||
|
p.v.SetLen(n + 1)
|
||||||
|
} else {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||||
|
}
|
||||||
|
elem := p.v.Index(n)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
elem.SetInt(int64(int32(x)))
|
||||||
|
case reflect.Uint32:
|
||||||
|
elem.SetUint(uint64(x))
|
||||||
|
case reflect.Float32:
|
||||||
|
elem.SetFloat(float64(math.Float32frombits(x)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word32Slice) Len() int {
|
||||||
|
return p.v.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word32Slice) Index(i int) uint32 {
|
||||||
|
elem := p.v.Index(i)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int32:
|
||||||
|
return uint32(elem.Int())
|
||||||
|
case reflect.Uint32:
|
||||||
|
return uint32(elem.Uint())
|
||||||
|
case reflect.Float32:
|
||||||
|
return math.Float32bits(float32(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32Slice returns a reference to a []int32, []uint32, []float32, or []enum field in the struct.
|
||||||
|
func structPointer_Word32Slice(p structPointer, f field) word32Slice {
|
||||||
|
return word32Slice{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64 is like word32 but for 64-bit values.
|
||||||
|
type word64 struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
switch t {
|
||||||
|
case int64Type:
|
||||||
|
if len(o.int64s) == 0 {
|
||||||
|
o.int64s = make([]int64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.int64s[0] = int64(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.int64s[0]))
|
||||||
|
o.int64s = o.int64s[1:]
|
||||||
|
return
|
||||||
|
case uint64Type:
|
||||||
|
if len(o.uint64s) == 0 {
|
||||||
|
o.uint64s = make([]uint64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.uint64s[0] = x
|
||||||
|
p.v.Set(reflect.ValueOf(&o.uint64s[0]))
|
||||||
|
o.uint64s = o.uint64s[1:]
|
||||||
|
return
|
||||||
|
case float64Type:
|
||||||
|
if len(o.float64s) == 0 {
|
||||||
|
o.float64s = make([]float64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.float64s[0] = math.Float64frombits(x)
|
||||||
|
p.v.Set(reflect.ValueOf(&o.float64s[0]))
|
||||||
|
o.float64s = o.float64s[1:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_IsNil(p word64) bool {
|
||||||
|
return p.v.IsNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_Get(p word64) uint64 {
|
||||||
|
elem := p.v.Elem()
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
return uint64(elem.Int())
|
||||||
|
case reflect.Uint64:
|
||||||
|
return elem.Uint()
|
||||||
|
case reflect.Float64:
|
||||||
|
return math.Float64bits(elem.Float())
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64(p structPointer, f field) word64 {
|
||||||
|
return word64{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64Val is like word32Val but for 64-bit values.
|
||||||
|
type word64Val struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||||
|
switch p.v.Type() {
|
||||||
|
case int64Type:
|
||||||
|
p.v.SetInt(int64(x))
|
||||||
|
return
|
||||||
|
case uint64Type:
|
||||||
|
p.v.SetUint(x)
|
||||||
|
return
|
||||||
|
case float64Type:
|
||||||
|
p.v.SetFloat(math.Float64frombits(x))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64Val_Get(p word64Val) uint64 {
|
||||||
|
elem := p.v
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
return uint64(elem.Int())
|
||||||
|
case reflect.Uint64:
|
||||||
|
return elem.Uint()
|
||||||
|
case reflect.Float64:
|
||||||
|
return math.Float64bits(elem.Float())
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||||
|
return word64Val{structPointer_field(p, f)}
|
||||||
|
}
|
||||||
|
|
||||||
|
type word64Slice struct {
|
||||||
|
v reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word64Slice) Append(x uint64) {
|
||||||
|
n, m := p.v.Len(), p.v.Cap()
|
||||||
|
if n < m {
|
||||||
|
p.v.SetLen(n + 1)
|
||||||
|
} else {
|
||||||
|
t := p.v.Type().Elem()
|
||||||
|
p.v.Set(reflect.Append(p.v, reflect.Zero(t)))
|
||||||
|
}
|
||||||
|
elem := p.v.Index(n)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
elem.SetInt(int64(int64(x)))
|
||||||
|
case reflect.Uint64:
|
||||||
|
elem.SetUint(uint64(x))
|
||||||
|
case reflect.Float64:
|
||||||
|
elem.SetFloat(float64(math.Float64frombits(x)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word64Slice) Len() int {
|
||||||
|
return p.v.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p word64Slice) Index(i int) uint64 {
|
||||||
|
elem := p.v.Index(i)
|
||||||
|
switch elem.Kind() {
|
||||||
|
case reflect.Int64:
|
||||||
|
return uint64(elem.Int())
|
||||||
|
case reflect.Uint64:
|
||||||
|
return uint64(elem.Uint())
|
||||||
|
case reflect.Float64:
|
||||||
|
return math.Float64bits(float64(elem.Float()))
|
||||||
|
}
|
||||||
|
panic("unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64Slice(p structPointer, f field) word64Slice {
|
||||||
|
return word64Slice{structPointer_field(p, f)}
|
||||||
|
}
|
270
vendor/github.com/golang/protobuf/proto/pointer_unsafe.go
generated
vendored
Normal file
270
vendor/github.com/golang/protobuf/proto/pointer_unsafe.go
generated
vendored
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
// +build !appengine,!js
|
||||||
|
|
||||||
|
// This file contains the implementation of the proto field accesses using package unsafe.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: These type_Foo functions would more idiomatically be methods,
|
||||||
|
// but Go does not allow methods on pointer types, and we must preserve
|
||||||
|
// some pointer type for the garbage collector. We use these
|
||||||
|
// funcs with clunky names as our poor approximation to methods.
|
||||||
|
//
|
||||||
|
// An alternative would be
|
||||||
|
// type structPointer struct { p unsafe.Pointer }
|
||||||
|
// but that does not registerize as well.
|
||||||
|
|
||||||
|
// A structPointer is a pointer to a struct.
|
||||||
|
type structPointer unsafe.Pointer
|
||||||
|
|
||||||
|
// toStructPointer returns a structPointer equivalent to the given reflect value.
|
||||||
|
func toStructPointer(v reflect.Value) structPointer {
|
||||||
|
return structPointer(unsafe.Pointer(v.Pointer()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsNil reports whether p is nil.
|
||||||
|
func structPointer_IsNil(p structPointer) bool {
|
||||||
|
return p == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface returns the struct pointer, assumed to have element type t,
|
||||||
|
// as an interface value.
|
||||||
|
func structPointer_Interface(p structPointer, t reflect.Type) interface{} {
|
||||||
|
return reflect.NewAt(t, unsafe.Pointer(p)).Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
// A field identifies a field in a struct, accessible from a structPointer.
|
||||||
|
// In this implementation, a field is identified by its byte offset from the start of the struct.
|
||||||
|
type field uintptr
|
||||||
|
|
||||||
|
// toField returns a field equivalent to the given reflect field.
|
||||||
|
func toField(f *reflect.StructField) field {
|
||||||
|
return field(f.Offset)
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalidField is an invalid field identifier.
|
||||||
|
const invalidField = ^field(0)
|
||||||
|
|
||||||
|
// IsValid reports whether the field identifier is valid.
|
||||||
|
func (f field) IsValid() bool {
|
||||||
|
return f != ^field(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes returns the address of a []byte field in the struct.
|
||||||
|
func structPointer_Bytes(p structPointer, f field) *[]byte {
|
||||||
|
return (*[]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BytesSlice returns the address of a [][]byte field in the struct.
|
||||||
|
func structPointer_BytesSlice(p structPointer, f field) *[][]byte {
|
||||||
|
return (*[][]byte)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool returns the address of a *bool field in the struct.
|
||||||
|
func structPointer_Bool(p structPointer, f field) **bool {
|
||||||
|
return (**bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolVal returns the address of a bool field in the struct.
|
||||||
|
func structPointer_BoolVal(p structPointer, f field) *bool {
|
||||||
|
return (*bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// BoolSlice returns the address of a []bool field in the struct.
|
||||||
|
func structPointer_BoolSlice(p structPointer, f field) *[]bool {
|
||||||
|
return (*[]bool)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the address of a *string field in the struct.
|
||||||
|
func structPointer_String(p structPointer, f field) **string {
|
||||||
|
return (**string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringVal returns the address of a string field in the struct.
|
||||||
|
func structPointer_StringVal(p structPointer, f field) *string {
|
||||||
|
return (*string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringSlice returns the address of a []string field in the struct.
|
||||||
|
func structPointer_StringSlice(p structPointer, f field) *[]string {
|
||||||
|
return (*[]string)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtMap returns the address of an extension map field in the struct.
|
||||||
|
func structPointer_Extensions(p structPointer, f field) *XXX_InternalExtensions {
|
||||||
|
return (*XXX_InternalExtensions)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_ExtMap(p structPointer, f field) *map[int32]Extension {
|
||||||
|
return (*map[int32]Extension)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAt returns the reflect.Value for a pointer to a field in the struct.
|
||||||
|
func structPointer_NewAt(p structPointer, f field, typ reflect.Type) reflect.Value {
|
||||||
|
return reflect.NewAt(typ, unsafe.Pointer(uintptr(p)+uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStructPointer writes a *struct field in the struct.
|
||||||
|
func structPointer_SetStructPointer(p structPointer, f field, q structPointer) {
|
||||||
|
*(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f))) = q
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStructPointer reads a *struct field in the struct.
|
||||||
|
func structPointer_GetStructPointer(p structPointer, f field) structPointer {
|
||||||
|
return *(*structPointer)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructPointerSlice the address of a []*struct field in the struct.
|
||||||
|
func structPointer_StructPointerSlice(p structPointer, f field) *structPointerSlice {
|
||||||
|
return (*structPointerSlice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A structPointerSlice represents a slice of pointers to structs (themselves submessages or groups).
|
||||||
|
type structPointerSlice []structPointer
|
||||||
|
|
||||||
|
func (v *structPointerSlice) Len() int { return len(*v) }
|
||||||
|
func (v *structPointerSlice) Index(i int) structPointer { return (*v)[i] }
|
||||||
|
func (v *structPointerSlice) Append(p structPointer) { *v = append(*v, p) }
|
||||||
|
|
||||||
|
// A word32 is the address of a "pointer to 32-bit value" field.
|
||||||
|
type word32 **uint32
|
||||||
|
|
||||||
|
// IsNil reports whether *v is nil.
|
||||||
|
func word32_IsNil(p word32) bool {
|
||||||
|
return *p == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets *v to point at a newly allocated word set to x.
|
||||||
|
func word32_Set(p word32, o *Buffer, x uint32) {
|
||||||
|
if len(o.uint32s) == 0 {
|
||||||
|
o.uint32s = make([]uint32, uint32PoolSize)
|
||||||
|
}
|
||||||
|
o.uint32s[0] = x
|
||||||
|
*p = &o.uint32s[0]
|
||||||
|
o.uint32s = o.uint32s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the value pointed at by *v.
|
||||||
|
func word32_Get(p word32) uint32 {
|
||||||
|
return **p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32 returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||||
|
func structPointer_Word32(p structPointer, f field) word32 {
|
||||||
|
return word32((**uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Val is the address of a 32-bit value field.
|
||||||
|
type word32Val *uint32
|
||||||
|
|
||||||
|
// Set sets *p to x.
|
||||||
|
func word32Val_Set(p word32Val, x uint32) {
|
||||||
|
*p = x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get gets the value pointed at by p.
|
||||||
|
func word32Val_Get(p word32Val) uint32 {
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
// Word32Val returns the address of a *int32, *uint32, *float32, or *enum field in the struct.
|
||||||
|
func structPointer_Word32Val(p structPointer, f field) word32Val {
|
||||||
|
return word32Val((*uint32)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// A word32Slice is a slice of 32-bit values.
|
||||||
|
type word32Slice []uint32
|
||||||
|
|
||||||
|
func (v *word32Slice) Append(x uint32) { *v = append(*v, x) }
|
||||||
|
func (v *word32Slice) Len() int { return len(*v) }
|
||||||
|
func (v *word32Slice) Index(i int) uint32 { return (*v)[i] }
|
||||||
|
|
||||||
|
// Word32Slice returns the address of a []int32, []uint32, []float32, or []enum field in the struct.
|
||||||
|
func structPointer_Word32Slice(p structPointer, f field) *word32Slice {
|
||||||
|
return (*word32Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64 is like word32 but for 64-bit values.
|
||||||
|
type word64 **uint64
|
||||||
|
|
||||||
|
func word64_Set(p word64, o *Buffer, x uint64) {
|
||||||
|
if len(o.uint64s) == 0 {
|
||||||
|
o.uint64s = make([]uint64, uint64PoolSize)
|
||||||
|
}
|
||||||
|
o.uint64s[0] = x
|
||||||
|
*p = &o.uint64s[0]
|
||||||
|
o.uint64s = o.uint64s[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_IsNil(p word64) bool {
|
||||||
|
return *p == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64_Get(p word64) uint64 {
|
||||||
|
return **p
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64(p structPointer, f field) word64 {
|
||||||
|
return word64((**uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64Val is like word32Val but for 64-bit values.
|
||||||
|
type word64Val *uint64
|
||||||
|
|
||||||
|
func word64Val_Set(p word64Val, o *Buffer, x uint64) {
|
||||||
|
*p = x
|
||||||
|
}
|
||||||
|
|
||||||
|
func word64Val_Get(p word64Val) uint64 {
|
||||||
|
return *p
|
||||||
|
}
|
||||||
|
|
||||||
|
func structPointer_Word64Val(p structPointer, f field) word64Val {
|
||||||
|
return word64Val((*uint64)(unsafe.Pointer(uintptr(p) + uintptr(f))))
|
||||||
|
}
|
||||||
|
|
||||||
|
// word64Slice is like word32Slice but for 64-bit values.
|
||||||
|
type word64Slice []uint64
|
||||||
|
|
||||||
|
func (v *word64Slice) Append(x uint64) { *v = append(*v, x) }
|
||||||
|
func (v *word64Slice) Len() int { return len(*v) }
|
||||||
|
func (v *word64Slice) Index(i int) uint64 { return (*v)[i] }
|
||||||
|
|
||||||
|
func structPointer_Word64Slice(p structPointer, f field) *word64Slice {
|
||||||
|
return (*word64Slice)(unsafe.Pointer(uintptr(p) + uintptr(f)))
|
||||||
|
}
|
872
vendor/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
872
vendor/github.com/golang/protobuf/proto/properties.go
generated
vendored
Normal file
@ -0,0 +1,872 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Routines for encoding data into the wire format for protocol buffers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const debug bool = false
|
||||||
|
|
||||||
|
// Constants that identify the encoding of a value on the wire.
|
||||||
|
const (
|
||||||
|
WireVarint = 0
|
||||||
|
WireFixed64 = 1
|
||||||
|
WireBytes = 2
|
||||||
|
WireStartGroup = 3
|
||||||
|
WireEndGroup = 4
|
||||||
|
WireFixed32 = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
const startSize = 10 // initial slice/string sizes
|
||||||
|
|
||||||
|
// Encoders are defined in encode.go
|
||||||
|
// An encoder outputs the full representation of a field, including its
|
||||||
|
// tag and encoder type.
|
||||||
|
type encoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||||
|
|
||||||
|
// A valueEncoder encodes a single integer in a particular encoding.
|
||||||
|
type valueEncoder func(o *Buffer, x uint64) error
|
||||||
|
|
||||||
|
// Sizers are defined in encode.go
|
||||||
|
// A sizer returns the encoded size of a field, including its tag and encoder
|
||||||
|
// type.
|
||||||
|
type sizer func(prop *Properties, base structPointer) int
|
||||||
|
|
||||||
|
// A valueSizer returns the encoded size of a single integer in a particular
|
||||||
|
// encoding.
|
||||||
|
type valueSizer func(x uint64) int
|
||||||
|
|
||||||
|
// Decoders are defined in decode.go
|
||||||
|
// A decoder creates a value from its wire representation.
|
||||||
|
// Unrecognized subelements are saved in unrec.
|
||||||
|
type decoder func(p *Buffer, prop *Properties, base structPointer) error
|
||||||
|
|
||||||
|
// A valueDecoder decodes a single integer in a particular encoding.
|
||||||
|
type valueDecoder func(o *Buffer) (x uint64, err error)
|
||||||
|
|
||||||
|
// A oneofMarshaler does the marshaling for all oneof fields in a message.
|
||||||
|
type oneofMarshaler func(Message, *Buffer) error
|
||||||
|
|
||||||
|
// A oneofUnmarshaler does the unmarshaling for a oneof field in a message.
|
||||||
|
type oneofUnmarshaler func(Message, int, int, *Buffer) (bool, error)
|
||||||
|
|
||||||
|
// A oneofSizer does the sizing for all oneof fields in a message.
|
||||||
|
type oneofSizer func(Message) int
|
||||||
|
|
||||||
|
// tagMap is an optimization over map[int]int for typical protocol buffer
|
||||||
|
// use-cases. Encoded protocol buffers are often in tag order with small tag
|
||||||
|
// numbers.
|
||||||
|
type tagMap struct {
|
||||||
|
fastTags []int
|
||||||
|
slowTags map[int]int
|
||||||
|
}
|
||||||
|
|
||||||
|
// tagMapFastLimit is the upper bound on the tag number that will be stored in
|
||||||
|
// the tagMap slice rather than its map.
|
||||||
|
const tagMapFastLimit = 1024
|
||||||
|
|
||||||
|
func (p *tagMap) get(t int) (int, bool) {
|
||||||
|
if t > 0 && t < tagMapFastLimit {
|
||||||
|
if t >= len(p.fastTags) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
fi := p.fastTags[t]
|
||||||
|
return fi, fi >= 0
|
||||||
|
}
|
||||||
|
fi, ok := p.slowTags[t]
|
||||||
|
return fi, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *tagMap) put(t int, fi int) {
|
||||||
|
if t > 0 && t < tagMapFastLimit {
|
||||||
|
for len(p.fastTags) < t+1 {
|
||||||
|
p.fastTags = append(p.fastTags, -1)
|
||||||
|
}
|
||||||
|
p.fastTags[t] = fi
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if p.slowTags == nil {
|
||||||
|
p.slowTags = make(map[int]int)
|
||||||
|
}
|
||||||
|
p.slowTags[t] = fi
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructProperties represents properties for all the fields of a struct.
|
||||||
|
// decoderTags and decoderOrigNames should only be used by the decoder.
|
||||||
|
type StructProperties struct {
|
||||||
|
Prop []*Properties // properties for each field
|
||||||
|
reqCount int // required count
|
||||||
|
decoderTags tagMap // map from proto tag to struct field number
|
||||||
|
decoderOrigNames map[string]int // map from original name to struct field number
|
||||||
|
order []int // list of struct field numbers in tag order
|
||||||
|
unrecField field // field id of the XXX_unrecognized []byte field
|
||||||
|
extendable bool // is this an extendable proto
|
||||||
|
|
||||||
|
oneofMarshaler oneofMarshaler
|
||||||
|
oneofUnmarshaler oneofUnmarshaler
|
||||||
|
oneofSizer oneofSizer
|
||||||
|
stype reflect.Type
|
||||||
|
|
||||||
|
// OneofTypes contains information about the oneof fields in this message.
|
||||||
|
// It is keyed by the original name of a field.
|
||||||
|
OneofTypes map[string]*OneofProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
// OneofProperties represents information about a specific field in a oneof.
|
||||||
|
type OneofProperties struct {
|
||||||
|
Type reflect.Type // pointer to generated struct type for this oneof field
|
||||||
|
Field int // struct field number of the containing oneof in the message
|
||||||
|
Prop *Properties
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement the sorting interface so we can sort the fields in tag order, as recommended by the spec.
|
||||||
|
// See encode.go, (*Buffer).enc_struct.
|
||||||
|
|
||||||
|
func (sp *StructProperties) Len() int { return len(sp.order) }
|
||||||
|
func (sp *StructProperties) Less(i, j int) bool {
|
||||||
|
return sp.Prop[sp.order[i]].Tag < sp.Prop[sp.order[j]].Tag
|
||||||
|
}
|
||||||
|
func (sp *StructProperties) Swap(i, j int) { sp.order[i], sp.order[j] = sp.order[j], sp.order[i] }
|
||||||
|
|
||||||
|
// Properties represents the protocol-specific behavior of a single struct field.
|
||||||
|
type Properties struct {
|
||||||
|
Name string // name of the field, for error messages
|
||||||
|
OrigName string // original name before protocol compiler (always set)
|
||||||
|
JSONName string // name to use for JSON; determined by protoc
|
||||||
|
Wire string
|
||||||
|
WireType int
|
||||||
|
Tag int
|
||||||
|
Required bool
|
||||||
|
Optional bool
|
||||||
|
Repeated bool
|
||||||
|
Packed bool // relevant for repeated primitives only
|
||||||
|
Enum string // set for enum types only
|
||||||
|
proto3 bool // whether this is known to be a proto3 field; set for []byte only
|
||||||
|
oneof bool // whether this is a oneof field
|
||||||
|
|
||||||
|
Default string // default value
|
||||||
|
HasDefault bool // whether an explicit default was provided
|
||||||
|
def_uint64 uint64
|
||||||
|
|
||||||
|
enc encoder
|
||||||
|
valEnc valueEncoder // set for bool and numeric types only
|
||||||
|
field field
|
||||||
|
tagcode []byte // encoding of EncodeVarint((Tag<<3)|WireType)
|
||||||
|
tagbuf [8]byte
|
||||||
|
stype reflect.Type // set for struct types only
|
||||||
|
sprop *StructProperties // set for struct types only
|
||||||
|
isMarshaler bool
|
||||||
|
isUnmarshaler bool
|
||||||
|
|
||||||
|
mtype reflect.Type // set for map types only
|
||||||
|
mkeyprop *Properties // set for map types only
|
||||||
|
mvalprop *Properties // set for map types only
|
||||||
|
|
||||||
|
size sizer
|
||||||
|
valSize valueSizer // set for bool and numeric types only
|
||||||
|
|
||||||
|
dec decoder
|
||||||
|
valDec valueDecoder // set for bool and numeric types only
|
||||||
|
|
||||||
|
// If this is a packable field, this will be the decoder for the packed version of the field.
|
||||||
|
packedDec decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// String formats the properties in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) String() string {
|
||||||
|
s := p.Wire
|
||||||
|
s = ","
|
||||||
|
s += strconv.Itoa(p.Tag)
|
||||||
|
if p.Required {
|
||||||
|
s += ",req"
|
||||||
|
}
|
||||||
|
if p.Optional {
|
||||||
|
s += ",opt"
|
||||||
|
}
|
||||||
|
if p.Repeated {
|
||||||
|
s += ",rep"
|
||||||
|
}
|
||||||
|
if p.Packed {
|
||||||
|
s += ",packed"
|
||||||
|
}
|
||||||
|
s += ",name=" + p.OrigName
|
||||||
|
if p.JSONName != p.OrigName {
|
||||||
|
s += ",json=" + p.JSONName
|
||||||
|
}
|
||||||
|
if p.proto3 {
|
||||||
|
s += ",proto3"
|
||||||
|
}
|
||||||
|
if p.oneof {
|
||||||
|
s += ",oneof"
|
||||||
|
}
|
||||||
|
if len(p.Enum) > 0 {
|
||||||
|
s += ",enum=" + p.Enum
|
||||||
|
}
|
||||||
|
if p.HasDefault {
|
||||||
|
s += ",def=" + p.Default
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse populates p by parsing a string in the protobuf struct field tag style.
|
||||||
|
func (p *Properties) Parse(s string) {
|
||||||
|
// "bytes,49,opt,name=foo,def=hello!"
|
||||||
|
fields := strings.Split(s, ",") // breaks def=, but handled below.
|
||||||
|
if len(fields) < 2 {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: tag has too few fields: %q\n", s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Wire = fields[0]
|
||||||
|
switch p.Wire {
|
||||||
|
case "varint":
|
||||||
|
p.WireType = WireVarint
|
||||||
|
p.valEnc = (*Buffer).EncodeVarint
|
||||||
|
p.valDec = (*Buffer).DecodeVarint
|
||||||
|
p.valSize = sizeVarint
|
||||||
|
case "fixed32":
|
||||||
|
p.WireType = WireFixed32
|
||||||
|
p.valEnc = (*Buffer).EncodeFixed32
|
||||||
|
p.valDec = (*Buffer).DecodeFixed32
|
||||||
|
p.valSize = sizeFixed32
|
||||||
|
case "fixed64":
|
||||||
|
p.WireType = WireFixed64
|
||||||
|
p.valEnc = (*Buffer).EncodeFixed64
|
||||||
|
p.valDec = (*Buffer).DecodeFixed64
|
||||||
|
p.valSize = sizeFixed64
|
||||||
|
case "zigzag32":
|
||||||
|
p.WireType = WireVarint
|
||||||
|
p.valEnc = (*Buffer).EncodeZigzag32
|
||||||
|
p.valDec = (*Buffer).DecodeZigzag32
|
||||||
|
p.valSize = sizeZigzag32
|
||||||
|
case "zigzag64":
|
||||||
|
p.WireType = WireVarint
|
||||||
|
p.valEnc = (*Buffer).EncodeZigzag64
|
||||||
|
p.valDec = (*Buffer).DecodeZigzag64
|
||||||
|
p.valSize = sizeZigzag64
|
||||||
|
case "bytes", "group":
|
||||||
|
p.WireType = WireBytes
|
||||||
|
// no numeric converter for non-numeric types
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: tag has unknown wire type: %q\n", s)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
p.Tag, err = strconv.Atoi(fields[1])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 2; i < len(fields); i++ {
|
||||||
|
f := fields[i]
|
||||||
|
switch {
|
||||||
|
case f == "req":
|
||||||
|
p.Required = true
|
||||||
|
case f == "opt":
|
||||||
|
p.Optional = true
|
||||||
|
case f == "rep":
|
||||||
|
p.Repeated = true
|
||||||
|
case f == "packed":
|
||||||
|
p.Packed = true
|
||||||
|
case strings.HasPrefix(f, "name="):
|
||||||
|
p.OrigName = f[5:]
|
||||||
|
case strings.HasPrefix(f, "json="):
|
||||||
|
p.JSONName = f[5:]
|
||||||
|
case strings.HasPrefix(f, "enum="):
|
||||||
|
p.Enum = f[5:]
|
||||||
|
case f == "proto3":
|
||||||
|
p.proto3 = true
|
||||||
|
case f == "oneof":
|
||||||
|
p.oneof = true
|
||||||
|
case strings.HasPrefix(f, "def="):
|
||||||
|
p.HasDefault = true
|
||||||
|
p.Default = f[4:] // rest of string
|
||||||
|
if i+1 < len(fields) {
|
||||||
|
// Commas aren't escaped, and def is always last.
|
||||||
|
p.Default += "," + strings.Join(fields[i+1:], ",")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func logNoSliceEnc(t1, t2 reflect.Type) {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no slice oenc for %T = []%T\n", t1, t2)
|
||||||
|
}
|
||||||
|
|
||||||
|
var protoMessageType = reflect.TypeOf((*Message)(nil)).Elem()
|
||||||
|
|
||||||
|
// Initialize the fields for encoding and decoding.
|
||||||
|
func (p *Properties) setEncAndDec(typ reflect.Type, f *reflect.StructField, lockGetProp bool) {
|
||||||
|
p.enc = nil
|
||||||
|
p.dec = nil
|
||||||
|
p.size = nil
|
||||||
|
|
||||||
|
switch t1 := typ; t1.Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no coders for %v\n", t1)
|
||||||
|
|
||||||
|
// proto3 scalar types
|
||||||
|
|
||||||
|
case reflect.Bool:
|
||||||
|
p.enc = (*Buffer).enc_proto3_bool
|
||||||
|
p.dec = (*Buffer).dec_proto3_bool
|
||||||
|
p.size = size_proto3_bool
|
||||||
|
case reflect.Int32:
|
||||||
|
p.enc = (*Buffer).enc_proto3_int32
|
||||||
|
p.dec = (*Buffer).dec_proto3_int32
|
||||||
|
p.size = size_proto3_int32
|
||||||
|
case reflect.Uint32:
|
||||||
|
p.enc = (*Buffer).enc_proto3_uint32
|
||||||
|
p.dec = (*Buffer).dec_proto3_int32 // can reuse
|
||||||
|
p.size = size_proto3_uint32
|
||||||
|
case reflect.Int64, reflect.Uint64:
|
||||||
|
p.enc = (*Buffer).enc_proto3_int64
|
||||||
|
p.dec = (*Buffer).dec_proto3_int64
|
||||||
|
p.size = size_proto3_int64
|
||||||
|
case reflect.Float32:
|
||||||
|
p.enc = (*Buffer).enc_proto3_uint32 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_proto3_int32
|
||||||
|
p.size = size_proto3_uint32
|
||||||
|
case reflect.Float64:
|
||||||
|
p.enc = (*Buffer).enc_proto3_int64 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_proto3_int64
|
||||||
|
p.size = size_proto3_int64
|
||||||
|
case reflect.String:
|
||||||
|
p.enc = (*Buffer).enc_proto3_string
|
||||||
|
p.dec = (*Buffer).dec_proto3_string
|
||||||
|
p.size = size_proto3_string
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch t2 := t1.Elem(); t2.Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no encoder function for %v -> %v\n", t1, t2)
|
||||||
|
break
|
||||||
|
case reflect.Bool:
|
||||||
|
p.enc = (*Buffer).enc_bool
|
||||||
|
p.dec = (*Buffer).dec_bool
|
||||||
|
p.size = size_bool
|
||||||
|
case reflect.Int32:
|
||||||
|
p.enc = (*Buffer).enc_int32
|
||||||
|
p.dec = (*Buffer).dec_int32
|
||||||
|
p.size = size_int32
|
||||||
|
case reflect.Uint32:
|
||||||
|
p.enc = (*Buffer).enc_uint32
|
||||||
|
p.dec = (*Buffer).dec_int32 // can reuse
|
||||||
|
p.size = size_uint32
|
||||||
|
case reflect.Int64, reflect.Uint64:
|
||||||
|
p.enc = (*Buffer).enc_int64
|
||||||
|
p.dec = (*Buffer).dec_int64
|
||||||
|
p.size = size_int64
|
||||||
|
case reflect.Float32:
|
||||||
|
p.enc = (*Buffer).enc_uint32 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_int32
|
||||||
|
p.size = size_uint32
|
||||||
|
case reflect.Float64:
|
||||||
|
p.enc = (*Buffer).enc_int64 // can just treat them as bits
|
||||||
|
p.dec = (*Buffer).dec_int64
|
||||||
|
p.size = size_int64
|
||||||
|
case reflect.String:
|
||||||
|
p.enc = (*Buffer).enc_string
|
||||||
|
p.dec = (*Buffer).dec_string
|
||||||
|
p.size = size_string
|
||||||
|
case reflect.Struct:
|
||||||
|
p.stype = t1.Elem()
|
||||||
|
p.isMarshaler = isMarshaler(t1)
|
||||||
|
p.isUnmarshaler = isUnmarshaler(t1)
|
||||||
|
if p.Wire == "bytes" {
|
||||||
|
p.enc = (*Buffer).enc_struct_message
|
||||||
|
p.dec = (*Buffer).dec_struct_message
|
||||||
|
p.size = size_struct_message
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_struct_group
|
||||||
|
p.dec = (*Buffer).dec_struct_group
|
||||||
|
p.size = size_struct_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Slice:
|
||||||
|
switch t2 := t1.Elem(); t2.Kind() {
|
||||||
|
default:
|
||||||
|
logNoSliceEnc(t1, t2)
|
||||||
|
break
|
||||||
|
case reflect.Bool:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_bool
|
||||||
|
p.size = size_slice_packed_bool
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_bool
|
||||||
|
p.size = size_slice_bool
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_bool
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_bool
|
||||||
|
case reflect.Int32:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_int32
|
||||||
|
p.size = size_slice_packed_int32
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_int32
|
||||||
|
p.size = size_slice_int32
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int32
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||||
|
case reflect.Uint32:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||||
|
p.size = size_slice_packed_uint32
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_uint32
|
||||||
|
p.size = size_slice_uint32
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int32
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||||
|
case reflect.Int64, reflect.Uint64:
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_int64
|
||||||
|
p.size = size_slice_packed_int64
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_int64
|
||||||
|
p.size = size_slice_int64
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int64
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||||
|
case reflect.Uint8:
|
||||||
|
p.dec = (*Buffer).dec_slice_byte
|
||||||
|
if p.proto3 {
|
||||||
|
p.enc = (*Buffer).enc_proto3_slice_byte
|
||||||
|
p.size = size_proto3_slice_byte
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_byte
|
||||||
|
p.size = size_slice_byte
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
switch t2.Bits() {
|
||||||
|
case 32:
|
||||||
|
// can just treat them as bits
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_uint32
|
||||||
|
p.size = size_slice_packed_uint32
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_uint32
|
||||||
|
p.size = size_slice_uint32
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int32
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int32
|
||||||
|
case 64:
|
||||||
|
// can just treat them as bits
|
||||||
|
if p.Packed {
|
||||||
|
p.enc = (*Buffer).enc_slice_packed_int64
|
||||||
|
p.size = size_slice_packed_int64
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_int64
|
||||||
|
p.size = size_slice_int64
|
||||||
|
}
|
||||||
|
p.dec = (*Buffer).dec_slice_int64
|
||||||
|
p.packedDec = (*Buffer).dec_slice_packed_int64
|
||||||
|
default:
|
||||||
|
logNoSliceEnc(t1, t2)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
p.enc = (*Buffer).enc_slice_string
|
||||||
|
p.dec = (*Buffer).dec_slice_string
|
||||||
|
p.size = size_slice_string
|
||||||
|
case reflect.Ptr:
|
||||||
|
switch t3 := t2.Elem(); t3.Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no ptr oenc for %T -> %T -> %T\n", t1, t2, t3)
|
||||||
|
break
|
||||||
|
case reflect.Struct:
|
||||||
|
p.stype = t2.Elem()
|
||||||
|
p.isMarshaler = isMarshaler(t2)
|
||||||
|
p.isUnmarshaler = isUnmarshaler(t2)
|
||||||
|
if p.Wire == "bytes" {
|
||||||
|
p.enc = (*Buffer).enc_slice_struct_message
|
||||||
|
p.dec = (*Buffer).dec_slice_struct_message
|
||||||
|
p.size = size_slice_struct_message
|
||||||
|
} else {
|
||||||
|
p.enc = (*Buffer).enc_slice_struct_group
|
||||||
|
p.dec = (*Buffer).dec_slice_struct_group
|
||||||
|
p.size = size_slice_struct_group
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
switch t2.Elem().Kind() {
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: no slice elem oenc for %T -> %T -> %T\n", t1, t2, t2.Elem())
|
||||||
|
break
|
||||||
|
case reflect.Uint8:
|
||||||
|
p.enc = (*Buffer).enc_slice_slice_byte
|
||||||
|
p.dec = (*Buffer).dec_slice_slice_byte
|
||||||
|
p.size = size_slice_slice_byte
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Map:
|
||||||
|
p.enc = (*Buffer).enc_new_map
|
||||||
|
p.dec = (*Buffer).dec_new_map
|
||||||
|
p.size = size_new_map
|
||||||
|
|
||||||
|
p.mtype = t1
|
||||||
|
p.mkeyprop = &Properties{}
|
||||||
|
p.mkeyprop.init(reflect.PtrTo(p.mtype.Key()), "Key", f.Tag.Get("protobuf_key"), nil, lockGetProp)
|
||||||
|
p.mvalprop = &Properties{}
|
||||||
|
vtype := p.mtype.Elem()
|
||||||
|
if vtype.Kind() != reflect.Ptr && vtype.Kind() != reflect.Slice {
|
||||||
|
// The value type is not a message (*T) or bytes ([]byte),
|
||||||
|
// so we need encoders for the pointer to this type.
|
||||||
|
vtype = reflect.PtrTo(vtype)
|
||||||
|
}
|
||||||
|
p.mvalprop.init(vtype, "Value", f.Tag.Get("protobuf_val"), nil, lockGetProp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// precalculate tag code
|
||||||
|
wire := p.WireType
|
||||||
|
if p.Packed {
|
||||||
|
wire = WireBytes
|
||||||
|
}
|
||||||
|
x := uint32(p.Tag)<<3 | uint32(wire)
|
||||||
|
i := 0
|
||||||
|
for i = 0; x > 127; i++ {
|
||||||
|
p.tagbuf[i] = 0x80 | uint8(x&0x7F)
|
||||||
|
x >>= 7
|
||||||
|
}
|
||||||
|
p.tagbuf[i] = uint8(x)
|
||||||
|
p.tagcode = p.tagbuf[0 : i+1]
|
||||||
|
|
||||||
|
if p.stype != nil {
|
||||||
|
if lockGetProp {
|
||||||
|
p.sprop = GetProperties(p.stype)
|
||||||
|
} else {
|
||||||
|
p.sprop = getPropertiesLocked(p.stype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
marshalerType = reflect.TypeOf((*Marshaler)(nil)).Elem()
|
||||||
|
unmarshalerType = reflect.TypeOf((*Unmarshaler)(nil)).Elem()
|
||||||
|
)
|
||||||
|
|
||||||
|
// isMarshaler reports whether type t implements Marshaler.
|
||||||
|
func isMarshaler(t reflect.Type) bool {
|
||||||
|
// We're checking for (likely) pointer-receiver methods
|
||||||
|
// so if t is not a pointer, something is very wrong.
|
||||||
|
// The calls above only invoke isMarshaler on pointer types.
|
||||||
|
if t.Kind() != reflect.Ptr {
|
||||||
|
panic("proto: misuse of isMarshaler")
|
||||||
|
}
|
||||||
|
return t.Implements(marshalerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isUnmarshaler reports whether type t implements Unmarshaler.
|
||||||
|
func isUnmarshaler(t reflect.Type) bool {
|
||||||
|
// We're checking for (likely) pointer-receiver methods
|
||||||
|
// so if t is not a pointer, something is very wrong.
|
||||||
|
// The calls above only invoke isUnmarshaler on pointer types.
|
||||||
|
if t.Kind() != reflect.Ptr {
|
||||||
|
panic("proto: misuse of isUnmarshaler")
|
||||||
|
}
|
||||||
|
return t.Implements(unmarshalerType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init populates the properties from a protocol buffer struct tag.
|
||||||
|
func (p *Properties) Init(typ reflect.Type, name, tag string, f *reflect.StructField) {
|
||||||
|
p.init(typ, name, tag, f, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Properties) init(typ reflect.Type, name, tag string, f *reflect.StructField, lockGetProp bool) {
|
||||||
|
// "bytes,49,opt,def=hello!"
|
||||||
|
p.Name = name
|
||||||
|
p.OrigName = name
|
||||||
|
if f != nil {
|
||||||
|
p.field = toField(f)
|
||||||
|
}
|
||||||
|
if tag == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Parse(tag)
|
||||||
|
p.setEncAndDec(typ, f, lockGetProp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
propertiesMu sync.RWMutex
|
||||||
|
propertiesMap = make(map[reflect.Type]*StructProperties)
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetProperties returns the list of properties for the type represented by t.
|
||||||
|
// t must represent a generated struct type of a protocol message.
|
||||||
|
func GetProperties(t reflect.Type) *StructProperties {
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
panic("proto: type must have kind struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Most calls to GetProperties in a long-running program will be
|
||||||
|
// retrieving details for types we have seen before.
|
||||||
|
propertiesMu.RLock()
|
||||||
|
sprop, ok := propertiesMap[t]
|
||||||
|
propertiesMu.RUnlock()
|
||||||
|
if ok {
|
||||||
|
if collectStats {
|
||||||
|
stats.Chit++
|
||||||
|
}
|
||||||
|
return sprop
|
||||||
|
}
|
||||||
|
|
||||||
|
propertiesMu.Lock()
|
||||||
|
sprop = getPropertiesLocked(t)
|
||||||
|
propertiesMu.Unlock()
|
||||||
|
return sprop
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPropertiesLocked requires that propertiesMu is held.
|
||||||
|
func getPropertiesLocked(t reflect.Type) *StructProperties {
|
||||||
|
if prop, ok := propertiesMap[t]; ok {
|
||||||
|
if collectStats {
|
||||||
|
stats.Chit++
|
||||||
|
}
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
if collectStats {
|
||||||
|
stats.Cmiss++
|
||||||
|
}
|
||||||
|
|
||||||
|
prop := new(StructProperties)
|
||||||
|
// in case of recursive protos, fill this in now.
|
||||||
|
propertiesMap[t] = prop
|
||||||
|
|
||||||
|
// build properties
|
||||||
|
prop.extendable = reflect.PtrTo(t).Implements(extendableProtoType) ||
|
||||||
|
reflect.PtrTo(t).Implements(extendableProtoV1Type)
|
||||||
|
prop.unrecField = invalidField
|
||||||
|
prop.Prop = make([]*Properties, t.NumField())
|
||||||
|
prop.order = make([]int, t.NumField())
|
||||||
|
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
p := new(Properties)
|
||||||
|
name := f.Name
|
||||||
|
p.init(f.Type, name, f.Tag.Get("protobuf"), &f, false)
|
||||||
|
|
||||||
|
if f.Name == "XXX_InternalExtensions" { // special case
|
||||||
|
p.enc = (*Buffer).enc_exts
|
||||||
|
p.dec = nil // not needed
|
||||||
|
p.size = size_exts
|
||||||
|
} else if f.Name == "XXX_extensions" { // special case
|
||||||
|
p.enc = (*Buffer).enc_map
|
||||||
|
p.dec = nil // not needed
|
||||||
|
p.size = size_map
|
||||||
|
} else if f.Name == "XXX_unrecognized" { // special case
|
||||||
|
prop.unrecField = toField(&f)
|
||||||
|
}
|
||||||
|
oneof := f.Tag.Get("protobuf_oneof") // special case
|
||||||
|
if oneof != "" {
|
||||||
|
// Oneof fields don't use the traditional protobuf tag.
|
||||||
|
p.OrigName = oneof
|
||||||
|
}
|
||||||
|
prop.Prop[i] = p
|
||||||
|
prop.order[i] = i
|
||||||
|
if debug {
|
||||||
|
print(i, " ", f.Name, " ", t.String(), " ")
|
||||||
|
if p.Tag > 0 {
|
||||||
|
print(p.String())
|
||||||
|
}
|
||||||
|
print("\n")
|
||||||
|
}
|
||||||
|
if p.enc == nil && !strings.HasPrefix(f.Name, "XXX_") && oneof == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "proto: no encoder for", f.Name, f.Type.String(), "[GetProperties]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-order prop.order.
|
||||||
|
sort.Sort(prop)
|
||||||
|
|
||||||
|
type oneofMessage interface {
|
||||||
|
XXX_OneofFuncs() (func(Message, *Buffer) error, func(Message, int, int, *Buffer) (bool, error), func(Message) int, []interface{})
|
||||||
|
}
|
||||||
|
if om, ok := reflect.Zero(reflect.PtrTo(t)).Interface().(oneofMessage); ok {
|
||||||
|
var oots []interface{}
|
||||||
|
prop.oneofMarshaler, prop.oneofUnmarshaler, prop.oneofSizer, oots = om.XXX_OneofFuncs()
|
||||||
|
prop.stype = t
|
||||||
|
|
||||||
|
// Interpret oneof metadata.
|
||||||
|
prop.OneofTypes = make(map[string]*OneofProperties)
|
||||||
|
for _, oot := range oots {
|
||||||
|
oop := &OneofProperties{
|
||||||
|
Type: reflect.ValueOf(oot).Type(), // *T
|
||||||
|
Prop: new(Properties),
|
||||||
|
}
|
||||||
|
sft := oop.Type.Elem().Field(0)
|
||||||
|
oop.Prop.Name = sft.Name
|
||||||
|
oop.Prop.Parse(sft.Tag.Get("protobuf"))
|
||||||
|
// There will be exactly one interface field that
|
||||||
|
// this new value is assignable to.
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
f := t.Field(i)
|
||||||
|
if f.Type.Kind() != reflect.Interface {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !oop.Type.AssignableTo(f.Type) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
oop.Field = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
prop.OneofTypes[oop.Prop.OrigName] = oop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// build required counts
|
||||||
|
// build tags
|
||||||
|
reqCount := 0
|
||||||
|
prop.decoderOrigNames = make(map[string]int)
|
||||||
|
for i, p := range prop.Prop {
|
||||||
|
if strings.HasPrefix(p.Name, "XXX_") {
|
||||||
|
// Internal fields should not appear in tags/origNames maps.
|
||||||
|
// They are handled specially when encoding and decoding.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if p.Required {
|
||||||
|
reqCount++
|
||||||
|
}
|
||||||
|
prop.decoderTags.put(p.Tag, i)
|
||||||
|
prop.decoderOrigNames[p.OrigName] = i
|
||||||
|
}
|
||||||
|
prop.reqCount = reqCount
|
||||||
|
|
||||||
|
return prop
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the Properties object for the x[0]'th field of the structure.
|
||||||
|
func propByIndex(t reflect.Type, x []int) *Properties {
|
||||||
|
if len(x) != 1 {
|
||||||
|
fmt.Fprintf(os.Stderr, "proto: field index dimension %d (not 1) for type %s\n", len(x), t)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
prop := GetProperties(t)
|
||||||
|
return prop.Prop[x[0]]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the address and type of a pointer to a struct from an interface.
|
||||||
|
func getbase(pb Message) (t reflect.Type, b structPointer, err error) {
|
||||||
|
if pb == nil {
|
||||||
|
err = ErrNil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// get the reflect type of the pointer to the struct.
|
||||||
|
t = reflect.TypeOf(pb)
|
||||||
|
// get the address of the struct.
|
||||||
|
value := reflect.ValueOf(pb)
|
||||||
|
b = toStructPointer(value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// A global registry of enum types.
|
||||||
|
// The generated code will register the generated maps by calling RegisterEnum.
|
||||||
|
|
||||||
|
var enumValueMaps = make(map[string]map[string]int32)
|
||||||
|
|
||||||
|
// RegisterEnum is called from the generated code to install the enum descriptor
|
||||||
|
// maps into the global table to aid parsing text format protocol buffers.
|
||||||
|
func RegisterEnum(typeName string, unusedNameMap map[int32]string, valueMap map[string]int32) {
|
||||||
|
if _, ok := enumValueMaps[typeName]; ok {
|
||||||
|
panic("proto: duplicate enum registered: " + typeName)
|
||||||
|
}
|
||||||
|
enumValueMaps[typeName] = valueMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnumValueMap returns the mapping from names to integers of the
|
||||||
|
// enum type enumType, or a nil if not found.
|
||||||
|
func EnumValueMap(enumType string) map[string]int32 {
|
||||||
|
return enumValueMaps[enumType]
|
||||||
|
}
|
||||||
|
|
||||||
|
// A registry of all linked message types.
|
||||||
|
// The string is a fully-qualified proto name ("pkg.Message").
|
||||||
|
var (
|
||||||
|
protoTypes = make(map[string]reflect.Type)
|
||||||
|
revProtoTypes = make(map[reflect.Type]string)
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterType is called from generated code and maps from the fully qualified
|
||||||
|
// proto name to the type (pointer to struct) of the protocol buffer.
|
||||||
|
func RegisterType(x Message, name string) {
|
||||||
|
if _, ok := protoTypes[name]; ok {
|
||||||
|
// TODO: Some day, make this a panic.
|
||||||
|
log.Printf("proto: duplicate proto type registered: %s", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t := reflect.TypeOf(x)
|
||||||
|
protoTypes[name] = t
|
||||||
|
revProtoTypes[t] = name
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageName returns the fully-qualified proto name for the given message type.
|
||||||
|
func MessageName(x Message) string {
|
||||||
|
type xname interface {
|
||||||
|
XXX_MessageName() string
|
||||||
|
}
|
||||||
|
if m, ok := x.(xname); ok {
|
||||||
|
return m.XXX_MessageName()
|
||||||
|
}
|
||||||
|
return revProtoTypes[reflect.TypeOf(x)]
|
||||||
|
}
|
||||||
|
|
||||||
|
// MessageType returns the message type (pointer to struct) for a named message.
|
||||||
|
func MessageType(name string) reflect.Type { return protoTypes[name] }
|
||||||
|
|
||||||
|
// A registry of all linked proto files.
|
||||||
|
var (
|
||||||
|
protoFiles = make(map[string][]byte) // file name => fileDescriptor
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterFile is called from generated code and maps from the
|
||||||
|
// full file name of a .proto file to its compressed FileDescriptorProto.
|
||||||
|
func RegisterFile(filename string, fileDescriptor []byte) {
|
||||||
|
protoFiles[filename] = fileDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileDescriptor returns the compressed FileDescriptorProto for a .proto file.
|
||||||
|
func FileDescriptor(filename string) []byte { return protoFiles[filename] }
|
854
vendor/github.com/golang/protobuf/proto/text.go
generated
vendored
Normal file
854
vendor/github.com/golang/protobuf/proto/text.go
generated
vendored
Normal file
@ -0,0 +1,854 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
// Functions for writing the text protocol buffer format.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
newline = []byte("\n")
|
||||||
|
spaces = []byte(" ")
|
||||||
|
gtNewline = []byte(">\n")
|
||||||
|
endBraceNewline = []byte("}\n")
|
||||||
|
backslashN = []byte{'\\', 'n'}
|
||||||
|
backslashR = []byte{'\\', 'r'}
|
||||||
|
backslashT = []byte{'\\', 't'}
|
||||||
|
backslashDQ = []byte{'\\', '"'}
|
||||||
|
backslashBS = []byte{'\\', '\\'}
|
||||||
|
posInf = []byte("inf")
|
||||||
|
negInf = []byte("-inf")
|
||||||
|
nan = []byte("nan")
|
||||||
|
)
|
||||||
|
|
||||||
|
type writer interface {
|
||||||
|
io.Writer
|
||||||
|
WriteByte(byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// textWriter is an io.Writer that tracks its indentation level.
|
||||||
|
type textWriter struct {
|
||||||
|
ind int
|
||||||
|
complete bool // if the current position is a complete line
|
||||||
|
compact bool // whether to write out as a one-liner
|
||||||
|
w writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) WriteString(s string) (n int, err error) {
|
||||||
|
if !strings.Contains(s, "\n") {
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
w.complete = false
|
||||||
|
return io.WriteString(w.w, s)
|
||||||
|
}
|
||||||
|
// WriteString is typically called without newlines, so this
|
||||||
|
// codepath and its copy are rare. We copy to avoid
|
||||||
|
// duplicating all of Write's logic here.
|
||||||
|
return w.Write([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) Write(p []byte) (n int, err error) {
|
||||||
|
newlines := bytes.Count(p, newline)
|
||||||
|
if newlines == 0 {
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
n, err = w.w.Write(p)
|
||||||
|
w.complete = false
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
frags := bytes.SplitN(p, newline, newlines+1)
|
||||||
|
if w.compact {
|
||||||
|
for i, frag := range frags {
|
||||||
|
if i > 0 {
|
||||||
|
if err := w.w.WriteByte(' '); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
nn, err := w.w.Write(frag)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, frag := range frags {
|
||||||
|
if w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
nn, err := w.w.Write(frag)
|
||||||
|
n += nn
|
||||||
|
if err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
if i+1 < len(frags) {
|
||||||
|
if err := w.w.WriteByte('\n'); err != nil {
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.complete = len(frags[len(frags)-1]) == 0
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) WriteByte(c byte) error {
|
||||||
|
if w.compact && c == '\n' {
|
||||||
|
c = ' '
|
||||||
|
}
|
||||||
|
if !w.compact && w.complete {
|
||||||
|
w.writeIndent()
|
||||||
|
}
|
||||||
|
err := w.w.WriteByte(c)
|
||||||
|
w.complete = c == '\n'
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) indent() { w.ind++ }
|
||||||
|
|
||||||
|
func (w *textWriter) unindent() {
|
||||||
|
if w.ind == 0 {
|
||||||
|
log.Print("proto: textWriter unindented too far")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.ind--
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeName(w *textWriter, props *Properties) error {
|
||||||
|
if _, err := w.WriteString(props.OrigName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if props.Wire != "group" {
|
||||||
|
return w.WriteByte(':')
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// raw is the interface satisfied by RawMessage.
|
||||||
|
type raw interface {
|
||||||
|
Bytes() []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func requiresQuotes(u string) bool {
|
||||||
|
// When type URL contains any characters except [0-9A-Za-z./\-]*, it must be quoted.
|
||||||
|
for _, ch := range u {
|
||||||
|
switch {
|
||||||
|
case ch == '.' || ch == '/' || ch == '_':
|
||||||
|
continue
|
||||||
|
case '0' <= ch && ch <= '9':
|
||||||
|
continue
|
||||||
|
case 'A' <= ch && ch <= 'Z':
|
||||||
|
continue
|
||||||
|
case 'a' <= ch && ch <= 'z':
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// isAny reports whether sv is a google.protobuf.Any message
|
||||||
|
func isAny(sv reflect.Value) bool {
|
||||||
|
type wkt interface {
|
||||||
|
XXX_WellKnownType() string
|
||||||
|
}
|
||||||
|
t, ok := sv.Addr().Interface().(wkt)
|
||||||
|
return ok && t.XXX_WellKnownType() == "Any"
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeProto3Any writes an expanded google.protobuf.Any message.
|
||||||
|
//
|
||||||
|
// It returns (false, nil) if sv value can't be unmarshaled (e.g. because
|
||||||
|
// required messages are not linked in).
|
||||||
|
//
|
||||||
|
// It returns (true, error) when sv was written in expanded format or an error
|
||||||
|
// was encountered.
|
||||||
|
func (tm *TextMarshaler) writeProto3Any(w *textWriter, sv reflect.Value) (bool, error) {
|
||||||
|
turl := sv.FieldByName("TypeUrl")
|
||||||
|
val := sv.FieldByName("Value")
|
||||||
|
if !turl.IsValid() || !val.IsValid() {
|
||||||
|
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||||
|
}
|
||||||
|
|
||||||
|
b, ok := val.Interface().([]byte)
|
||||||
|
if !ok {
|
||||||
|
return true, errors.New("proto: invalid google.protobuf.Any message")
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := strings.Split(turl.String(), "/")
|
||||||
|
mt := MessageType(parts[len(parts)-1])
|
||||||
|
if mt == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
m := reflect.New(mt.Elem())
|
||||||
|
if err := Unmarshal(b, m.Interface().(Message)); err != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
w.Write([]byte("["))
|
||||||
|
u := turl.String()
|
||||||
|
if requiresQuotes(u) {
|
||||||
|
writeString(w, u)
|
||||||
|
} else {
|
||||||
|
w.Write([]byte(u))
|
||||||
|
}
|
||||||
|
if w.compact {
|
||||||
|
w.Write([]byte("]:<"))
|
||||||
|
} else {
|
||||||
|
w.Write([]byte("]: <\n"))
|
||||||
|
w.ind++
|
||||||
|
}
|
||||||
|
if err := tm.writeStruct(w, m.Elem()); err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
if w.compact {
|
||||||
|
w.Write([]byte("> "))
|
||||||
|
} else {
|
||||||
|
w.ind--
|
||||||
|
w.Write([]byte(">\n"))
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *TextMarshaler) writeStruct(w *textWriter, sv reflect.Value) error {
|
||||||
|
if tm.ExpandAny && isAny(sv) {
|
||||||
|
if canExpand, err := tm.writeProto3Any(w, sv); canExpand {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
st := sv.Type()
|
||||||
|
sprops := GetProperties(st)
|
||||||
|
for i := 0; i < sv.NumField(); i++ {
|
||||||
|
fv := sv.Field(i)
|
||||||
|
props := sprops.Prop[i]
|
||||||
|
name := st.Field(i).Name
|
||||||
|
|
||||||
|
if strings.HasPrefix(name, "XXX_") {
|
||||||
|
// There are two XXX_ fields:
|
||||||
|
// XXX_unrecognized []byte
|
||||||
|
// XXX_extensions map[int32]proto.Extension
|
||||||
|
// The first is handled here;
|
||||||
|
// the second is handled at the bottom of this function.
|
||||||
|
if name == "XXX_unrecognized" && !fv.IsNil() {
|
||||||
|
if err := writeUnknownStruct(w, fv.Interface().([]byte)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||||
|
// Field not filled in. This could be an optional field or
|
||||||
|
// a required field that wasn't filled in. Either way, there
|
||||||
|
// isn't anything we can show for it.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() == reflect.Slice && fv.IsNil() {
|
||||||
|
// Repeated field that is empty, or a bytes field that is unused.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if props.Repeated && fv.Kind() == reflect.Slice {
|
||||||
|
// Repeated field.
|
||||||
|
for j := 0; j < fv.Len(); j++ {
|
||||||
|
if err := writeName(w, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v := fv.Index(j)
|
||||||
|
if v.Kind() == reflect.Ptr && v.IsNil() {
|
||||||
|
// A nil message in a repeated field is not valid,
|
||||||
|
// but we can handle that more gracefully than panicking.
|
||||||
|
if _, err := w.Write([]byte("<nil>\n")); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := tm.writeAny(w, v, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() == reflect.Map {
|
||||||
|
// Map fields are rendered as a repeated struct with key/value fields.
|
||||||
|
keys := fv.MapKeys()
|
||||||
|
sort.Sort(mapKeys(keys))
|
||||||
|
for _, key := range keys {
|
||||||
|
val := fv.MapIndex(key)
|
||||||
|
if err := writeName(w, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// open struct
|
||||||
|
if err := w.WriteByte('<'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
// key
|
||||||
|
if _, err := w.WriteString("key:"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tm.writeAny(w, key, props.mkeyprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// nil values aren't legal, but we can avoid panicking because of them.
|
||||||
|
if val.Kind() != reflect.Ptr || !val.IsNil() {
|
||||||
|
// value
|
||||||
|
if _, err := w.WriteString("value:"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tm.writeAny(w, val, props.mvalprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// close struct
|
||||||
|
w.unindent()
|
||||||
|
if err := w.WriteByte('>'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if props.proto3 && fv.Kind() == reflect.Slice && fv.Len() == 0 {
|
||||||
|
// empty bytes field
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if fv.Kind() != reflect.Ptr && fv.Kind() != reflect.Slice {
|
||||||
|
// proto3 non-repeated scalar field; skip if zero value
|
||||||
|
if isProto3Zero(fv) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fv.Kind() == reflect.Interface {
|
||||||
|
// Check if it is a oneof.
|
||||||
|
if st.Field(i).Tag.Get("protobuf_oneof") != "" {
|
||||||
|
// fv is nil, or holds a pointer to generated struct.
|
||||||
|
// That generated struct has exactly one field,
|
||||||
|
// which has a protobuf struct tag.
|
||||||
|
if fv.IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
inner := fv.Elem().Elem() // interface -> *T -> T
|
||||||
|
tag := inner.Type().Field(0).Tag.Get("protobuf")
|
||||||
|
props = new(Properties) // Overwrite the outer props var, but not its pointee.
|
||||||
|
props.Parse(tag)
|
||||||
|
// Write the value in the oneof, not the oneof itself.
|
||||||
|
fv = inner.Field(0)
|
||||||
|
|
||||||
|
// Special case to cope with malformed messages gracefully:
|
||||||
|
// If the value in the oneof is a nil pointer, don't panic
|
||||||
|
// in writeAny.
|
||||||
|
if fv.Kind() == reflect.Ptr && fv.IsNil() {
|
||||||
|
// Use errors.New so writeAny won't render quotes.
|
||||||
|
msg := errors.New("/* nil */")
|
||||||
|
fv = reflect.ValueOf(&msg).Elem()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writeName(w, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if b, ok := fv.Interface().(raw); ok {
|
||||||
|
if err := writeRaw(w, b.Bytes()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enums have a String method, so writeAny will work fine.
|
||||||
|
if err := tm.writeAny(w, fv, props); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extensions (the XXX_extensions field).
|
||||||
|
pv := sv.Addr()
|
||||||
|
if _, ok := extendable(pv.Interface()); ok {
|
||||||
|
if err := tm.writeExtensions(w, pv); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeRaw writes an uninterpreted raw message.
|
||||||
|
func writeRaw(w *textWriter, b []byte) error {
|
||||||
|
if err := w.WriteByte('<'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
if err := writeUnknownStruct(w, b); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.unindent()
|
||||||
|
if err := w.WriteByte('>'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeAny writes an arbitrary field.
|
||||||
|
func (tm *TextMarshaler) writeAny(w *textWriter, v reflect.Value, props *Properties) error {
|
||||||
|
v = reflect.Indirect(v)
|
||||||
|
|
||||||
|
// Floats have special cases.
|
||||||
|
if v.Kind() == reflect.Float32 || v.Kind() == reflect.Float64 {
|
||||||
|
x := v.Float()
|
||||||
|
var b []byte
|
||||||
|
switch {
|
||||||
|
case math.IsInf(x, 1):
|
||||||
|
b = posInf
|
||||||
|
case math.IsInf(x, -1):
|
||||||
|
b = negInf
|
||||||
|
case math.IsNaN(x):
|
||||||
|
b = nan
|
||||||
|
}
|
||||||
|
if b != nil {
|
||||||
|
_, err := w.Write(b)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Other values are handled below.
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't attempt to serialise every possible value type; only those
|
||||||
|
// that can occur in protocol buffers.
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
// Should only be a []byte; repeated fields are handled in writeStruct.
|
||||||
|
if err := writeString(w, string(v.Bytes())); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case reflect.String:
|
||||||
|
if err := writeString(w, v.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
// Required/optional group/message.
|
||||||
|
var bra, ket byte = '<', '>'
|
||||||
|
if props != nil && props.Wire == "group" {
|
||||||
|
bra, ket = '{', '}'
|
||||||
|
}
|
||||||
|
if err := w.WriteByte(bra); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.indent()
|
||||||
|
if etm, ok := v.Interface().(encoding.TextMarshaler); ok {
|
||||||
|
text, err := etm.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = w.Write(text); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else if err := tm.writeStruct(w, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
w.unindent()
|
||||||
|
if err := w.WriteByte(ket); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
_, err := fmt.Fprint(w, v.Interface())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// equivalent to C's isprint.
|
||||||
|
func isprint(c byte) bool {
|
||||||
|
return c >= 0x20 && c < 0x7f
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeString writes a string in the protocol buffer text format.
|
||||||
|
// It is similar to strconv.Quote except we don't use Go escape sequences,
|
||||||
|
// we treat the string as a byte sequence, and we use octal escapes.
|
||||||
|
// These differences are to maintain interoperability with the other
|
||||||
|
// languages' implementations of the text format.
|
||||||
|
func writeString(w *textWriter, s string) error {
|
||||||
|
// use WriteByte here to get any needed indent
|
||||||
|
if err := w.WriteByte('"'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Loop over the bytes, not the runes.
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
var err error
|
||||||
|
// Divergence from C++: we don't escape apostrophes.
|
||||||
|
// There's no need to escape them, and the C++ parser
|
||||||
|
// copes with a naked apostrophe.
|
||||||
|
switch c := s[i]; c {
|
||||||
|
case '\n':
|
||||||
|
_, err = w.w.Write(backslashN)
|
||||||
|
case '\r':
|
||||||
|
_, err = w.w.Write(backslashR)
|
||||||
|
case '\t':
|
||||||
|
_, err = w.w.Write(backslashT)
|
||||||
|
case '"':
|
||||||
|
_, err = w.w.Write(backslashDQ)
|
||||||
|
case '\\':
|
||||||
|
_, err = w.w.Write(backslashBS)
|
||||||
|
default:
|
||||||
|
if isprint(c) {
|
||||||
|
err = w.w.WriteByte(c)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(w.w, "\\%03o", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return w.WriteByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUnknownStruct(w *textWriter, data []byte) (err error) {
|
||||||
|
if !w.compact {
|
||||||
|
if _, err := fmt.Fprintf(w, "/* %d unknown bytes */\n", len(data)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b := NewBuffer(data)
|
||||||
|
for b.index < len(b.buf) {
|
||||||
|
x, err := b.DecodeVarint()
|
||||||
|
if err != nil {
|
||||||
|
_, err := fmt.Fprintf(w, "/* %v */\n", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
wire, tag := x&7, x>>3
|
||||||
|
if wire == WireEndGroup {
|
||||||
|
w.unindent()
|
||||||
|
if _, err := w.Write(endBraceNewline); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprint(w, tag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if wire != WireStartGroup {
|
||||||
|
if err := w.WriteByte(':'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !w.compact || wire == WireStartGroup {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch wire {
|
||||||
|
case WireBytes:
|
||||||
|
buf, e := b.DecodeRawBytes(false)
|
||||||
|
if e == nil {
|
||||||
|
_, err = fmt.Fprintf(w, "%q", buf)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(w, "/* %v */", e)
|
||||||
|
}
|
||||||
|
case WireFixed32:
|
||||||
|
x, err = b.DecodeFixed32()
|
||||||
|
err = writeUnknownInt(w, x, err)
|
||||||
|
case WireFixed64:
|
||||||
|
x, err = b.DecodeFixed64()
|
||||||
|
err = writeUnknownInt(w, x, err)
|
||||||
|
case WireStartGroup:
|
||||||
|
err = w.WriteByte('{')
|
||||||
|
w.indent()
|
||||||
|
case WireVarint:
|
||||||
|
x, err = b.DecodeVarint()
|
||||||
|
err = writeUnknownInt(w, x, err)
|
||||||
|
default:
|
||||||
|
_, err = fmt.Fprintf(w, "/* unknown wire type %d */", wire)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeUnknownInt(w *textWriter, x uint64, err error) error {
|
||||||
|
if err == nil {
|
||||||
|
_, err = fmt.Fprint(w, x)
|
||||||
|
} else {
|
||||||
|
_, err = fmt.Fprintf(w, "/* %v */", err)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type int32Slice []int32
|
||||||
|
|
||||||
|
func (s int32Slice) Len() int { return len(s) }
|
||||||
|
func (s int32Slice) Less(i, j int) bool { return s[i] < s[j] }
|
||||||
|
func (s int32Slice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
|
// writeExtensions writes all the extensions in pv.
|
||||||
|
// pv is assumed to be a pointer to a protocol message struct that is extendable.
|
||||||
|
func (tm *TextMarshaler) writeExtensions(w *textWriter, pv reflect.Value) error {
|
||||||
|
emap := extensionMaps[pv.Type().Elem()]
|
||||||
|
ep, _ := extendable(pv.Interface())
|
||||||
|
|
||||||
|
// Order the extensions by ID.
|
||||||
|
// This isn't strictly necessary, but it will give us
|
||||||
|
// canonical output, which will also make testing easier.
|
||||||
|
m, mu := ep.extensionsRead()
|
||||||
|
if m == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mu.Lock()
|
||||||
|
ids := make([]int32, 0, len(m))
|
||||||
|
for id := range m {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
sort.Sort(int32Slice(ids))
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
for _, extNum := range ids {
|
||||||
|
ext := m[extNum]
|
||||||
|
var desc *ExtensionDesc
|
||||||
|
if emap != nil {
|
||||||
|
desc = emap[extNum]
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
// Unknown extension.
|
||||||
|
if err := writeUnknownStruct(w, ext.enc); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pb, err := GetExtension(ep, desc)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed getting extension: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repeated extensions will appear as a slice.
|
||||||
|
if !desc.repeated() {
|
||||||
|
if err := tm.writeExtension(w, desc.Name, pb); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
v := reflect.ValueOf(pb)
|
||||||
|
for i := 0; i < v.Len(); i++ {
|
||||||
|
if err := tm.writeExtension(w, desc.Name, v.Index(i).Interface()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tm *TextMarshaler) writeExtension(w *textWriter, name string, pb interface{}) error {
|
||||||
|
if _, err := fmt.Fprintf(w, "[%s]:", name); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !w.compact {
|
||||||
|
if err := w.WriteByte(' '); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := tm.writeAny(w, reflect.ValueOf(pb), nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := w.WriteByte('\n'); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *textWriter) writeIndent() {
|
||||||
|
if !w.complete {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
remain := w.ind * 2
|
||||||
|
for remain > 0 {
|
||||||
|
n := remain
|
||||||
|
if n > len(spaces) {
|
||||||
|
n = len(spaces)
|
||||||
|
}
|
||||||
|
w.w.Write(spaces[:n])
|
||||||
|
remain -= n
|
||||||
|
}
|
||||||
|
w.complete = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextMarshaler is a configurable text format marshaler.
|
||||||
|
type TextMarshaler struct {
|
||||||
|
Compact bool // use compact text format (one line).
|
||||||
|
ExpandAny bool // expand google.protobuf.Any messages of known types
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal writes a given protocol buffer in text format.
|
||||||
|
// The only errors returned are from w.
|
||||||
|
func (tm *TextMarshaler) Marshal(w io.Writer, pb Message) error {
|
||||||
|
val := reflect.ValueOf(pb)
|
||||||
|
if pb == nil || val.IsNil() {
|
||||||
|
w.Write([]byte("<nil>"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var bw *bufio.Writer
|
||||||
|
ww, ok := w.(writer)
|
||||||
|
if !ok {
|
||||||
|
bw = bufio.NewWriter(w)
|
||||||
|
ww = bw
|
||||||
|
}
|
||||||
|
aw := &textWriter{
|
||||||
|
w: ww,
|
||||||
|
complete: true,
|
||||||
|
compact: tm.Compact,
|
||||||
|
}
|
||||||
|
|
||||||
|
if etm, ok := pb.(encoding.TextMarshaler); ok {
|
||||||
|
text, err := etm.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = aw.Write(text); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bw != nil {
|
||||||
|
return bw.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Dereference the received pointer so we don't have outer < and >.
|
||||||
|
v := reflect.Indirect(val)
|
||||||
|
if err := tm.writeStruct(aw, v); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if bw != nil {
|
||||||
|
return bw.Flush()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Text is the same as Marshal, but returns the string directly.
|
||||||
|
func (tm *TextMarshaler) Text(pb Message) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
tm.Marshal(&buf, pb)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultTextMarshaler = TextMarshaler{}
|
||||||
|
compactTextMarshaler = TextMarshaler{Compact: true}
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: consider removing some of the Marshal functions below.
|
||||||
|
|
||||||
|
// MarshalText writes a given protocol buffer in text format.
|
||||||
|
// The only errors returned are from w.
|
||||||
|
func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }
|
||||||
|
|
||||||
|
// MarshalTextString is the same as MarshalText, but returns the string directly.
|
||||||
|
func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }
|
||||||
|
|
||||||
|
// CompactText writes a given protocol buffer in compact text format (one line).
|
||||||
|
func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }
|
||||||
|
|
||||||
|
// CompactTextString is the same as CompactText, but returns the string directly.
|
||||||
|
func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }
|
895
vendor/github.com/golang/protobuf/proto/text_parser.go
generated
vendored
Normal file
895
vendor/github.com/golang/protobuf/proto/text_parser.go
generated
vendored
Normal file
@ -0,0 +1,895 @@
|
|||||||
|
// Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
//
|
||||||
|
// Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
// https://github.com/golang/protobuf
|
||||||
|
//
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * 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.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived from
|
||||||
|
// this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "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 COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
package proto
|
||||||
|
|
||||||
|
// Functions for parsing the Text protocol buffer format.
|
||||||
|
// TODO: message sets.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error string emitted when deserializing Any and fields are already set
|
||||||
|
const anyRepeatedlyUnpacked = "Any message unpacked multiple times, or %q already set"
|
||||||
|
|
||||||
|
type ParseError struct {
|
||||||
|
Message string
|
||||||
|
Line int // 1-based line number
|
||||||
|
Offset int // 0-based byte offset from start of input
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ParseError) Error() string {
|
||||||
|
if p.Line == 1 {
|
||||||
|
// show offset only for first line
|
||||||
|
return fmt.Sprintf("line 1.%d: %v", p.Offset, p.Message)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("line %d: %v", p.Line, p.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
type token struct {
|
||||||
|
value string
|
||||||
|
err *ParseError
|
||||||
|
line int // line number
|
||||||
|
offset int // byte number from start of input, not start of line
|
||||||
|
unquoted string // the unquoted version of value, if it was a quoted string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *token) String() string {
|
||||||
|
if t.err == nil {
|
||||||
|
return fmt.Sprintf("%q (line=%d, offset=%d)", t.value, t.line, t.offset)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("parse error: %v", t.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type textParser struct {
|
||||||
|
s string // remaining input
|
||||||
|
done bool // whether the parsing is finished (success or error)
|
||||||
|
backed bool // whether back() was called
|
||||||
|
offset, line int
|
||||||
|
cur token
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTextParser(s string) *textParser {
|
||||||
|
p := new(textParser)
|
||||||
|
p.s = s
|
||||||
|
p.line = 1
|
||||||
|
p.cur.line = 1
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) errorf(format string, a ...interface{}) *ParseError {
|
||||||
|
pe := &ParseError{fmt.Sprintf(format, a...), p.cur.line, p.cur.offset}
|
||||||
|
p.cur.err = pe
|
||||||
|
p.done = true
|
||||||
|
return pe
|
||||||
|
}
|
||||||
|
|
||||||
|
// Numbers and identifiers are matched by [-+._A-Za-z0-9]
|
||||||
|
func isIdentOrNumberChar(c byte) bool {
|
||||||
|
switch {
|
||||||
|
case 'A' <= c && c <= 'Z', 'a' <= c && c <= 'z':
|
||||||
|
return true
|
||||||
|
case '0' <= c && c <= '9':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
switch c {
|
||||||
|
case '-', '+', '.', '_':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isWhitespace(c byte) bool {
|
||||||
|
switch c {
|
||||||
|
case ' ', '\t', '\n', '\r':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func isQuote(c byte) bool {
|
||||||
|
switch c {
|
||||||
|
case '"', '\'':
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) skipWhitespace() {
|
||||||
|
i := 0
|
||||||
|
for i < len(p.s) && (isWhitespace(p.s[i]) || p.s[i] == '#') {
|
||||||
|
if p.s[i] == '#' {
|
||||||
|
// comment; skip to end of line or input
|
||||||
|
for i < len(p.s) && p.s[i] != '\n' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == len(p.s) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p.s[i] == '\n' {
|
||||||
|
p.line++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
p.offset += i
|
||||||
|
p.s = p.s[i:len(p.s)]
|
||||||
|
if len(p.s) == 0 {
|
||||||
|
p.done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) advance() {
|
||||||
|
// Skip whitespace
|
||||||
|
p.skipWhitespace()
|
||||||
|
if p.done {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start of non-whitespace
|
||||||
|
p.cur.err = nil
|
||||||
|
p.cur.offset, p.cur.line = p.offset, p.line
|
||||||
|
p.cur.unquoted = ""
|
||||||
|
switch p.s[0] {
|
||||||
|
case '<', '>', '{', '}', ':', '[', ']', ';', ',', '/':
|
||||||
|
// Single symbol
|
||||||
|
p.cur.value, p.s = p.s[0:1], p.s[1:len(p.s)]
|
||||||
|
case '"', '\'':
|
||||||
|
// Quoted string
|
||||||
|
i := 1
|
||||||
|
for i < len(p.s) && p.s[i] != p.s[0] && p.s[i] != '\n' {
|
||||||
|
if p.s[i] == '\\' && i+1 < len(p.s) {
|
||||||
|
// skip escaped char
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i >= len(p.s) || p.s[i] != p.s[0] {
|
||||||
|
p.errorf("unmatched quote")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
unq, err := unquoteC(p.s[1:i], rune(p.s[0]))
|
||||||
|
if err != nil {
|
||||||
|
p.errorf("invalid quoted string %s: %v", p.s[0:i+1], err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cur.value, p.s = p.s[0:i+1], p.s[i+1:len(p.s)]
|
||||||
|
p.cur.unquoted = unq
|
||||||
|
default:
|
||||||
|
i := 0
|
||||||
|
for i < len(p.s) && isIdentOrNumberChar(p.s[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
p.errorf("unexpected byte %#x", p.s[0])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.cur.value, p.s = p.s[0:i], p.s[i:len(p.s)]
|
||||||
|
}
|
||||||
|
p.offset += len(p.cur.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
errBadUTF8 = errors.New("proto: bad UTF-8")
|
||||||
|
errBadHex = errors.New("proto: bad hexadecimal")
|
||||||
|
)
|
||||||
|
|
||||||
|
func unquoteC(s string, quote rune) (string, error) {
|
||||||
|
// This is based on C++'s tokenizer.cc.
|
||||||
|
// Despite its name, this is *not* parsing C syntax.
|
||||||
|
// For instance, "\0" is an invalid quoted string.
|
||||||
|
|
||||||
|
// Avoid allocation in trivial cases.
|
||||||
|
simple := true
|
||||||
|
for _, r := range s {
|
||||||
|
if r == '\\' || r == quote {
|
||||||
|
simple = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if simple {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 0, 3*len(s)/2)
|
||||||
|
for len(s) > 0 {
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && n == 1 {
|
||||||
|
return "", errBadUTF8
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
if r != '\\' {
|
||||||
|
if r < utf8.RuneSelf {
|
||||||
|
buf = append(buf, byte(r))
|
||||||
|
} else {
|
||||||
|
buf = append(buf, string(r)...)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, tail, err := unescape(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buf = append(buf, ch...)
|
||||||
|
s = tail
|
||||||
|
}
|
||||||
|
return string(buf), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func unescape(s string) (ch string, tail string, err error) {
|
||||||
|
r, n := utf8.DecodeRuneInString(s)
|
||||||
|
if r == utf8.RuneError && n == 1 {
|
||||||
|
return "", "", errBadUTF8
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
switch r {
|
||||||
|
case 'a':
|
||||||
|
return "\a", s, nil
|
||||||
|
case 'b':
|
||||||
|
return "\b", s, nil
|
||||||
|
case 'f':
|
||||||
|
return "\f", s, nil
|
||||||
|
case 'n':
|
||||||
|
return "\n", s, nil
|
||||||
|
case 'r':
|
||||||
|
return "\r", s, nil
|
||||||
|
case 't':
|
||||||
|
return "\t", s, nil
|
||||||
|
case 'v':
|
||||||
|
return "\v", s, nil
|
||||||
|
case '?':
|
||||||
|
return "?", s, nil // trigraph workaround
|
||||||
|
case '\'', '"', '\\':
|
||||||
|
return string(r), s, nil
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', 'x', 'X':
|
||||||
|
if len(s) < 2 {
|
||||||
|
return "", "", fmt.Errorf(`\%c requires 2 following digits`, r)
|
||||||
|
}
|
||||||
|
base := 8
|
||||||
|
ss := s[:2]
|
||||||
|
s = s[2:]
|
||||||
|
if r == 'x' || r == 'X' {
|
||||||
|
base = 16
|
||||||
|
} else {
|
||||||
|
ss = string(r) + ss
|
||||||
|
}
|
||||||
|
i, err := strconv.ParseUint(ss, base, 8)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", err
|
||||||
|
}
|
||||||
|
return string([]byte{byte(i)}), s, nil
|
||||||
|
case 'u', 'U':
|
||||||
|
n := 4
|
||||||
|
if r == 'U' {
|
||||||
|
n = 8
|
||||||
|
}
|
||||||
|
if len(s) < n {
|
||||||
|
return "", "", fmt.Errorf(`\%c requires %d digits`, r, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
bs := make([]byte, n/2)
|
||||||
|
for i := 0; i < n; i += 2 {
|
||||||
|
a, ok1 := unhex(s[i])
|
||||||
|
b, ok2 := unhex(s[i+1])
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
return "", "", errBadHex
|
||||||
|
}
|
||||||
|
bs[i/2] = a<<4 | b
|
||||||
|
}
|
||||||
|
s = s[n:]
|
||||||
|
return string(bs), s, nil
|
||||||
|
}
|
||||||
|
return "", "", fmt.Errorf(`unknown escape \%c`, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from src/pkg/strconv/quote.go.
|
||||||
|
func unhex(b byte) (v byte, ok bool) {
|
||||||
|
switch {
|
||||||
|
case '0' <= b && b <= '9':
|
||||||
|
return b - '0', true
|
||||||
|
case 'a' <= b && b <= 'f':
|
||||||
|
return b - 'a' + 10, true
|
||||||
|
case 'A' <= b && b <= 'F':
|
||||||
|
return b - 'A' + 10, true
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back off the parser by one token. Can only be done between calls to next().
|
||||||
|
// It makes the next advance() a no-op.
|
||||||
|
func (p *textParser) back() { p.backed = true }
|
||||||
|
|
||||||
|
// Advances the parser and returns the new current token.
|
||||||
|
func (p *textParser) next() *token {
|
||||||
|
if p.backed || p.done {
|
||||||
|
p.backed = false
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
p.advance()
|
||||||
|
if p.done {
|
||||||
|
p.cur.value = ""
|
||||||
|
} else if len(p.cur.value) > 0 && isQuote(p.cur.value[0]) {
|
||||||
|
// Look for multiple quoted strings separated by whitespace,
|
||||||
|
// and concatenate them.
|
||||||
|
cat := p.cur
|
||||||
|
for {
|
||||||
|
p.skipWhitespace()
|
||||||
|
if p.done || !isQuote(p.s[0]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
p.advance()
|
||||||
|
if p.cur.err != nil {
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
cat.value += " " + p.cur.value
|
||||||
|
cat.unquoted += p.cur.unquoted
|
||||||
|
}
|
||||||
|
p.done = false // parser may have seen EOF, but we want to return cat
|
||||||
|
p.cur = cat
|
||||||
|
}
|
||||||
|
return &p.cur
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) consumeToken(s string) error {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != s {
|
||||||
|
p.back()
|
||||||
|
return p.errorf("expected %q, found %q", s, tok.value)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a RequiredNotSetError indicating which required field was not set.
|
||||||
|
func (p *textParser) missingRequiredFieldError(sv reflect.Value) *RequiredNotSetError {
|
||||||
|
st := sv.Type()
|
||||||
|
sprops := GetProperties(st)
|
||||||
|
for i := 0; i < st.NumField(); i++ {
|
||||||
|
if !isNil(sv.Field(i)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
props := sprops.Prop[i]
|
||||||
|
if props.Required {
|
||||||
|
return &RequiredNotSetError{fmt.Sprintf("%v.%v", st, props.OrigName)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &RequiredNotSetError{fmt.Sprintf("%v.<unknown field name>", st)} // should not happen
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the index in the struct for the named field, as well as the parsed tag properties.
|
||||||
|
func structFieldByName(sprops *StructProperties, name string) (int, *Properties, bool) {
|
||||||
|
i, ok := sprops.decoderOrigNames[name]
|
||||||
|
if ok {
|
||||||
|
return i, sprops.Prop[i], true
|
||||||
|
}
|
||||||
|
return -1, nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume a ':' from the input stream (if the next token is a colon),
|
||||||
|
// returning an error if a colon is needed but not present.
|
||||||
|
func (p *textParser) checkForColon(props *Properties, typ reflect.Type) *ParseError {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != ":" {
|
||||||
|
// Colon is optional when the field is a group or message.
|
||||||
|
needColon := true
|
||||||
|
switch props.Wire {
|
||||||
|
case "group":
|
||||||
|
needColon = false
|
||||||
|
case "bytes":
|
||||||
|
// A "bytes" field is either a message, a string, or a repeated field;
|
||||||
|
// those three become *T, *string and []T respectively, so we can check for
|
||||||
|
// this field being a pointer to a non-string.
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
// *T or *string
|
||||||
|
if typ.Elem().Kind() == reflect.String {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if typ.Kind() == reflect.Slice {
|
||||||
|
// []T or []*T
|
||||||
|
if typ.Elem().Kind() != reflect.Ptr {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else if typ.Kind() == reflect.String {
|
||||||
|
// The proto3 exception is for a string field,
|
||||||
|
// which requires a colon.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
needColon = false
|
||||||
|
}
|
||||||
|
if needColon {
|
||||||
|
return p.errorf("expected ':', found %q", tok.value)
|
||||||
|
}
|
||||||
|
p.back()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) readStruct(sv reflect.Value, terminator string) error {
|
||||||
|
st := sv.Type()
|
||||||
|
sprops := GetProperties(st)
|
||||||
|
reqCount := sprops.reqCount
|
||||||
|
var reqFieldErr error
|
||||||
|
fieldSet := make(map[string]bool)
|
||||||
|
// A struct is a sequence of "name: value", terminated by one of
|
||||||
|
// '>' or '}', or the end of the input. A name may also be
|
||||||
|
// "[extension]" or "[type/url]".
|
||||||
|
//
|
||||||
|
// The whole struct can also be an expanded Any message, like:
|
||||||
|
// [type/url] < ... struct contents ... >
|
||||||
|
for {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value == terminator {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tok.value == "[" {
|
||||||
|
// Looks like an extension or an Any.
|
||||||
|
//
|
||||||
|
// TODO: Check whether we need to handle
|
||||||
|
// namespace rooted names (e.g. ".something.Foo").
|
||||||
|
extName, err := p.consumeExtName()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if s := strings.LastIndex(extName, "/"); s >= 0 {
|
||||||
|
// If it contains a slash, it's an Any type URL.
|
||||||
|
messageName := extName[s+1:]
|
||||||
|
mt := MessageType(messageName)
|
||||||
|
if mt == nil {
|
||||||
|
return p.errorf("unrecognized message %q in google.protobuf.Any", messageName)
|
||||||
|
}
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
// consume an optional colon
|
||||||
|
if tok.value == ":" {
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
default:
|
||||||
|
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
v := reflect.New(mt.Elem())
|
||||||
|
if pe := p.readStruct(v.Elem(), terminator); pe != nil {
|
||||||
|
return pe
|
||||||
|
}
|
||||||
|
b, err := Marshal(v.Interface().(Message))
|
||||||
|
if err != nil {
|
||||||
|
return p.errorf("failed to marshal message of type %q: %v", messageName, err)
|
||||||
|
}
|
||||||
|
if fieldSet["type_url"] {
|
||||||
|
return p.errorf(anyRepeatedlyUnpacked, "type_url")
|
||||||
|
}
|
||||||
|
if fieldSet["value"] {
|
||||||
|
return p.errorf(anyRepeatedlyUnpacked, "value")
|
||||||
|
}
|
||||||
|
sv.FieldByName("TypeUrl").SetString(extName)
|
||||||
|
sv.FieldByName("Value").SetBytes(b)
|
||||||
|
fieldSet["type_url"] = true
|
||||||
|
fieldSet["value"] = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var desc *ExtensionDesc
|
||||||
|
// This could be faster, but it's functional.
|
||||||
|
// TODO: Do something smarter than a linear scan.
|
||||||
|
for _, d := range RegisteredExtensions(reflect.New(st).Interface().(Message)) {
|
||||||
|
if d.Name == extName {
|
||||||
|
desc = d
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if desc == nil {
|
||||||
|
return p.errorf("unrecognized extension %q", extName)
|
||||||
|
}
|
||||||
|
|
||||||
|
props := &Properties{}
|
||||||
|
props.Parse(desc.Tag)
|
||||||
|
|
||||||
|
typ := reflect.TypeOf(desc.ExtensionType)
|
||||||
|
if err := p.checkForColon(props, typ); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rep := desc.repeated()
|
||||||
|
|
||||||
|
// Read the extension structure, and set it in
|
||||||
|
// the value we're constructing.
|
||||||
|
var ext reflect.Value
|
||||||
|
if !rep {
|
||||||
|
ext = reflect.New(typ).Elem()
|
||||||
|
} else {
|
||||||
|
ext = reflect.New(typ.Elem()).Elem()
|
||||||
|
}
|
||||||
|
if err := p.readAny(ext, props); err != nil {
|
||||||
|
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqFieldErr = err
|
||||||
|
}
|
||||||
|
ep := sv.Addr().Interface().(Message)
|
||||||
|
if !rep {
|
||||||
|
SetExtension(ep, desc, ext.Interface())
|
||||||
|
} else {
|
||||||
|
old, err := GetExtension(ep, desc)
|
||||||
|
var sl reflect.Value
|
||||||
|
if err == nil {
|
||||||
|
sl = reflect.ValueOf(old) // existing slice
|
||||||
|
} else {
|
||||||
|
sl = reflect.MakeSlice(typ, 0, 1)
|
||||||
|
}
|
||||||
|
sl = reflect.Append(sl, ext)
|
||||||
|
SetExtension(ep, desc, sl.Interface())
|
||||||
|
}
|
||||||
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is a normal, non-extension field.
|
||||||
|
name := tok.value
|
||||||
|
var dst reflect.Value
|
||||||
|
fi, props, ok := structFieldByName(sprops, name)
|
||||||
|
if ok {
|
||||||
|
dst = sv.Field(fi)
|
||||||
|
} else if oop, ok := sprops.OneofTypes[name]; ok {
|
||||||
|
// It is a oneof.
|
||||||
|
props = oop.Prop
|
||||||
|
nv := reflect.New(oop.Type.Elem())
|
||||||
|
dst = nv.Elem().Field(0)
|
||||||
|
field := sv.Field(oop.Field)
|
||||||
|
if !field.IsNil() {
|
||||||
|
return p.errorf("field '%s' would overwrite already parsed oneof '%s'", name, sv.Type().Field(oop.Field).Name)
|
||||||
|
}
|
||||||
|
field.Set(nv)
|
||||||
|
}
|
||||||
|
if !dst.IsValid() {
|
||||||
|
return p.errorf("unknown field name %q in %v", name, st)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dst.Kind() == reflect.Map {
|
||||||
|
// Consume any colon.
|
||||||
|
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Construct the map if it doesn't already exist.
|
||||||
|
if dst.IsNil() {
|
||||||
|
dst.Set(reflect.MakeMap(dst.Type()))
|
||||||
|
}
|
||||||
|
key := reflect.New(dst.Type().Key()).Elem()
|
||||||
|
val := reflect.New(dst.Type().Elem()).Elem()
|
||||||
|
|
||||||
|
// The map entry should be this sequence of tokens:
|
||||||
|
// < key : KEY value : VALUE >
|
||||||
|
// However, implementations may omit key or value, and technically
|
||||||
|
// we should support them in any order. See b/28924776 for a time
|
||||||
|
// this went wrong.
|
||||||
|
|
||||||
|
tok := p.next()
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
default:
|
||||||
|
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value == terminator {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
switch tok.value {
|
||||||
|
case "key":
|
||||||
|
if err := p.consumeToken(":"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.readAny(key, props.mkeyprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "value":
|
||||||
|
if err := p.checkForColon(props.mvalprop, dst.Type().Elem()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.readAny(val, props.mvalprop); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
p.back()
|
||||||
|
return p.errorf(`expected "key", "value", or %q, found %q`, terminator, tok.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.SetMapIndex(key, val)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that it's not already set if it's not a repeated field.
|
||||||
|
if !props.Repeated && fieldSet[name] {
|
||||||
|
return p.errorf("non-repeated field %q was repeated", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.checkForColon(props, dst.Type()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse into the field.
|
||||||
|
fieldSet[name] = true
|
||||||
|
if err := p.readAny(dst, props); err != nil {
|
||||||
|
if _, ok := err.(*RequiredNotSetError); !ok {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
reqFieldErr = err
|
||||||
|
}
|
||||||
|
if props.Required {
|
||||||
|
reqCount--
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.consumeOptionalSeparator(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqCount > 0 {
|
||||||
|
return p.missingRequiredFieldError(sv)
|
||||||
|
}
|
||||||
|
return reqFieldErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeExtName consumes extension name or expanded Any type URL and the
|
||||||
|
// following ']'. It returns the name or URL consumed.
|
||||||
|
func (p *textParser) consumeExtName() (string, error) {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return "", tok.err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If extension name or type url is quoted, it's a single token.
|
||||||
|
if len(tok.value) > 2 && isQuote(tok.value[0]) && tok.value[len(tok.value)-1] == tok.value[0] {
|
||||||
|
name, err := unquoteC(tok.value[1:len(tok.value)-1], rune(tok.value[0]))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return name, p.consumeToken("]")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume everything up to "]"
|
||||||
|
var parts []string
|
||||||
|
for tok.value != "]" {
|
||||||
|
parts = append(parts, tok.value)
|
||||||
|
tok = p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return "", p.errorf("unrecognized type_url or extension name: %s", tok.err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(parts, ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// consumeOptionalSeparator consumes an optional semicolon or comma.
|
||||||
|
// It is used in readStruct to provide backward compatibility.
|
||||||
|
func (p *textParser) consumeOptionalSeparator() error {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value != ";" && tok.value != "," {
|
||||||
|
p.back()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *textParser) readAny(v reflect.Value, props *Properties) error {
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value == "" {
|
||||||
|
return p.errorf("unexpected EOF")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch fv := v; fv.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
at := v.Type()
|
||||||
|
if at.Elem().Kind() == reflect.Uint8 {
|
||||||
|
// Special case for []byte
|
||||||
|
if tok.value[0] != '"' && tok.value[0] != '\'' {
|
||||||
|
// Deliberately written out here, as the error after
|
||||||
|
// this switch statement would write "invalid []byte: ...",
|
||||||
|
// which is not as user-friendly.
|
||||||
|
return p.errorf("invalid string: %v", tok.value)
|
||||||
|
}
|
||||||
|
bytes := []byte(tok.unquoted)
|
||||||
|
fv.Set(reflect.ValueOf(bytes))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// Repeated field.
|
||||||
|
if tok.value == "[" {
|
||||||
|
// Repeated field with list notation, like [1,2,3].
|
||||||
|
for {
|
||||||
|
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||||
|
err := p.readAny(fv.Index(fv.Len()-1), props)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tok := p.next()
|
||||||
|
if tok.err != nil {
|
||||||
|
return tok.err
|
||||||
|
}
|
||||||
|
if tok.value == "]" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if tok.value != "," {
|
||||||
|
return p.errorf("Expected ']' or ',' found %q", tok.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// One value of the repeated field.
|
||||||
|
p.back()
|
||||||
|
fv.Set(reflect.Append(fv, reflect.New(at.Elem()).Elem()))
|
||||||
|
return p.readAny(fv.Index(fv.Len()-1), props)
|
||||||
|
case reflect.Bool:
|
||||||
|
// true/1/t/True or false/f/0/False.
|
||||||
|
switch tok.value {
|
||||||
|
case "true", "1", "t", "True":
|
||||||
|
fv.SetBool(true)
|
||||||
|
return nil
|
||||||
|
case "false", "0", "f", "False":
|
||||||
|
fv.SetBool(false)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
v := tok.value
|
||||||
|
// Ignore 'f' for compatibility with output generated by C++, but don't
|
||||||
|
// remove 'f' when the value is "-inf" or "inf".
|
||||||
|
if strings.HasSuffix(v, "f") && tok.value != "-inf" && tok.value != "inf" {
|
||||||
|
v = v[:len(v)-1]
|
||||||
|
}
|
||||||
|
if f, err := strconv.ParseFloat(v, fv.Type().Bits()); err == nil {
|
||||||
|
fv.SetFloat(f)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Int32:
|
||||||
|
if x, err := strconv.ParseInt(tok.value, 0, 32); err == nil {
|
||||||
|
fv.SetInt(x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(props.Enum) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m, ok := enumValueMaps[props.Enum]
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
x, ok := m[tok.value]
|
||||||
|
if !ok {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fv.SetInt(int64(x))
|
||||||
|
return nil
|
||||||
|
case reflect.Int64:
|
||||||
|
if x, err := strconv.ParseInt(tok.value, 0, 64); err == nil {
|
||||||
|
fv.SetInt(x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
case reflect.Ptr:
|
||||||
|
// A basic field (indirected through pointer), or a repeated message/group
|
||||||
|
p.back()
|
||||||
|
fv.Set(reflect.New(fv.Type().Elem()))
|
||||||
|
return p.readAny(fv.Elem(), props)
|
||||||
|
case reflect.String:
|
||||||
|
if tok.value[0] == '"' || tok.value[0] == '\'' {
|
||||||
|
fv.SetString(tok.unquoted)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
var terminator string
|
||||||
|
switch tok.value {
|
||||||
|
case "{":
|
||||||
|
terminator = "}"
|
||||||
|
case "<":
|
||||||
|
terminator = ">"
|
||||||
|
default:
|
||||||
|
return p.errorf("expected '{' or '<', found %q", tok.value)
|
||||||
|
}
|
||||||
|
// TODO: Handle nested messages which implement encoding.TextUnmarshaler.
|
||||||
|
return p.readStruct(fv, terminator)
|
||||||
|
case reflect.Uint32:
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 32); err == nil {
|
||||||
|
fv.SetUint(x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case reflect.Uint64:
|
||||||
|
if x, err := strconv.ParseUint(tok.value, 0, 64); err == nil {
|
||||||
|
fv.SetUint(x)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return p.errorf("invalid %v: %v", v.Type(), tok.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalText reads a protocol buffer in Text format. UnmarshalText resets pb
|
||||||
|
// before starting to unmarshal, so any existing data in pb is always removed.
|
||||||
|
// If a required field is not set and no other error occurs,
|
||||||
|
// UnmarshalText returns *RequiredNotSetError.
|
||||||
|
func UnmarshalText(s string, pb Message) error {
|
||||||
|
if um, ok := pb.(encoding.TextUnmarshaler); ok {
|
||||||
|
err := um.UnmarshalText([]byte(s))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pb.Reset()
|
||||||
|
v := reflect.ValueOf(pb)
|
||||||
|
if pe := newTextParser(s).readStruct(v.Elem(), ""); pe != nil {
|
||||||
|
return pe
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
36
vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile
generated
vendored
Normal file
36
vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/Makefile
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Go support for Protocol Buffers - Google's data interchange format
|
||||||
|
#
|
||||||
|
# Copyright 2010 The Go Authors. All rights reserved.
|
||||||
|
# https://github.com/golang/protobuf
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without
|
||||||
|
# modification, are permitted provided that the following conditions are
|
||||||
|
# met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * 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.
|
||||||
|
# * Neither the name of Google Inc. nor the names of its
|
||||||
|
# contributors may be used to endorse or promote products derived from
|
||||||
|
# this software without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
# "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 COPYRIGHT
|
||||||
|
# OWNER OR CONTRIBUTORS 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.
|
||||||
|
|
||||||
|
# Not stored here, but descriptor.proto is in https://github.com/google/protobuf/
|
||||||
|
# at src/google/protobuf/descriptor.proto
|
||||||
|
regenerate:
|
||||||
|
@echo WARNING! THIS RULE IS PROBABLY NOT RIGHT FOR YOUR INSTALLATION
|
||||||
|
protoc --go_out=../../../../.. -I$(HOME)/src/protobuf/include $(HOME)/src/protobuf/include/google/protobuf/descriptor.proto
|
2152
vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go
generated
vendored
Normal file
2152
vendor/github.com/golang/protobuf/protoc-gen-go/descriptor/descriptor.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
12
vendor/vendor.json
vendored
12
vendor/vendor.json
vendored
@ -104,6 +104,18 @@
|
|||||||
"revision": "54be5f394ed2c3e19dac9134a40a95ba5a017f7b",
|
"revision": "54be5f394ed2c3e19dac9134a40a95ba5a017f7b",
|
||||||
"revisionTime": "2017-07-10T16:04:46Z"
|
"revisionTime": "2017-07-10T16:04:46Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "yqF125xVSkmfLpIVGrLlfE05IUk=",
|
||||||
|
"path": "github.com/golang/protobuf/proto",
|
||||||
|
"revision": "748d386b5c1ea99658fd69fe9f03991ce86a90c1",
|
||||||
|
"revisionTime": "2017-07-26T21:28:29Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "Z1gJ3PKzwBpOoPnTSEM5yd0zHYA=",
|
||||||
|
"path": "github.com/golang/protobuf/protoc-gen-go/descriptor",
|
||||||
|
"revision": "748d386b5c1ea99658fd69fe9f03991ce86a90c1",
|
||||||
|
"revisionTime": "2017-07-26T21:28:29Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "p/8vSviYF91gFflhrt5vkyksroo=",
|
"checksumSHA1": "p/8vSviYF91gFflhrt5vkyksroo=",
|
||||||
"path": "github.com/golang/snappy",
|
"path": "github.com/golang/snappy",
|
||||||
|
Loading…
Reference in New Issue
Block a user