mirror of https://github.com/ethereum/go-ethereum
commit
0db4a0e898
@ -0,0 +1,6 @@ |
|||||||
|
language: go |
||||||
|
go: 1.1 |
||||||
|
|
||||||
|
script: |
||||||
|
- go vet ./... |
||||||
|
- go test -v ./... |
@ -0,0 +1,21 @@ |
|||||||
|
Copyright (C) 2013 Jeremy Saenz |
||||||
|
All Rights Reserved. |
||||||
|
|
||||||
|
MIT LICENSE |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||||
|
this software and associated documentation files (the "Software"), to deal in |
||||||
|
the Software without restriction, including without limitation the rights to |
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so, |
||||||
|
subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,298 @@ |
|||||||
|
[![Build Status](https://travis-ci.org/codegangsta/cli.png?branch=master)](https://travis-ci.org/codegangsta/cli) |
||||||
|
|
||||||
|
# cli.go |
||||||
|
cli.go is simple, fast, and fun package for building command line apps in Go. The goal is to enable developers to write fast and distributable command line applications in an expressive way. |
||||||
|
|
||||||
|
You can view the API docs here: |
||||||
|
http://godoc.org/github.com/codegangsta/cli |
||||||
|
|
||||||
|
## Overview |
||||||
|
Command line apps are usually so tiny that there is absolutely no reason why your code should *not* be self-documenting. Things like generating help text and parsing command flags/options should not hinder productivity when writing a command line app. |
||||||
|
|
||||||
|
**This is where cli.go comes into play.** cli.go makes command line programming fun, organized, and expressive! |
||||||
|
|
||||||
|
## Installation |
||||||
|
Make sure you have a working Go environment (go 1.1 is *required*). [See the install instructions](http://golang.org/doc/install.html). |
||||||
|
|
||||||
|
To install `cli.go`, simply run: |
||||||
|
``` |
||||||
|
$ go get github.com/codegangsta/cli |
||||||
|
``` |
||||||
|
|
||||||
|
Make sure your `PATH` includes to the `$GOPATH/bin` directory so your commands can be easily used: |
||||||
|
``` |
||||||
|
export PATH=$PATH:$GOPATH/bin |
||||||
|
``` |
||||||
|
|
||||||
|
## Getting Started |
||||||
|
One of the philosophies behind cli.go is that an API should be playful and full of discovery. So a cli.go app can be as little as one line of code in `main()`. |
||||||
|
|
||||||
|
``` go |
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
cli.NewApp().Run(os.Args) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
This app will run and show help text, but is not very useful. Let's give an action to execute and some help documentation: |
||||||
|
|
||||||
|
``` go |
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "boom" |
||||||
|
app.Usage = "make an explosive entrance" |
||||||
|
app.Action = func(c *cli.Context) { |
||||||
|
println("boom! I say!") |
||||||
|
} |
||||||
|
|
||||||
|
app.Run(os.Args) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Running this already gives you a ton of functionality, plus support for things like subcommands and flags, which are covered below. |
||||||
|
|
||||||
|
## Example |
||||||
|
|
||||||
|
Being a programmer can be a lonely job. Thankfully by the power of automation that is not the case! Let's create a greeter app to fend off our demons of loneliness! |
||||||
|
|
||||||
|
Start by creating a directory named `greet`, and within it, add a file, `greet.go` with the following code in it: |
||||||
|
|
||||||
|
``` go |
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "greet" |
||||||
|
app.Usage = "fight the loneliness!" |
||||||
|
app.Action = func(c *cli.Context) { |
||||||
|
println("Hello friend!") |
||||||
|
} |
||||||
|
|
||||||
|
app.Run(os.Args) |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
Install our command to the `$GOPATH/bin` directory: |
||||||
|
|
||||||
|
``` |
||||||
|
$ go install |
||||||
|
``` |
||||||
|
|
||||||
|
Finally run our new command: |
||||||
|
|
||||||
|
``` |
||||||
|
$ greet |
||||||
|
Hello friend! |
||||||
|
``` |
||||||
|
|
||||||
|
cli.go also generates some bitchass help text: |
||||||
|
``` |
||||||
|
$ greet help |
||||||
|
NAME: |
||||||
|
greet - fight the loneliness! |
||||||
|
|
||||||
|
USAGE: |
||||||
|
greet [global options] command [command options] [arguments...] |
||||||
|
|
||||||
|
VERSION: |
||||||
|
0.0.0 |
||||||
|
|
||||||
|
COMMANDS: |
||||||
|
help, h Shows a list of commands or help for one command |
||||||
|
|
||||||
|
GLOBAL OPTIONS |
||||||
|
--version Shows version information |
||||||
|
``` |
||||||
|
|
||||||
|
### Arguments |
||||||
|
You can lookup arguments by calling the `Args` function on `cli.Context`. |
||||||
|
|
||||||
|
``` go |
||||||
|
... |
||||||
|
app.Action = func(c *cli.Context) { |
||||||
|
println("Hello", c.Args()[0]) |
||||||
|
} |
||||||
|
... |
||||||
|
``` |
||||||
|
|
||||||
|
### Flags |
||||||
|
Setting and querying flags is simple. |
||||||
|
``` go |
||||||
|
... |
||||||
|
app.Flags = []cli.Flag { |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "lang", |
||||||
|
Value: "english", |
||||||
|
Usage: "language for the greeting", |
||||||
|
}, |
||||||
|
} |
||||||
|
app.Action = func(c *cli.Context) { |
||||||
|
name := "someone" |
||||||
|
if len(c.Args()) > 0 { |
||||||
|
name = c.Args()[0] |
||||||
|
} |
||||||
|
if c.String("lang") == "spanish" { |
||||||
|
println("Hola", name) |
||||||
|
} else { |
||||||
|
println("Hello", name) |
||||||
|
} |
||||||
|
} |
||||||
|
... |
||||||
|
``` |
||||||
|
|
||||||
|
#### Alternate Names |
||||||
|
|
||||||
|
You can set alternate (or short) names for flags by providing a comma-delimited list for the `Name`. e.g. |
||||||
|
|
||||||
|
``` go |
||||||
|
app.Flags = []cli.Flag { |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "lang, l", |
||||||
|
Value: "english", |
||||||
|
Usage: "language for the greeting", |
||||||
|
}, |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
That flag can then be set with `--lang spanish` or `-l spanish`. Note that giving two different forms of the same flag in the same command invocation is an error. |
||||||
|
|
||||||
|
#### Values from the Environment |
||||||
|
|
||||||
|
You can also have the default value set from the environment via `EnvVar`. e.g. |
||||||
|
|
||||||
|
``` go |
||||||
|
app.Flags = []cli.Flag { |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "lang, l", |
||||||
|
Value: "english", |
||||||
|
Usage: "language for the greeting", |
||||||
|
EnvVar: "APP_LANG", |
||||||
|
}, |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
The `EnvVar` may also be given as a comma-delimited "cascade", where the first environment variable that resolves is used as the default. |
||||||
|
|
||||||
|
``` go |
||||||
|
app.Flags = []cli.Flag { |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "lang, l", |
||||||
|
Value: "english", |
||||||
|
Usage: "language for the greeting", |
||||||
|
EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", |
||||||
|
}, |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
### Subcommands |
||||||
|
|
||||||
|
Subcommands can be defined for a more git-like command line app. |
||||||
|
```go |
||||||
|
... |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "add", |
||||||
|
ShortName: "a", |
||||||
|
Usage: "add a task to the list", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("added task: ", c.Args().First()) |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "complete", |
||||||
|
ShortName: "c", |
||||||
|
Usage: "complete a task on the list", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("completed task: ", c.Args().First()) |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "template", |
||||||
|
ShortName: "r", |
||||||
|
Usage: "options for task templates", |
||||||
|
Subcommands: []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "add", |
||||||
|
Usage: "add a new template", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("new task template: ", c.Args().First()) |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "remove", |
||||||
|
Usage: "remove an existing template", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("removed task template: ", c.Args().First()) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
... |
||||||
|
``` |
||||||
|
|
||||||
|
### Bash Completion |
||||||
|
|
||||||
|
You can enable completion commands by setting the `EnableBashCompletion` |
||||||
|
flag on the `App` object. By default, this setting will only auto-complete to |
||||||
|
show an app's subcommands, but you can write your own completion methods for |
||||||
|
the App or its subcommands. |
||||||
|
```go |
||||||
|
... |
||||||
|
var tasks = []string{"cook", "clean", "laundry", "eat", "sleep", "code"} |
||||||
|
app := cli.NewApp() |
||||||
|
app.EnableBashCompletion = true |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "complete", |
||||||
|
ShortName: "c", |
||||||
|
Usage: "complete a task on the list", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("completed task: ", c.Args().First()) |
||||||
|
}, |
||||||
|
BashComplete: func(c *cli.Context) { |
||||||
|
// This will complete if no args are passed |
||||||
|
if len(c.Args()) > 0 { |
||||||
|
return |
||||||
|
} |
||||||
|
for _, t := range tasks { |
||||||
|
fmt.Println(t) |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
} |
||||||
|
... |
||||||
|
``` |
||||||
|
|
||||||
|
#### To Enable |
||||||
|
|
||||||
|
Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while |
||||||
|
setting the `PROG` variable to the name of your program: |
||||||
|
|
||||||
|
`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` |
||||||
|
|
||||||
|
|
||||||
|
## Contribution Guidelines |
||||||
|
Feel free to put up a pull request to fix a bug or maybe add a feature. I will give it a code review and make sure that it does not break backwards compatibility. If I or any other collaborators agree that it is in line with the vision of the project, we will work with you to get the code into a mergeable state and merge it into the master branch. |
||||||
|
|
||||||
|
If you have contributed something significant to the project, I will most likely add you as a collaborator. As a collaborator you are given the ability to merge others pull requests. It is very important that new code does not break existing code, so be careful about what code you do choose to merge. If you have any questions feel free to link @codegangsta to the issue in question and we can review it together. |
||||||
|
|
||||||
|
If you feel like you have contributed to the project but have not yet been added as a collaborator, I probably forgot to add you. Hit @codegangsta up over email and we will get it figured out. |
@ -0,0 +1,296 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"text/tabwriter" |
||||||
|
"text/template" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// App is the main structure of a cli application. It is recomended that
|
||||||
|
// and app be created with the cli.NewApp() function
|
||||||
|
type App struct { |
||||||
|
// The name of the program. Defaults to os.Args[0]
|
||||||
|
Name string |
||||||
|
// Description of the program.
|
||||||
|
Usage string |
||||||
|
// Version of the program
|
||||||
|
Version string |
||||||
|
// List of commands to execute
|
||||||
|
Commands []Command |
||||||
|
// List of flags to parse
|
||||||
|
Flags []Flag |
||||||
|
// Boolean to enable bash completion commands
|
||||||
|
EnableBashCompletion bool |
||||||
|
// Boolean to hide built-in help command
|
||||||
|
HideHelp bool |
||||||
|
// Boolean to hide built-in version flag
|
||||||
|
HideVersion bool |
||||||
|
// An action to execute when the bash-completion flag is set
|
||||||
|
BashComplete func(context *Context) |
||||||
|
// An action to execute before any subcommands are run, but after the context is ready
|
||||||
|
// If a non-nil error is returned, no subcommands are run
|
||||||
|
Before func(context *Context) error |
||||||
|
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||||
|
// It is run even if Action() panics
|
||||||
|
After func(context *Context) error |
||||||
|
// The action to execute when no subcommands are specified
|
||||||
|
Action func(context *Context) |
||||||
|
// Execute this function if the proper command cannot be found
|
||||||
|
CommandNotFound func(context *Context, command string) |
||||||
|
// Compilation date
|
||||||
|
Compiled time.Time |
||||||
|
// Author
|
||||||
|
Author string |
||||||
|
// Author e-mail
|
||||||
|
Email string |
||||||
|
// Writer writer to write output to
|
||||||
|
Writer io.Writer |
||||||
|
} |
||||||
|
|
||||||
|
// Tries to find out when this binary was compiled.
|
||||||
|
// Returns the current time if it fails to find it.
|
||||||
|
func compileTime() time.Time { |
||||||
|
info, err := os.Stat(os.Args[0]) |
||||||
|
if err != nil { |
||||||
|
return time.Now() |
||||||
|
} |
||||||
|
return info.ModTime() |
||||||
|
} |
||||||
|
|
||||||
|
// Creates a new cli Application with some reasonable defaults for Name, Usage, Version and Action.
|
||||||
|
func NewApp() *App { |
||||||
|
return &App{ |
||||||
|
Name: os.Args[0], |
||||||
|
Usage: "A new cli application", |
||||||
|
Version: "0.0.0", |
||||||
|
BashComplete: DefaultAppComplete, |
||||||
|
Action: helpCommand.Action, |
||||||
|
Compiled: compileTime(), |
||||||
|
Author: "Author", |
||||||
|
Email: "unknown@email", |
||||||
|
Writer: os.Stdout, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Entry point to the cli app. Parses the arguments slice and routes to the proper flag/args combination
|
||||||
|
func (a *App) Run(arguments []string) (err error) { |
||||||
|
if HelpPrinter == nil { |
||||||
|
defer func() { |
||||||
|
HelpPrinter = nil |
||||||
|
}() |
||||||
|
|
||||||
|
HelpPrinter = func(templ string, data interface{}) { |
||||||
|
w := tabwriter.NewWriter(a.Writer, 0, 8, 1, '\t', 0) |
||||||
|
t := template.Must(template.New("help").Parse(templ)) |
||||||
|
err := t.Execute(w, data) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
w.Flush() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// append help to commands
|
||||||
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp { |
||||||
|
a.Commands = append(a.Commands, helpCommand) |
||||||
|
if (HelpFlag != BoolFlag{}) { |
||||||
|
a.appendFlag(HelpFlag) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
//append version/help flags
|
||||||
|
if a.EnableBashCompletion { |
||||||
|
a.appendFlag(BashCompletionFlag) |
||||||
|
} |
||||||
|
|
||||||
|
if !a.HideVersion { |
||||||
|
a.appendFlag(VersionFlag) |
||||||
|
} |
||||||
|
|
||||||
|
// parse flags
|
||||||
|
set := flagSet(a.Name, a.Flags) |
||||||
|
set.SetOutput(ioutil.Discard) |
||||||
|
err = set.Parse(arguments[1:]) |
||||||
|
nerr := normalizeFlags(a.Flags, set) |
||||||
|
if nerr != nil { |
||||||
|
fmt.Fprintln(a.Writer, nerr) |
||||||
|
context := NewContext(a, set, set) |
||||||
|
ShowAppHelp(context) |
||||||
|
fmt.Fprintln(a.Writer) |
||||||
|
return nerr |
||||||
|
} |
||||||
|
context := NewContext(a, set, set) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") |
||||||
|
ShowAppHelp(context) |
||||||
|
fmt.Fprintln(a.Writer) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if checkCompletions(context) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if checkHelp(context) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if checkVersion(context) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if a.After != nil { |
||||||
|
defer func() { |
||||||
|
// err is always nil here.
|
||||||
|
// There is a check to see if it is non-nil
|
||||||
|
// just few lines before.
|
||||||
|
err = a.After(context) |
||||||
|
}() |
||||||
|
} |
||||||
|
|
||||||
|
if a.Before != nil { |
||||||
|
err := a.Before(context) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
args := context.Args() |
||||||
|
if args.Present() { |
||||||
|
name := args.First() |
||||||
|
c := a.Command(name) |
||||||
|
if c != nil { |
||||||
|
return c.Run(context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Run default Action
|
||||||
|
a.Action(context) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Another entry point to the cli app, takes care of passing arguments and error handling
|
||||||
|
func (a *App) RunAndExitOnError() { |
||||||
|
if err := a.Run(os.Args); err != nil { |
||||||
|
fmt.Fprintln(os.Stderr, err) |
||||||
|
os.Exit(1) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Invokes the subcommand given the context, parses ctx.Args() to generate command-specific flags
|
||||||
|
func (a *App) RunAsSubcommand(ctx *Context) (err error) { |
||||||
|
// append help to commands
|
||||||
|
if len(a.Commands) > 0 { |
||||||
|
if a.Command(helpCommand.Name) == nil && !a.HideHelp { |
||||||
|
a.Commands = append(a.Commands, helpCommand) |
||||||
|
if (HelpFlag != BoolFlag{}) { |
||||||
|
a.appendFlag(HelpFlag) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// append flags
|
||||||
|
if a.EnableBashCompletion { |
||||||
|
a.appendFlag(BashCompletionFlag) |
||||||
|
} |
||||||
|
|
||||||
|
// parse flags
|
||||||
|
set := flagSet(a.Name, a.Flags) |
||||||
|
set.SetOutput(ioutil.Discard) |
||||||
|
err = set.Parse(ctx.Args().Tail()) |
||||||
|
nerr := normalizeFlags(a.Flags, set) |
||||||
|
context := NewContext(a, set, ctx.globalSet) |
||||||
|
|
||||||
|
if nerr != nil { |
||||||
|
fmt.Fprintln(a.Writer, nerr) |
||||||
|
if len(a.Commands) > 0 { |
||||||
|
ShowSubcommandHelp(context) |
||||||
|
} else { |
||||||
|
ShowCommandHelp(ctx, context.Args().First()) |
||||||
|
} |
||||||
|
fmt.Fprintln(a.Writer) |
||||||
|
return nerr |
||||||
|
} |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(a.Writer, "Incorrect Usage.\n\n") |
||||||
|
ShowSubcommandHelp(context) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if checkCompletions(context) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if len(a.Commands) > 0 { |
||||||
|
if checkSubcommandHelp(context) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
} else { |
||||||
|
if checkCommandHelp(ctx, context.Args().First()) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if a.After != nil { |
||||||
|
defer func() { |
||||||
|
// err is always nil here.
|
||||||
|
// There is a check to see if it is non-nil
|
||||||
|
// just few lines before.
|
||||||
|
err = a.After(context) |
||||||
|
}() |
||||||
|
} |
||||||
|
|
||||||
|
if a.Before != nil { |
||||||
|
err := a.Before(context) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
args := context.Args() |
||||||
|
if args.Present() { |
||||||
|
name := args.First() |
||||||
|
c := a.Command(name) |
||||||
|
if c != nil { |
||||||
|
return c.Run(context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Run default Action
|
||||||
|
a.Action(context) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the named command on App. Returns nil if the command does not exist
|
||||||
|
func (a *App) Command(name string) *Command { |
||||||
|
for _, c := range a.Commands { |
||||||
|
if c.HasName(name) { |
||||||
|
return &c |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (a *App) hasFlag(flag Flag) bool { |
||||||
|
for _, f := range a.Flags { |
||||||
|
if flag == f { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (a *App) appendFlag(flag Flag) { |
||||||
|
if !a.hasFlag(flag) { |
||||||
|
a.Flags = append(a.Flags, flag) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,619 @@ |
|||||||
|
package cli_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
func ExampleApp() { |
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "--name", "Jeremy"} |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "greet" |
||||||
|
app.Flags = []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, |
||||||
|
} |
||||||
|
app.Action = func(c *cli.Context) { |
||||||
|
fmt.Printf("Hello %v\n", c.String("name")) |
||||||
|
} |
||||||
|
app.Run(os.Args) |
||||||
|
// Output:
|
||||||
|
// Hello Jeremy
|
||||||
|
} |
||||||
|
|
||||||
|
func ExampleAppSubcommand() { |
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"say", "hi", "english", "--name", "Jeremy"} |
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "say" |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "hello", |
||||||
|
ShortName: "hi", |
||||||
|
Usage: "use it to see a description", |
||||||
|
Description: "This is how we describe hello the function", |
||||||
|
Subcommands: []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "english", |
||||||
|
ShortName: "en", |
||||||
|
Usage: "sends a greeting in english", |
||||||
|
Description: "greets someone in english", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "name", |
||||||
|
Value: "Bob", |
||||||
|
Usage: "Name of the person to greet", |
||||||
|
}, |
||||||
|
}, |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
fmt.Println("Hello,", c.String("name")) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Run(os.Args) |
||||||
|
// Output:
|
||||||
|
// Hello, Jeremy
|
||||||
|
} |
||||||
|
|
||||||
|
func ExampleAppHelp() { |
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "h", "describeit"} |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "greet" |
||||||
|
app.Flags = []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "name", Value: "bob", Usage: "a name to say"}, |
||||||
|
} |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "describeit", |
||||||
|
ShortName: "d", |
||||||
|
Usage: "use it to see a description", |
||||||
|
Description: "This is how we describe describeit the function", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
fmt.Printf("i like to describe things") |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
app.Run(os.Args) |
||||||
|
// Output:
|
||||||
|
// NAME:
|
||||||
|
// describeit - use it to see a description
|
||||||
|
//
|
||||||
|
// USAGE:
|
||||||
|
// command describeit [arguments...]
|
||||||
|
//
|
||||||
|
// DESCRIPTION:
|
||||||
|
// This is how we describe describeit the function
|
||||||
|
} |
||||||
|
|
||||||
|
func ExampleAppBashComplete() { |
||||||
|
// set args for examples sake
|
||||||
|
os.Args = []string{"greet", "--generate-bash-completion"} |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "greet" |
||||||
|
app.EnableBashCompletion = true |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "describeit", |
||||||
|
ShortName: "d", |
||||||
|
Usage: "use it to see a description", |
||||||
|
Description: "This is how we describe describeit the function", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
fmt.Printf("i like to describe things") |
||||||
|
}, |
||||||
|
}, { |
||||||
|
Name: "next", |
||||||
|
Usage: "next example", |
||||||
|
Description: "more stuff to see when generating bash completion", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
fmt.Printf("the next example") |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Run(os.Args) |
||||||
|
// Output:
|
||||||
|
// describeit
|
||||||
|
// d
|
||||||
|
// next
|
||||||
|
// help
|
||||||
|
// h
|
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_Run(t *testing.T) { |
||||||
|
s := "" |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
app.Action = func(c *cli.Context) { |
||||||
|
s = s + c.Args().First() |
||||||
|
} |
||||||
|
|
||||||
|
err := app.Run([]string{"command", "foo"}) |
||||||
|
expect(t, err, nil) |
||||||
|
err = app.Run([]string{"command", "bar"}) |
||||||
|
expect(t, err, nil) |
||||||
|
expect(t, s, "foobar") |
||||||
|
} |
||||||
|
|
||||||
|
var commandAppTests = []struct { |
||||||
|
name string |
||||||
|
expected bool |
||||||
|
}{ |
||||||
|
{"foobar", true}, |
||||||
|
{"batbaz", true}, |
||||||
|
{"b", true}, |
||||||
|
{"f", true}, |
||||||
|
{"bat", false}, |
||||||
|
{"nothing", false}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_Command(t *testing.T) { |
||||||
|
app := cli.NewApp() |
||||||
|
fooCommand := cli.Command{Name: "foobar", ShortName: "f"} |
||||||
|
batCommand := cli.Command{Name: "batbaz", ShortName: "b"} |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
fooCommand, |
||||||
|
batCommand, |
||||||
|
} |
||||||
|
|
||||||
|
for _, test := range commandAppTests { |
||||||
|
expect(t, app.Command(test.name) != nil, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_CommandWithArgBeforeFlags(t *testing.T) { |
||||||
|
var parsedOption, firstArg string |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
command := cli.Command{ |
||||||
|
Name: "cmd", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, |
||||||
|
}, |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
parsedOption = c.String("option") |
||||||
|
firstArg = c.Args().First() |
||||||
|
}, |
||||||
|
} |
||||||
|
app.Commands = []cli.Command{command} |
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option"}) |
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option") |
||||||
|
expect(t, firstArg, "my-arg") |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_RunAsSubcommandParseFlags(t *testing.T) { |
||||||
|
var context *cli.Context |
||||||
|
|
||||||
|
a := cli.NewApp() |
||||||
|
a.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "foo", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
context = c |
||||||
|
}, |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "lang", |
||||||
|
Value: "english", |
||||||
|
Usage: "language for the greeting", |
||||||
|
}, |
||||||
|
}, |
||||||
|
Before: func(_ *cli.Context) error { return nil }, |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"", "foo", "--lang", "spanish", "abcd"}) |
||||||
|
|
||||||
|
expect(t, context.Args().Get(0), "abcd") |
||||||
|
expect(t, context.String("lang"), "spanish") |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_CommandWithFlagBeforeTerminator(t *testing.T) { |
||||||
|
var parsedOption string |
||||||
|
var args []string |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
command := cli.Command{ |
||||||
|
Name: "cmd", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "option", Value: "", Usage: "some option"}, |
||||||
|
}, |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
parsedOption = c.String("option") |
||||||
|
args = c.Args() |
||||||
|
}, |
||||||
|
} |
||||||
|
app.Commands = []cli.Command{command} |
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--option", "my-option", "--", "--notARealFlag"}) |
||||||
|
|
||||||
|
expect(t, parsedOption, "my-option") |
||||||
|
expect(t, args[0], "my-arg") |
||||||
|
expect(t, args[1], "--") |
||||||
|
expect(t, args[2], "--notARealFlag") |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_CommandWithNoFlagBeforeTerminator(t *testing.T) { |
||||||
|
var args []string |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
command := cli.Command{ |
||||||
|
Name: "cmd", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
args = c.Args() |
||||||
|
}, |
||||||
|
} |
||||||
|
app.Commands = []cli.Command{command} |
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "--", "notAFlagAtAll"}) |
||||||
|
|
||||||
|
expect(t, args[0], "my-arg") |
||||||
|
expect(t, args[1], "--") |
||||||
|
expect(t, args[2], "notAFlagAtAll") |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_Float64Flag(t *testing.T) { |
||||||
|
var meters float64 |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
app.Flags = []cli.Flag{ |
||||||
|
cli.Float64Flag{Name: "height", Value: 1.5, Usage: "Set the height, in meters"}, |
||||||
|
} |
||||||
|
app.Action = func(c *cli.Context) { |
||||||
|
meters = c.Float64("height") |
||||||
|
} |
||||||
|
|
||||||
|
app.Run([]string{"", "--height", "1.93"}) |
||||||
|
expect(t, meters, 1.93) |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_ParseSliceFlags(t *testing.T) { |
||||||
|
var parsedOption, firstArg string |
||||||
|
var parsedIntSlice []int |
||||||
|
var parsedStringSlice []string |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
command := cli.Command{ |
||||||
|
Name: "cmd", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.IntSliceFlag{Name: "p", Value: &cli.IntSlice{}, Usage: "set one or more ip addr"}, |
||||||
|
cli.StringSliceFlag{Name: "ip", Value: &cli.StringSlice{}, Usage: "set one or more ports to open"}, |
||||||
|
}, |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
parsedIntSlice = c.IntSlice("p") |
||||||
|
parsedStringSlice = c.StringSlice("ip") |
||||||
|
parsedOption = c.String("option") |
||||||
|
firstArg = c.Args().First() |
||||||
|
}, |
||||||
|
} |
||||||
|
app.Commands = []cli.Command{command} |
||||||
|
|
||||||
|
app.Run([]string{"", "cmd", "my-arg", "-p", "22", "-p", "80", "-ip", "8.8.8.8", "-ip", "8.8.4.4"}) |
||||||
|
|
||||||
|
IntsEquals := func(a, b []int) bool { |
||||||
|
if len(a) != len(b) { |
||||||
|
return false |
||||||
|
} |
||||||
|
for i, v := range a { |
||||||
|
if v != b[i] { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
StrsEquals := func(a, b []string) bool { |
||||||
|
if len(a) != len(b) { |
||||||
|
return false |
||||||
|
} |
||||||
|
for i, v := range a { |
||||||
|
if v != b[i] { |
||||||
|
return false |
||||||
|
} |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
var expectedIntSlice = []int{22, 80} |
||||||
|
var expectedStringSlice = []string{"8.8.8.8", "8.8.4.4"} |
||||||
|
|
||||||
|
if !IntsEquals(parsedIntSlice, expectedIntSlice) { |
||||||
|
t.Errorf("%v does not match %v", parsedIntSlice, expectedIntSlice) |
||||||
|
} |
||||||
|
|
||||||
|
if !StrsEquals(parsedStringSlice, expectedStringSlice) { |
||||||
|
t.Errorf("%v does not match %v", parsedStringSlice, expectedStringSlice) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_DefaultStdout(t *testing.T) { |
||||||
|
app := cli.NewApp() |
||||||
|
|
||||||
|
if app.Writer != os.Stdout { |
||||||
|
t.Error("Default output writer not set.") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type mockWriter struct { |
||||||
|
written []byte |
||||||
|
} |
||||||
|
|
||||||
|
func (fw *mockWriter) Write(p []byte) (n int, err error) { |
||||||
|
if fw.written == nil { |
||||||
|
fw.written = p |
||||||
|
} else { |
||||||
|
fw.written = append(fw.written, p...) |
||||||
|
} |
||||||
|
|
||||||
|
return len(p), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (fw *mockWriter) GetWritten() (b []byte) { |
||||||
|
return fw.written |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_SetStdout(t *testing.T) { |
||||||
|
w := &mockWriter{} |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "test" |
||||||
|
app.Writer = w |
||||||
|
|
||||||
|
err := app.Run([]string{"help"}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
t.Fatalf("Run error: %s", err) |
||||||
|
} |
||||||
|
|
||||||
|
if len(w.written) == 0 { |
||||||
|
t.Error("App did not write output to desired writer.") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_BeforeFunc(t *testing.T) { |
||||||
|
beforeRun, subcommandRun := false, false |
||||||
|
beforeError := fmt.Errorf("fail") |
||||||
|
var err error |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
|
||||||
|
app.Before = func(c *cli.Context) error { |
||||||
|
beforeRun = true |
||||||
|
s := c.String("opt") |
||||||
|
if s == "fail" { |
||||||
|
return beforeError |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
app.Commands = []cli.Command{ |
||||||
|
cli.Command{ |
||||||
|
Name: "sub", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
subcommandRun = true |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Flags = []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "opt"}, |
||||||
|
} |
||||||
|
|
||||||
|
// run with the Before() func succeeding
|
||||||
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
t.Fatalf("Run error: %s", err) |
||||||
|
} |
||||||
|
|
||||||
|
if beforeRun == false { |
||||||
|
t.Errorf("Before() not executed when expected") |
||||||
|
} |
||||||
|
|
||||||
|
if subcommandRun == false { |
||||||
|
t.Errorf("Subcommand not executed when expected") |
||||||
|
} |
||||||
|
|
||||||
|
// reset
|
||||||
|
beforeRun, subcommandRun = false, false |
||||||
|
|
||||||
|
// run with the Before() func failing
|
||||||
|
err = app.Run([]string{"command", "--opt", "fail", "sub"}) |
||||||
|
|
||||||
|
// should be the same error produced by the Before func
|
||||||
|
if err != beforeError { |
||||||
|
t.Errorf("Run error expected, but not received") |
||||||
|
} |
||||||
|
|
||||||
|
if beforeRun == false { |
||||||
|
t.Errorf("Before() not executed when expected") |
||||||
|
} |
||||||
|
|
||||||
|
if subcommandRun == true { |
||||||
|
t.Errorf("Subcommand executed when NOT expected") |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func TestApp_AfterFunc(t *testing.T) { |
||||||
|
afterRun, subcommandRun := false, false |
||||||
|
afterError := fmt.Errorf("fail") |
||||||
|
var err error |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
|
||||||
|
app.After = func(c *cli.Context) error { |
||||||
|
afterRun = true |
||||||
|
s := c.String("opt") |
||||||
|
if s == "fail" { |
||||||
|
return afterError |
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
app.Commands = []cli.Command{ |
||||||
|
cli.Command{ |
||||||
|
Name: "sub", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
subcommandRun = true |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Flags = []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "opt"}, |
||||||
|
} |
||||||
|
|
||||||
|
// run with the After() func succeeding
|
||||||
|
err = app.Run([]string{"command", "--opt", "succeed", "sub"}) |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
t.Fatalf("Run error: %s", err) |
||||||
|
} |
||||||
|
|
||||||
|
if afterRun == false { |
||||||
|
t.Errorf("After() not executed when expected") |
||||||
|
} |
||||||
|
|
||||||
|
if subcommandRun == false { |
||||||
|
t.Errorf("Subcommand not executed when expected") |
||||||
|
} |
||||||
|
|
||||||
|
// reset
|
||||||
|
afterRun, subcommandRun = false, false |
||||||
|
|
||||||
|
// run with the Before() func failing
|
||||||
|
err = app.Run([]string{"command", "--opt", "fail", "sub"}) |
||||||
|
|
||||||
|
// should be the same error produced by the Before func
|
||||||
|
if err != afterError { |
||||||
|
t.Errorf("Run error expected, but not received") |
||||||
|
} |
||||||
|
|
||||||
|
if afterRun == false { |
||||||
|
t.Errorf("After() not executed when expected") |
||||||
|
} |
||||||
|
|
||||||
|
if subcommandRun == false { |
||||||
|
t.Errorf("Subcommand not executed when expected") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestAppNoHelpFlag(t *testing.T) { |
||||||
|
oldFlag := cli.HelpFlag |
||||||
|
defer func() { |
||||||
|
cli.HelpFlag = oldFlag |
||||||
|
}() |
||||||
|
|
||||||
|
cli.HelpFlag = cli.BoolFlag{} |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
err := app.Run([]string{"test", "-h"}) |
||||||
|
|
||||||
|
if err != flag.ErrHelp { |
||||||
|
t.Errorf("expected error about missing help flag, but got: %s (%T)", err, err) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestAppHelpPrinter(t *testing.T) { |
||||||
|
oldPrinter := cli.HelpPrinter |
||||||
|
defer func() { |
||||||
|
cli.HelpPrinter = oldPrinter |
||||||
|
}() |
||||||
|
|
||||||
|
var wasCalled = false |
||||||
|
cli.HelpPrinter = func(template string, data interface{}) { |
||||||
|
wasCalled = true |
||||||
|
} |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
app.Run([]string{"-h"}) |
||||||
|
|
||||||
|
if wasCalled == false { |
||||||
|
t.Errorf("Help printer expected to be called, but was not") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestAppVersionPrinter(t *testing.T) { |
||||||
|
oldPrinter := cli.VersionPrinter |
||||||
|
defer func() { |
||||||
|
cli.VersionPrinter = oldPrinter |
||||||
|
}() |
||||||
|
|
||||||
|
var wasCalled = false |
||||||
|
cli.VersionPrinter = func(c *cli.Context) { |
||||||
|
wasCalled = true |
||||||
|
} |
||||||
|
|
||||||
|
app := cli.NewApp() |
||||||
|
ctx := cli.NewContext(app, nil, nil) |
||||||
|
cli.ShowVersion(ctx) |
||||||
|
|
||||||
|
if wasCalled == false { |
||||||
|
t.Errorf("Version printer expected to be called, but was not") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestAppCommandNotFound(t *testing.T) { |
||||||
|
beforeRun, subcommandRun := false, false |
||||||
|
app := cli.NewApp() |
||||||
|
|
||||||
|
app.CommandNotFound = func(c *cli.Context, command string) { |
||||||
|
beforeRun = true |
||||||
|
} |
||||||
|
|
||||||
|
app.Commands = []cli.Command{ |
||||||
|
cli.Command{ |
||||||
|
Name: "bar", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
subcommandRun = true |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Run([]string{"command", "foo"}) |
||||||
|
|
||||||
|
expect(t, beforeRun, true) |
||||||
|
expect(t, subcommandRun, false) |
||||||
|
} |
||||||
|
|
||||||
|
func TestGlobalFlagsInSubcommands(t *testing.T) { |
||||||
|
subcommandRun := false |
||||||
|
app := cli.NewApp() |
||||||
|
|
||||||
|
app.Flags = []cli.Flag{ |
||||||
|
cli.BoolFlag{Name: "debug, d", Usage: "Enable debugging"}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Commands = []cli.Command{ |
||||||
|
cli.Command{ |
||||||
|
Name: "foo", |
||||||
|
Subcommands: []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "bar", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
if c.GlobalBool("debug") { |
||||||
|
subcommandRun = true |
||||||
|
} |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Run([]string{"command", "-d", "foo", "bar"}) |
||||||
|
|
||||||
|
expect(t, subcommandRun, true) |
||||||
|
} |
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
13
Godeps/_workspace/src/github.com/codegangsta/cli/autocomplete/bash_autocomplete
generated
vendored
@ -0,0 +1,13 @@ |
|||||||
|
#! /bin/bash |
||||||
|
|
||||||
|
_cli_bash_autocomplete() { |
||||||
|
local cur prev opts base |
||||||
|
COMPREPLY=() |
||||||
|
cur="${COMP_WORDS[COMP_CWORD]}" |
||||||
|
prev="${COMP_WORDS[COMP_CWORD-1]}" |
||||||
|
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) |
||||||
|
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) |
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
complete -F _cli_bash_autocomplete $PROG |
@ -0,0 +1,5 @@ |
|||||||
|
autoload -U compinit && compinit |
||||||
|
autoload -U bashcompinit && bashcompinit |
||||||
|
|
||||||
|
script_dir=$(dirname $0) |
||||||
|
source ${script_dir}/bash_autocomplete |
@ -0,0 +1,19 @@ |
|||||||
|
// Package cli provides a minimal framework for creating and organizing command line
|
||||||
|
// Go applications. cli is designed to be easy to understand and write, the most simple
|
||||||
|
// cli application can be written as follows:
|
||||||
|
// func main() {
|
||||||
|
// cli.NewApp().Run(os.Args)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Of course this application does not do much, so let's make this an actual application:
|
||||||
|
// func main() {
|
||||||
|
// app := cli.NewApp()
|
||||||
|
// app.Name = "greet"
|
||||||
|
// app.Usage = "say a greeting"
|
||||||
|
// app.Action = func(c *cli.Context) {
|
||||||
|
// println("Greetings")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// app.Run(os.Args)
|
||||||
|
// }
|
||||||
|
package cli |
@ -0,0 +1,100 @@ |
|||||||
|
package cli_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
|
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
func Example() { |
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "todo" |
||||||
|
app.Usage = "task list on the command line" |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "add", |
||||||
|
ShortName: "a", |
||||||
|
Usage: "add a task to the list", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("added task: ", c.Args().First()) |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
Name: "complete", |
||||||
|
ShortName: "c", |
||||||
|
Usage: "complete a task on the list", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("completed task: ", c.Args().First()) |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Run(os.Args) |
||||||
|
} |
||||||
|
|
||||||
|
func ExampleSubcommand() { |
||||||
|
app := cli.NewApp() |
||||||
|
app.Name = "say" |
||||||
|
app.Commands = []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "hello", |
||||||
|
ShortName: "hi", |
||||||
|
Usage: "use it to see a description", |
||||||
|
Description: "This is how we describe hello the function", |
||||||
|
Subcommands: []cli.Command{ |
||||||
|
{ |
||||||
|
Name: "english", |
||||||
|
ShortName: "en", |
||||||
|
Usage: "sends a greeting in english", |
||||||
|
Description: "greets someone in english", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "name", |
||||||
|
Value: "Bob", |
||||||
|
Usage: "Name of the person to greet", |
||||||
|
}, |
||||||
|
}, |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("Hello, ", c.String("name")) |
||||||
|
}, |
||||||
|
}, { |
||||||
|
Name: "spanish", |
||||||
|
ShortName: "sp", |
||||||
|
Usage: "sends a greeting in spanish", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "surname", |
||||||
|
Value: "Jones", |
||||||
|
Usage: "Surname of the person to greet", |
||||||
|
}, |
||||||
|
}, |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("Hola, ", c.String("surname")) |
||||||
|
}, |
||||||
|
}, { |
||||||
|
Name: "french", |
||||||
|
ShortName: "fr", |
||||||
|
Usage: "sends a greeting in french", |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{ |
||||||
|
Name: "nickname", |
||||||
|
Value: "Stevie", |
||||||
|
Usage: "Nickname of the person to greet", |
||||||
|
}, |
||||||
|
}, |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("Bonjour, ", c.String("nickname")) |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, { |
||||||
|
Name: "bye", |
||||||
|
Usage: "says goodbye", |
||||||
|
Action: func(c *cli.Context) { |
||||||
|
println("bye") |
||||||
|
}, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
app.Run(os.Args) |
||||||
|
} |
@ -0,0 +1,160 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io/ioutil" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
// Command is a subcommand for a cli.App.
|
||||||
|
type Command struct { |
||||||
|
// The name of the command
|
||||||
|
Name string |
||||||
|
// short name of the command. Typically one character
|
||||||
|
ShortName string |
||||||
|
// A short description of the usage of this command
|
||||||
|
Usage string |
||||||
|
// A longer explanation of how the command works
|
||||||
|
Description string |
||||||
|
// The function to call when checking for bash command completions
|
||||||
|
BashComplete func(context *Context) |
||||||
|
// An action to execute before any sub-subcommands are run, but after the context is ready
|
||||||
|
// If a non-nil error is returned, no sub-subcommands are run
|
||||||
|
Before func(context *Context) error |
||||||
|
// An action to execute after any subcommands are run, but after the subcommand has finished
|
||||||
|
// It is run even if Action() panics
|
||||||
|
After func(context *Context) error |
||||||
|
// The function to call when this command is invoked
|
||||||
|
Action func(context *Context) |
||||||
|
// List of child commands
|
||||||
|
Subcommands []Command |
||||||
|
// List of flags to parse
|
||||||
|
Flags []Flag |
||||||
|
// Treat all flags as normal arguments if true
|
||||||
|
SkipFlagParsing bool |
||||||
|
// Boolean to hide built-in help command
|
||||||
|
HideHelp bool |
||||||
|
} |
||||||
|
|
||||||
|
// Invokes the command given the context, parses ctx.Args() to generate command-specific flags
|
||||||
|
func (c Command) Run(ctx *Context) error { |
||||||
|
|
||||||
|
if len(c.Subcommands) > 0 || c.Before != nil || c.After != nil { |
||||||
|
return c.startApp(ctx) |
||||||
|
} |
||||||
|
|
||||||
|
if !c.HideHelp && (HelpFlag != BoolFlag{}) { |
||||||
|
// append help to flags
|
||||||
|
c.Flags = append( |
||||||
|
c.Flags, |
||||||
|
HelpFlag, |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
if ctx.App.EnableBashCompletion { |
||||||
|
c.Flags = append(c.Flags, BashCompletionFlag) |
||||||
|
} |
||||||
|
|
||||||
|
set := flagSet(c.Name, c.Flags) |
||||||
|
set.SetOutput(ioutil.Discard) |
||||||
|
|
||||||
|
firstFlagIndex := -1 |
||||||
|
terminatorIndex := -1 |
||||||
|
for index, arg := range ctx.Args() { |
||||||
|
if arg == "--" { |
||||||
|
terminatorIndex = index |
||||||
|
break |
||||||
|
} else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { |
||||||
|
firstFlagIndex = index |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var err error |
||||||
|
if firstFlagIndex > -1 && !c.SkipFlagParsing { |
||||||
|
args := ctx.Args() |
||||||
|
regularArgs := make([]string, len(args[1:firstFlagIndex])) |
||||||
|
copy(regularArgs, args[1:firstFlagIndex]) |
||||||
|
|
||||||
|
var flagArgs []string |
||||||
|
if terminatorIndex > -1 { |
||||||
|
flagArgs = args[firstFlagIndex:terminatorIndex] |
||||||
|
regularArgs = append(regularArgs, args[terminatorIndex:]...) |
||||||
|
} else { |
||||||
|
flagArgs = args[firstFlagIndex:] |
||||||
|
} |
||||||
|
|
||||||
|
err = set.Parse(append(flagArgs, regularArgs...)) |
||||||
|
} else { |
||||||
|
err = set.Parse(ctx.Args().Tail()) |
||||||
|
} |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
fmt.Fprint(ctx.App.Writer, "Incorrect Usage.\n\n") |
||||||
|
ShowCommandHelp(ctx, c.Name) |
||||||
|
fmt.Fprintln(ctx.App.Writer) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
nerr := normalizeFlags(c.Flags, set) |
||||||
|
if nerr != nil { |
||||||
|
fmt.Fprintln(ctx.App.Writer, nerr) |
||||||
|
fmt.Fprintln(ctx.App.Writer) |
||||||
|
ShowCommandHelp(ctx, c.Name) |
||||||
|
fmt.Fprintln(ctx.App.Writer) |
||||||
|
return nerr |
||||||
|
} |
||||||
|
context := NewContext(ctx.App, set, ctx.globalSet) |
||||||
|
|
||||||
|
if checkCommandCompletions(context, c.Name) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
if checkCommandHelp(context, c.Name) { |
||||||
|
return nil |
||||||
|
} |
||||||
|
context.Command = c |
||||||
|
c.Action(context) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// Returns true if Command.Name or Command.ShortName matches given name
|
||||||
|
func (c Command) HasName(name string) bool { |
||||||
|
return c.Name == name || c.ShortName == name |
||||||
|
} |
||||||
|
|
||||||
|
func (c Command) startApp(ctx *Context) error { |
||||||
|
app := NewApp() |
||||||
|
|
||||||
|
// set the name and usage
|
||||||
|
app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) |
||||||
|
if c.Description != "" { |
||||||
|
app.Usage = c.Description |
||||||
|
} else { |
||||||
|
app.Usage = c.Usage |
||||||
|
} |
||||||
|
|
||||||
|
// set CommandNotFound
|
||||||
|
app.CommandNotFound = ctx.App.CommandNotFound |
||||||
|
|
||||||
|
// set the flags and commands
|
||||||
|
app.Commands = c.Subcommands |
||||||
|
app.Flags = c.Flags |
||||||
|
app.HideHelp = c.HideHelp |
||||||
|
|
||||||
|
// bash completion
|
||||||
|
app.EnableBashCompletion = ctx.App.EnableBashCompletion |
||||||
|
if c.BashComplete != nil { |
||||||
|
app.BashComplete = c.BashComplete |
||||||
|
} |
||||||
|
|
||||||
|
// set the actions
|
||||||
|
app.Before = c.Before |
||||||
|
app.After = c.After |
||||||
|
if c.Action != nil { |
||||||
|
app.Action = c.Action |
||||||
|
} else { |
||||||
|
app.Action = helpSubcommand.Action |
||||||
|
} |
||||||
|
|
||||||
|
return app.RunAsSubcommand(ctx) |
||||||
|
} |
@ -0,0 +1,49 @@ |
|||||||
|
package cli_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
func TestCommandDoNotIgnoreFlags(t *testing.T) { |
||||||
|
app := cli.NewApp() |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
test := []string{"blah", "blah", "-break"} |
||||||
|
set.Parse(test) |
||||||
|
|
||||||
|
c := cli.NewContext(app, set, set) |
||||||
|
|
||||||
|
command := cli.Command{ |
||||||
|
Name: "test-cmd", |
||||||
|
ShortName: "tc", |
||||||
|
Usage: "this is for testing", |
||||||
|
Description: "testing", |
||||||
|
Action: func(_ *cli.Context) {}, |
||||||
|
} |
||||||
|
err := command.Run(c) |
||||||
|
|
||||||
|
expect(t, err.Error(), "flag provided but not defined: -break") |
||||||
|
} |
||||||
|
|
||||||
|
func TestCommandIgnoreFlags(t *testing.T) { |
||||||
|
app := cli.NewApp() |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
test := []string{"blah", "blah"} |
||||||
|
set.Parse(test) |
||||||
|
|
||||||
|
c := cli.NewContext(app, set, set) |
||||||
|
|
||||||
|
command := cli.Command{ |
||||||
|
Name: "test-cmd", |
||||||
|
ShortName: "tc", |
||||||
|
Usage: "this is for testing", |
||||||
|
Description: "testing", |
||||||
|
Action: func(_ *cli.Context) {}, |
||||||
|
SkipFlagParsing: true, |
||||||
|
} |
||||||
|
err := command.Run(c) |
||||||
|
|
||||||
|
expect(t, err, nil) |
||||||
|
} |
@ -0,0 +1,339 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"flag" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// Context is a type that is passed through to
|
||||||
|
// each Handler action in a cli application. Context
|
||||||
|
// can be used to retrieve context-specific Args and
|
||||||
|
// parsed command-line options.
|
||||||
|
type Context struct { |
||||||
|
App *App |
||||||
|
Command Command |
||||||
|
flagSet *flag.FlagSet |
||||||
|
globalSet *flag.FlagSet |
||||||
|
setFlags map[string]bool |
||||||
|
globalSetFlags map[string]bool |
||||||
|
} |
||||||
|
|
||||||
|
// Creates a new context. For use in when invoking an App or Command action.
|
||||||
|
func NewContext(app *App, set *flag.FlagSet, globalSet *flag.FlagSet) *Context { |
||||||
|
return &Context{App: app, flagSet: set, globalSet: globalSet} |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local int flag, returns 0 if no int flag exists
|
||||||
|
func (c *Context) Int(name string) int { |
||||||
|
return lookupInt(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local time.Duration flag, returns 0 if no time.Duration flag exists
|
||||||
|
func (c *Context) Duration(name string) time.Duration { |
||||||
|
return lookupDuration(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local float64 flag, returns 0 if no float64 flag exists
|
||||||
|
func (c *Context) Float64(name string) float64 { |
||||||
|
return lookupFloat64(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local bool flag, returns false if no bool flag exists
|
||||||
|
func (c *Context) Bool(name string) bool { |
||||||
|
return lookupBool(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local boolT flag, returns false if no bool flag exists
|
||||||
|
func (c *Context) BoolT(name string) bool { |
||||||
|
return lookupBoolT(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local string flag, returns "" if no string flag exists
|
||||||
|
func (c *Context) String(name string) string { |
||||||
|
return lookupString(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local string slice flag, returns nil if no string slice flag exists
|
||||||
|
func (c *Context) StringSlice(name string) []string { |
||||||
|
return lookupStringSlice(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local int slice flag, returns nil if no int slice flag exists
|
||||||
|
func (c *Context) IntSlice(name string) []int { |
||||||
|
return lookupIntSlice(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a local generic flag, returns nil if no generic flag exists
|
||||||
|
func (c *Context) Generic(name string) interface{} { |
||||||
|
return lookupGeneric(name, c.flagSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a global int flag, returns 0 if no int flag exists
|
||||||
|
func (c *Context) GlobalInt(name string) int { |
||||||
|
return lookupInt(name, c.globalSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a global time.Duration flag, returns 0 if no time.Duration flag exists
|
||||||
|
func (c *Context) GlobalDuration(name string) time.Duration { |
||||||
|
return lookupDuration(name, c.globalSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a global bool flag, returns false if no bool flag exists
|
||||||
|
func (c *Context) GlobalBool(name string) bool { |
||||||
|
return lookupBool(name, c.globalSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a global string flag, returns "" if no string flag exists
|
||||||
|
func (c *Context) GlobalString(name string) string { |
||||||
|
return lookupString(name, c.globalSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a global string slice flag, returns nil if no string slice flag exists
|
||||||
|
func (c *Context) GlobalStringSlice(name string) []string { |
||||||
|
return lookupStringSlice(name, c.globalSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a global int slice flag, returns nil if no int slice flag exists
|
||||||
|
func (c *Context) GlobalIntSlice(name string) []int { |
||||||
|
return lookupIntSlice(name, c.globalSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Looks up the value of a global generic flag, returns nil if no generic flag exists
|
||||||
|
func (c *Context) GlobalGeneric(name string) interface{} { |
||||||
|
return lookupGeneric(name, c.globalSet) |
||||||
|
} |
||||||
|
|
||||||
|
// Determines if the flag was actually set
|
||||||
|
func (c *Context) IsSet(name string) bool { |
||||||
|
if c.setFlags == nil { |
||||||
|
c.setFlags = make(map[string]bool) |
||||||
|
c.flagSet.Visit(func(f *flag.Flag) { |
||||||
|
c.setFlags[f.Name] = true |
||||||
|
}) |
||||||
|
} |
||||||
|
return c.setFlags[name] == true |
||||||
|
} |
||||||
|
|
||||||
|
// Determines if the global flag was actually set
|
||||||
|
func (c *Context) GlobalIsSet(name string) bool { |
||||||
|
if c.globalSetFlags == nil { |
||||||
|
c.globalSetFlags = make(map[string]bool) |
||||||
|
c.globalSet.Visit(func(f *flag.Flag) { |
||||||
|
c.globalSetFlags[f.Name] = true |
||||||
|
}) |
||||||
|
} |
||||||
|
return c.globalSetFlags[name] == true |
||||||
|
} |
||||||
|
|
||||||
|
// Returns a slice of flag names used in this context.
|
||||||
|
func (c *Context) FlagNames() (names []string) { |
||||||
|
for _, flag := range c.Command.Flags { |
||||||
|
name := strings.Split(flag.getName(), ",")[0] |
||||||
|
if name == "help" { |
||||||
|
continue |
||||||
|
} |
||||||
|
names = append(names, name) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Returns a slice of global flag names used by the app.
|
||||||
|
func (c *Context) GlobalFlagNames() (names []string) { |
||||||
|
for _, flag := range c.App.Flags { |
||||||
|
name := strings.Split(flag.getName(), ",")[0] |
||||||
|
if name == "help" || name == "version" { |
||||||
|
continue |
||||||
|
} |
||||||
|
names = append(names, name) |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
type Args []string |
||||||
|
|
||||||
|
// Returns the command line arguments associated with the context.
|
||||||
|
func (c *Context) Args() Args { |
||||||
|
args := Args(c.flagSet.Args()) |
||||||
|
return args |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the nth argument, or else a blank string
|
||||||
|
func (a Args) Get(n int) string { |
||||||
|
if len(a) > n { |
||||||
|
return a[n] |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the first argument, or else a blank string
|
||||||
|
func (a Args) First() string { |
||||||
|
return a.Get(0) |
||||||
|
} |
||||||
|
|
||||||
|
// Return the rest of the arguments (not the first one)
|
||||||
|
// or else an empty string slice
|
||||||
|
func (a Args) Tail() []string { |
||||||
|
if len(a) >= 2 { |
||||||
|
return []string(a)[1:] |
||||||
|
} |
||||||
|
return []string{} |
||||||
|
} |
||||||
|
|
||||||
|
// Checks if there are any arguments present
|
||||||
|
func (a Args) Present() bool { |
||||||
|
return len(a) != 0 |
||||||
|
} |
||||||
|
|
||||||
|
// Swaps arguments at the given indexes
|
||||||
|
func (a Args) Swap(from, to int) error { |
||||||
|
if from >= len(a) || to >= len(a) { |
||||||
|
return errors.New("index out of range") |
||||||
|
} |
||||||
|
a[from], a[to] = a[to], a[from] |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupInt(name string, set *flag.FlagSet) int { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
val, err := strconv.Atoi(f.Value.String()) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return val |
||||||
|
} |
||||||
|
|
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func lookupDuration(name string, set *flag.FlagSet) time.Duration { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
val, err := time.ParseDuration(f.Value.String()) |
||||||
|
if err == nil { |
||||||
|
return val |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func lookupFloat64(name string, set *flag.FlagSet) float64 { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
val, err := strconv.ParseFloat(f.Value.String(), 64) |
||||||
|
if err != nil { |
||||||
|
return 0 |
||||||
|
} |
||||||
|
return val |
||||||
|
} |
||||||
|
|
||||||
|
return 0 |
||||||
|
} |
||||||
|
|
||||||
|
func lookupString(name string, set *flag.FlagSet) string { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
return f.Value.String() |
||||||
|
} |
||||||
|
|
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
func lookupStringSlice(name string, set *flag.FlagSet) []string { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
return (f.Value.(*StringSlice)).Value() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupIntSlice(name string, set *flag.FlagSet) []int { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
return (f.Value.(*IntSlice)).Value() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupGeneric(name string, set *flag.FlagSet) interface{} { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
return f.Value |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func lookupBool(name string, set *flag.FlagSet) bool { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
val, err := strconv.ParseBool(f.Value.String()) |
||||||
|
if err != nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
return val |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func lookupBoolT(name string, set *flag.FlagSet) bool { |
||||||
|
f := set.Lookup(name) |
||||||
|
if f != nil { |
||||||
|
val, err := strconv.ParseBool(f.Value.String()) |
||||||
|
if err != nil { |
||||||
|
return true |
||||||
|
} |
||||||
|
return val |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { |
||||||
|
switch ff.Value.(type) { |
||||||
|
case *StringSlice: |
||||||
|
default: |
||||||
|
set.Set(name, ff.Value.String()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func normalizeFlags(flags []Flag, set *flag.FlagSet) error { |
||||||
|
visited := make(map[string]bool) |
||||||
|
set.Visit(func(f *flag.Flag) { |
||||||
|
visited[f.Name] = true |
||||||
|
}) |
||||||
|
for _, f := range flags { |
||||||
|
parts := strings.Split(f.getName(), ",") |
||||||
|
if len(parts) == 1 { |
||||||
|
continue |
||||||
|
} |
||||||
|
var ff *flag.Flag |
||||||
|
for _, name := range parts { |
||||||
|
name = strings.Trim(name, " ") |
||||||
|
if visited[name] { |
||||||
|
if ff != nil { |
||||||
|
return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) |
||||||
|
} |
||||||
|
ff = set.Lookup(name) |
||||||
|
} |
||||||
|
} |
||||||
|
if ff == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
for _, name := range parts { |
||||||
|
name = strings.Trim(name, " ") |
||||||
|
if !visited[name] { |
||||||
|
copyFlag(name, ff, set) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,99 @@ |
|||||||
|
package cli_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
func TestNewContext(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Int("myflag", 12, "doc") |
||||||
|
globalSet := flag.NewFlagSet("test", 0) |
||||||
|
globalSet.Int("myflag", 42, "doc") |
||||||
|
command := cli.Command{Name: "mycommand"} |
||||||
|
c := cli.NewContext(nil, set, globalSet) |
||||||
|
c.Command = command |
||||||
|
expect(t, c.Int("myflag"), 12) |
||||||
|
expect(t, c.GlobalInt("myflag"), 42) |
||||||
|
expect(t, c.Command.Name, "mycommand") |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_Int(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Int("myflag", 12, "doc") |
||||||
|
c := cli.NewContext(nil, set, set) |
||||||
|
expect(t, c.Int("myflag"), 12) |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_Duration(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Duration("myflag", time.Duration(12*time.Second), "doc") |
||||||
|
c := cli.NewContext(nil, set, set) |
||||||
|
expect(t, c.Duration("myflag"), time.Duration(12*time.Second)) |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_String(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.String("myflag", "hello world", "doc") |
||||||
|
c := cli.NewContext(nil, set, set) |
||||||
|
expect(t, c.String("myflag"), "hello world") |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_Bool(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Bool("myflag", false, "doc") |
||||||
|
c := cli.NewContext(nil, set, set) |
||||||
|
expect(t, c.Bool("myflag"), false) |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_BoolT(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Bool("myflag", true, "doc") |
||||||
|
c := cli.NewContext(nil, set, set) |
||||||
|
expect(t, c.BoolT("myflag"), true) |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_Args(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Bool("myflag", false, "doc") |
||||||
|
c := cli.NewContext(nil, set, set) |
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"}) |
||||||
|
expect(t, len(c.Args()), 2) |
||||||
|
expect(t, c.Bool("myflag"), true) |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_IsSet(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Bool("myflag", false, "doc") |
||||||
|
set.String("otherflag", "hello world", "doc") |
||||||
|
globalSet := flag.NewFlagSet("test", 0) |
||||||
|
globalSet.Bool("myflagGlobal", true, "doc") |
||||||
|
c := cli.NewContext(nil, set, globalSet) |
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"}) |
||||||
|
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) |
||||||
|
expect(t, c.IsSet("myflag"), true) |
||||||
|
expect(t, c.IsSet("otherflag"), false) |
||||||
|
expect(t, c.IsSet("bogusflag"), false) |
||||||
|
expect(t, c.IsSet("myflagGlobal"), false) |
||||||
|
} |
||||||
|
|
||||||
|
func TestContext_GlobalIsSet(t *testing.T) { |
||||||
|
set := flag.NewFlagSet("test", 0) |
||||||
|
set.Bool("myflag", false, "doc") |
||||||
|
set.String("otherflag", "hello world", "doc") |
||||||
|
globalSet := flag.NewFlagSet("test", 0) |
||||||
|
globalSet.Bool("myflagGlobal", true, "doc") |
||||||
|
globalSet.Bool("myflagGlobalUnset", true, "doc") |
||||||
|
c := cli.NewContext(nil, set, globalSet) |
||||||
|
set.Parse([]string{"--myflag", "bat", "baz"}) |
||||||
|
globalSet.Parse([]string{"--myflagGlobal", "bat", "baz"}) |
||||||
|
expect(t, c.GlobalIsSet("myflag"), false) |
||||||
|
expect(t, c.GlobalIsSet("otherflag"), false) |
||||||
|
expect(t, c.GlobalIsSet("bogusflag"), false) |
||||||
|
expect(t, c.GlobalIsSet("myflagGlobal"), true) |
||||||
|
expect(t, c.GlobalIsSet("myflagGlobalUnset"), false) |
||||||
|
expect(t, c.GlobalIsSet("bogusGlobal"), false) |
||||||
|
} |
@ -0,0 +1,454 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// This flag enables bash-completion for all commands and subcommands
|
||||||
|
var BashCompletionFlag = BoolFlag{ |
||||||
|
Name: "generate-bash-completion", |
||||||
|
} |
||||||
|
|
||||||
|
// This flag prints the version for the application
|
||||||
|
var VersionFlag = BoolFlag{ |
||||||
|
Name: "version, v", |
||||||
|
Usage: "print the version", |
||||||
|
} |
||||||
|
|
||||||
|
// This flag prints the help for all commands and subcommands
|
||||||
|
// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand
|
||||||
|
// unless HideHelp is set to true)
|
||||||
|
var HelpFlag = BoolFlag{ |
||||||
|
Name: "help, h", |
||||||
|
Usage: "show help", |
||||||
|
} |
||||||
|
|
||||||
|
// Flag is a common interface related to parsing flags in cli.
|
||||||
|
// For more advanced flag parsing techniques, it is recomended that
|
||||||
|
// this interface be implemented.
|
||||||
|
type Flag interface { |
||||||
|
fmt.Stringer |
||||||
|
// Apply Flag settings to the given flag set
|
||||||
|
Apply(*flag.FlagSet) |
||||||
|
getName() string |
||||||
|
} |
||||||
|
|
||||||
|
func flagSet(name string, flags []Flag) *flag.FlagSet { |
||||||
|
set := flag.NewFlagSet(name, flag.ContinueOnError) |
||||||
|
|
||||||
|
for _, f := range flags { |
||||||
|
f.Apply(set) |
||||||
|
} |
||||||
|
return set |
||||||
|
} |
||||||
|
|
||||||
|
func eachName(longName string, fn func(string)) { |
||||||
|
parts := strings.Split(longName, ",") |
||||||
|
for _, name := range parts { |
||||||
|
name = strings.Trim(name, " ") |
||||||
|
fn(name) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Generic is a generic parseable type identified by a specific flag
|
||||||
|
type Generic interface { |
||||||
|
Set(value string) error |
||||||
|
String() string |
||||||
|
} |
||||||
|
|
||||||
|
// GenericFlag is the flag type for types implementing Generic
|
||||||
|
type GenericFlag struct { |
||||||
|
Name string |
||||||
|
Value Generic |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
// String returns the string representation of the generic flag to display the
|
||||||
|
// help text to the user (uses the String() method of the generic flag to show
|
||||||
|
// the value)
|
||||||
|
func (f GenericFlag) String() string { |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s%s \"%v\"\t%v", prefixFor(f.Name), f.Name, f.Value, f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
// Apply takes the flagset and calls Set on the generic flag with the value
|
||||||
|
// provided by the user for parsing by the flag
|
||||||
|
func (f GenericFlag) Apply(set *flag.FlagSet) { |
||||||
|
val := f.Value |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
val.Set(envVal) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Var(f.Value, name, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f GenericFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type StringSlice []string |
||||||
|
|
||||||
|
func (f *StringSlice) Set(value string) error { |
||||||
|
*f = append(*f, value) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (f *StringSlice) String() string { |
||||||
|
return fmt.Sprintf("%s", *f) |
||||||
|
} |
||||||
|
|
||||||
|
func (f *StringSlice) Value() []string { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
type StringSliceFlag struct { |
||||||
|
Name string |
||||||
|
Value *StringSlice |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f StringSliceFlag) String() string { |
||||||
|
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") |
||||||
|
pref := prefixFor(firstName) |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f StringSliceFlag) Apply(set *flag.FlagSet) { |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
newVal := &StringSlice{} |
||||||
|
for _, s := range strings.Split(envVal, ",") { |
||||||
|
s = strings.TrimSpace(s) |
||||||
|
newVal.Set(s) |
||||||
|
} |
||||||
|
f.Value = newVal |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Var(f.Value, name, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f StringSliceFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type IntSlice []int |
||||||
|
|
||||||
|
func (f *IntSlice) Set(value string) error { |
||||||
|
|
||||||
|
tmp, err := strconv.Atoi(value) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} else { |
||||||
|
*f = append(*f, tmp) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (f *IntSlice) String() string { |
||||||
|
return fmt.Sprintf("%d", *f) |
||||||
|
} |
||||||
|
|
||||||
|
func (f *IntSlice) Value() []int { |
||||||
|
return *f |
||||||
|
} |
||||||
|
|
||||||
|
type IntSliceFlag struct { |
||||||
|
Name string |
||||||
|
Value *IntSlice |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f IntSliceFlag) String() string { |
||||||
|
firstName := strings.Trim(strings.Split(f.Name, ",")[0], " ") |
||||||
|
pref := prefixFor(firstName) |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s [%v]\t%v", prefixedNames(f.Name), pref+firstName+" option "+pref+firstName+" option", f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f IntSliceFlag) Apply(set *flag.FlagSet) { |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
newVal := &IntSlice{} |
||||||
|
for _, s := range strings.Split(envVal, ",") { |
||||||
|
s = strings.TrimSpace(s) |
||||||
|
err := newVal.Set(s) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintf(os.Stderr, err.Error()) |
||||||
|
} |
||||||
|
} |
||||||
|
f.Value = newVal |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Var(f.Value, name, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f IntSliceFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type BoolFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f BoolFlag) String() string { |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f BoolFlag) Apply(set *flag.FlagSet) { |
||||||
|
val := false |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
envValBool, err := strconv.ParseBool(envVal) |
||||||
|
if err == nil { |
||||||
|
val = envValBool |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Bool(name, val, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f BoolFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type BoolTFlag struct { |
||||||
|
Name string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f BoolTFlag) String() string { |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s\t%v", prefixedNames(f.Name), f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f BoolTFlag) Apply(set *flag.FlagSet) { |
||||||
|
val := true |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
envValBool, err := strconv.ParseBool(envVal) |
||||||
|
if err == nil { |
||||||
|
val = envValBool |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Bool(name, val, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f BoolTFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type StringFlag struct { |
||||||
|
Name string |
||||||
|
Value string |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f StringFlag) String() string { |
||||||
|
var fmtString string |
||||||
|
fmtString = "%s %v\t%v" |
||||||
|
|
||||||
|
if len(f.Value) > 0 { |
||||||
|
fmtString = "%s \"%v\"\t%v" |
||||||
|
} else { |
||||||
|
fmtString = "%s %v\t%v" |
||||||
|
} |
||||||
|
|
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf(fmtString, prefixedNames(f.Name), f.Value, f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f StringFlag) Apply(set *flag.FlagSet) { |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
f.Value = envVal |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.String(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f StringFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type IntFlag struct { |
||||||
|
Name string |
||||||
|
Value int |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f IntFlag) String() string { |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f IntFlag) Apply(set *flag.FlagSet) { |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
envValInt, err := strconv.ParseInt(envVal, 0, 64) |
||||||
|
if err == nil { |
||||||
|
f.Value = int(envValInt) |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Int(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f IntFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type DurationFlag struct { |
||||||
|
Name string |
||||||
|
Value time.Duration |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f DurationFlag) String() string { |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f DurationFlag) Apply(set *flag.FlagSet) { |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
envValDuration, err := time.ParseDuration(envVal) |
||||||
|
if err == nil { |
||||||
|
f.Value = envValDuration |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Duration(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f DurationFlag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
type Float64Flag struct { |
||||||
|
Name string |
||||||
|
Value float64 |
||||||
|
Usage string |
||||||
|
EnvVar string |
||||||
|
} |
||||||
|
|
||||||
|
func (f Float64Flag) String() string { |
||||||
|
return withEnvHint(f.EnvVar, fmt.Sprintf("%s \"%v\"\t%v", prefixedNames(f.Name), f.Value, f.Usage)) |
||||||
|
} |
||||||
|
|
||||||
|
func (f Float64Flag) Apply(set *flag.FlagSet) { |
||||||
|
if f.EnvVar != "" { |
||||||
|
for _, envVar := range strings.Split(f.EnvVar, ",") { |
||||||
|
envVar = strings.TrimSpace(envVar) |
||||||
|
if envVal := os.Getenv(envVar); envVal != "" { |
||||||
|
envValFloat, err := strconv.ParseFloat(envVal, 10) |
||||||
|
if err == nil { |
||||||
|
f.Value = float64(envValFloat) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
eachName(f.Name, func(name string) { |
||||||
|
set.Float64(name, f.Value, f.Usage) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (f Float64Flag) getName() string { |
||||||
|
return f.Name |
||||||
|
} |
||||||
|
|
||||||
|
func prefixFor(name string) (prefix string) { |
||||||
|
if len(name) == 1 { |
||||||
|
prefix = "-" |
||||||
|
} else { |
||||||
|
prefix = "--" |
||||||
|
} |
||||||
|
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func prefixedNames(fullName string) (prefixed string) { |
||||||
|
parts := strings.Split(fullName, ",") |
||||||
|
for i, name := range parts { |
||||||
|
name = strings.Trim(name, " ") |
||||||
|
prefixed += prefixFor(name) + name |
||||||
|
if i < len(parts)-1 { |
||||||
|
prefixed += ", " |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func withEnvHint(envVar, str string) string { |
||||||
|
envText := "" |
||||||
|
if envVar != "" { |
||||||
|
envText = fmt.Sprintf(" [$%s]", strings.Join(strings.Split(envVar, ","), ", $")) |
||||||
|
} |
||||||
|
return str + envText |
||||||
|
} |
@ -0,0 +1,742 @@ |
|||||||
|
package cli_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"reflect" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
|
||||||
|
"github.com/codegangsta/cli" |
||||||
|
) |
||||||
|
|
||||||
|
var boolFlagTests = []struct { |
||||||
|
name string |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"help", "--help\t"}, |
||||||
|
{"h", "-h\t"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestBoolFlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range boolFlagTests { |
||||||
|
flag := cli.BoolFlag{Name: test.name} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%s does not match %s", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var stringFlagTests = []struct { |
||||||
|
name string |
||||||
|
value string |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"help", "", "--help \t"}, |
||||||
|
{"h", "", "-h \t"}, |
||||||
|
{"h", "", "-h \t"}, |
||||||
|
{"test", "Something", "--test \"Something\"\t"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestStringFlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range stringFlagTests { |
||||||
|
flag := cli.StringFlag{Name: test.name, Value: test.value} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%s does not match %s", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestStringFlagWithEnvVarHelpOutput(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_FOO", "derp") |
||||||
|
for _, test := range stringFlagTests { |
||||||
|
flag := cli.StringFlag{Name: test.name, Value: test.value, EnvVar: "APP_FOO"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_FOO]") { |
||||||
|
t.Errorf("%s does not end with [$APP_FOO]", output) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var stringSliceFlagTests = []struct { |
||||||
|
name string |
||||||
|
value *cli.StringSlice |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"help", func() *cli.StringSlice { |
||||||
|
s := &cli.StringSlice{} |
||||||
|
s.Set("") |
||||||
|
return s |
||||||
|
}(), "--help [--help option --help option]\t"}, |
||||||
|
{"h", func() *cli.StringSlice { |
||||||
|
s := &cli.StringSlice{} |
||||||
|
s.Set("") |
||||||
|
return s |
||||||
|
}(), "-h [-h option -h option]\t"}, |
||||||
|
{"h", func() *cli.StringSlice { |
||||||
|
s := &cli.StringSlice{} |
||||||
|
s.Set("") |
||||||
|
return s |
||||||
|
}(), "-h [-h option -h option]\t"}, |
||||||
|
{"test", func() *cli.StringSlice { |
||||||
|
s := &cli.StringSlice{} |
||||||
|
s.Set("Something") |
||||||
|
return s |
||||||
|
}(), "--test [--test option --test option]\t"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestStringSliceFlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range stringSliceFlagTests { |
||||||
|
flag := cli.StringSliceFlag{Name: test.name, Value: test.value} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%q does not match %q", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestStringSliceFlagWithEnvVarHelpOutput(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_QWWX", "11,4") |
||||||
|
for _, test := range stringSliceFlagTests { |
||||||
|
flag := cli.StringSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_QWWX"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_QWWX]") { |
||||||
|
t.Errorf("%q does not end with [$APP_QWWX]", output) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var intFlagTests = []struct { |
||||||
|
name string |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"help", "--help \"0\"\t"}, |
||||||
|
{"h", "-h \"0\"\t"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestIntFlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range intFlagTests { |
||||||
|
flag := cli.IntFlag{Name: test.name} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%s does not match %s", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestIntFlagWithEnvVarHelpOutput(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_BAR", "2") |
||||||
|
for _, test := range intFlagTests { |
||||||
|
flag := cli.IntFlag{Name: test.name, EnvVar: "APP_BAR"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAR]") { |
||||||
|
t.Errorf("%s does not end with [$APP_BAR]", output) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var durationFlagTests = []struct { |
||||||
|
name string |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"help", "--help \"0\"\t"}, |
||||||
|
{"h", "-h \"0\"\t"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestDurationFlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range durationFlagTests { |
||||||
|
flag := cli.DurationFlag{Name: test.name} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%s does not match %s", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestDurationFlagWithEnvVarHelpOutput(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_BAR", "2h3m6s") |
||||||
|
for _, test := range durationFlagTests { |
||||||
|
flag := cli.DurationFlag{Name: test.name, EnvVar: "APP_BAR"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAR]") { |
||||||
|
t.Errorf("%s does not end with [$APP_BAR]", output) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var intSliceFlagTests = []struct { |
||||||
|
name string |
||||||
|
value *cli.IntSlice |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"help", &cli.IntSlice{}, "--help [--help option --help option]\t"}, |
||||||
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"}, |
||||||
|
{"h", &cli.IntSlice{}, "-h [-h option -h option]\t"}, |
||||||
|
{"test", func() *cli.IntSlice { |
||||||
|
i := &cli.IntSlice{} |
||||||
|
i.Set("9") |
||||||
|
return i |
||||||
|
}(), "--test [--test option --test option]\t"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestIntSliceFlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range intSliceFlagTests { |
||||||
|
flag := cli.IntSliceFlag{Name: test.name, Value: test.value} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%q does not match %q", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestIntSliceFlagWithEnvVarHelpOutput(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_SMURF", "42,3") |
||||||
|
for _, test := range intSliceFlagTests { |
||||||
|
flag := cli.IntSliceFlag{Name: test.name, Value: test.value, EnvVar: "APP_SMURF"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_SMURF]") { |
||||||
|
t.Errorf("%q does not end with [$APP_SMURF]", output) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var float64FlagTests = []struct { |
||||||
|
name string |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"help", "--help \"0\"\t"}, |
||||||
|
{"h", "-h \"0\"\t"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestFloat64FlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range float64FlagTests { |
||||||
|
flag := cli.Float64Flag{Name: test.name} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%s does not match %s", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestFloat64FlagWithEnvVarHelpOutput(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_BAZ", "99.4") |
||||||
|
for _, test := range float64FlagTests { |
||||||
|
flag := cli.Float64Flag{Name: test.name, EnvVar: "APP_BAZ"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_BAZ]") { |
||||||
|
t.Errorf("%s does not end with [$APP_BAZ]", output) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var genericFlagTests = []struct { |
||||||
|
name string |
||||||
|
value cli.Generic |
||||||
|
expected string |
||||||
|
}{ |
||||||
|
{"test", &Parser{"abc", "def"}, "--test \"abc,def\"\ttest flag"}, |
||||||
|
{"t", &Parser{"abc", "def"}, "-t \"abc,def\"\ttest flag"}, |
||||||
|
} |
||||||
|
|
||||||
|
func TestGenericFlagHelpOutput(t *testing.T) { |
||||||
|
|
||||||
|
for _, test := range genericFlagTests { |
||||||
|
flag := cli.GenericFlag{Name: test.name, Value: test.value, Usage: "test flag"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if output != test.expected { |
||||||
|
t.Errorf("%q does not match %q", output, test.expected) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestGenericFlagWithEnvVarHelpOutput(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_ZAP", "3") |
||||||
|
for _, test := range genericFlagTests { |
||||||
|
flag := cli.GenericFlag{Name: test.name, EnvVar: "APP_ZAP"} |
||||||
|
output := flag.String() |
||||||
|
|
||||||
|
if !strings.HasSuffix(output, " [$APP_ZAP]") { |
||||||
|
t.Errorf("%s does not end with [$APP_ZAP]", output) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiString(t *testing.T) { |
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "serve, s"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.String("serve") != "10" { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.String("s") != "10" { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run", "-s", "10"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiStringFromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_COUNT", "20") |
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "count, c", EnvVar: "APP_COUNT"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.String("count") != "20" { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.String("c") != "20" { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiStringFromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_COUNT", "20") |
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringFlag{Name: "count, c", EnvVar: "COMPAT_COUNT,APP_COUNT"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.String("count") != "20" { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.String("c") != "20" { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiStringSlice(t *testing.T) { |
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringSliceFlag{Name: "serve, s", Value: &cli.StringSlice{}}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("serve"), []string{"10", "20"}) { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("s"), []string{"10", "20"}) { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiStringSliceFromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40") |
||||||
|
|
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "APP_INTERVALS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiStringSliceFromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40") |
||||||
|
|
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.StringSliceFlag{Name: "intervals, i", Value: &cli.StringSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("intervals"), []string{"20", "30", "40"}) { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.StringSlice("i"), []string{"20", "30", "40"}) { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiInt(t *testing.T) { |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.IntFlag{Name: "serve, s"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Int("serve") != 10 { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.Int("s") != 10 { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run", "-s", "10"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiIntFromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "10") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.IntFlag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Int("timeout") != 10 { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.Int("t") != 10 { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiIntFromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "10") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.IntFlag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Int("timeout") != 10 { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.Int("t") != 10 { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiIntSlice(t *testing.T) { |
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.IntSliceFlag{Name: "serve, s", Value: &cli.IntSlice{}}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("serve"), []int{10, 20}) { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("s"), []int{10, 20}) { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run", "-s", "10", "-s", "20"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiIntSliceFromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40") |
||||||
|
|
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "APP_INTERVALS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiIntSliceFromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_INTERVALS", "20,30,40") |
||||||
|
|
||||||
|
(&cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.IntSliceFlag{Name: "intervals, i", Value: &cli.IntSlice{}, EnvVar: "COMPAT_INTERVALS,APP_INTERVALS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("intervals"), []int{20, 30, 40}) { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.IntSlice("i"), []int{20, 30, 40}) { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
}).Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiFloat64(t *testing.T) { |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.Float64Flag{Name: "serve, s"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Float64("serve") != 10.2 { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.Float64("s") != 10.2 { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run", "-s", "10.2"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiFloat64FromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.Float64Flag{Name: "timeout, t", EnvVar: "APP_TIMEOUT_SECONDS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Float64("timeout") != 15.5 { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.Float64("t") != 15.5 { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiFloat64FromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_TIMEOUT_SECONDS", "15.5") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.Float64Flag{Name: "timeout, t", EnvVar: "COMPAT_TIMEOUT_SECONDS,APP_TIMEOUT_SECONDS"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Float64("timeout") != 15.5 { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.Float64("t") != 15.5 { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiBool(t *testing.T) { |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.BoolFlag{Name: "serve, s"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Bool("serve") != true { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.Bool("s") != true { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run", "--serve"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiBoolFromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_DEBUG", "1") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.BoolFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Bool("debug") != true { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if ctx.Bool("d") != true { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiBoolFromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_DEBUG", "1") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.BoolFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.Bool("debug") != true { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if ctx.Bool("d") != true { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiBoolT(t *testing.T) { |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.BoolTFlag{Name: "serve, s"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.BoolT("serve") != true { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if ctx.BoolT("s") != true { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run", "--serve"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiBoolTFromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_DEBUG", "0") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.BoolTFlag{Name: "debug, d", EnvVar: "APP_DEBUG"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.BoolT("debug") != false { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if ctx.BoolT("d") != false { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseMultiBoolTFromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_DEBUG", "0") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.BoolTFlag{Name: "debug, d", EnvVar: "COMPAT_DEBUG,APP_DEBUG"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if ctx.BoolT("debug") != false { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if ctx.BoolT("d") != false { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
type Parser [2]string |
||||||
|
|
||||||
|
func (p *Parser) Set(value string) error { |
||||||
|
parts := strings.Split(value, ",") |
||||||
|
if len(parts) != 2 { |
||||||
|
return fmt.Errorf("invalid format") |
||||||
|
} |
||||||
|
|
||||||
|
(*p)[0] = parts[0] |
||||||
|
(*p)[1] = parts[1] |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Parser) String() string { |
||||||
|
return fmt.Sprintf("%s,%s", p[0], p[1]) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseGeneric(t *testing.T) { |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.GenericFlag{Name: "serve, s", Value: &Parser{}}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"10", "20"}) { |
||||||
|
t.Errorf("main name not set") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"10", "20"}) { |
||||||
|
t.Errorf("short name not set") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run", "-s", "10,20"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseGenericFromEnv(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_SERVE", "20,30") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.GenericFlag{Name: "serve, s", Value: &Parser{}, EnvVar: "APP_SERVE"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.Generic("serve"), &Parser{"20", "30"}) { |
||||||
|
t.Errorf("main name not set from env") |
||||||
|
} |
||||||
|
if !reflect.DeepEqual(ctx.Generic("s"), &Parser{"20", "30"}) { |
||||||
|
t.Errorf("short name not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
||||||
|
|
||||||
|
func TestParseGenericFromEnvCascade(t *testing.T) { |
||||||
|
os.Clearenv() |
||||||
|
os.Setenv("APP_FOO", "99,2000") |
||||||
|
a := cli.App{ |
||||||
|
Flags: []cli.Flag{ |
||||||
|
cli.GenericFlag{Name: "foos", Value: &Parser{}, EnvVar: "COMPAT_FOO,APP_FOO"}, |
||||||
|
}, |
||||||
|
Action: func(ctx *cli.Context) { |
||||||
|
if !reflect.DeepEqual(ctx.Generic("foos"), &Parser{"99", "2000"}) { |
||||||
|
t.Errorf("value not set from env") |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
a.Run([]string{"run"}) |
||||||
|
} |
@ -0,0 +1,211 @@ |
|||||||
|
package cli |
||||||
|
|
||||||
|
import "fmt" |
||||||
|
|
||||||
|
// The text template for the Default help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var AppHelpTemplate = `NAME: |
||||||
|
{{.Name}} - {{.Usage}} |
||||||
|
|
||||||
|
USAGE: |
||||||
|
{{.Name}} {{if .Flags}}[global options] {{end}}command{{if .Flags}} [command options]{{end}} [arguments...] |
||||||
|
|
||||||
|
VERSION: |
||||||
|
{{.Version}}{{if or .Author .Email}} |
||||||
|
|
||||||
|
AUTHOR:{{if .Author}} |
||||||
|
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}} |
||||||
|
{{.Email}}{{end}}{{end}} |
||||||
|
|
||||||
|
COMMANDS: |
||||||
|
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} |
||||||
|
{{end}}{{if .Flags}} |
||||||
|
GLOBAL OPTIONS: |
||||||
|
{{range .Flags}}{{.}} |
||||||
|
{{end}}{{end}} |
||||||
|
` |
||||||
|
|
||||||
|
// The text template for the command help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var CommandHelpTemplate = `NAME: |
||||||
|
{{.Name}} - {{.Usage}} |
||||||
|
|
||||||
|
USAGE: |
||||||
|
command {{.Name}}{{if .Flags}} [command options]{{end}} [arguments...]{{if .Description}} |
||||||
|
|
||||||
|
DESCRIPTION: |
||||||
|
{{.Description}}{{end}}{{if .Flags}} |
||||||
|
|
||||||
|
OPTIONS: |
||||||
|
{{range .Flags}}{{.}} |
||||||
|
{{end}}{{ end }} |
||||||
|
` |
||||||
|
|
||||||
|
// The text template for the subcommand help topic.
|
||||||
|
// cli.go uses text/template to render templates. You can
|
||||||
|
// render custom help text by setting this variable.
|
||||||
|
var SubcommandHelpTemplate = `NAME: |
||||||
|
{{.Name}} - {{.Usage}} |
||||||
|
|
||||||
|
USAGE: |
||||||
|
{{.Name}} command{{if .Flags}} [command options]{{end}} [arguments...] |
||||||
|
|
||||||
|
COMMANDS: |
||||||
|
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}} |
||||||
|
{{end}}{{if .Flags}} |
||||||
|
OPTIONS: |
||||||
|
{{range .Flags}}{{.}} |
||||||
|
{{end}}{{end}} |
||||||
|
` |
||||||
|
|
||||||
|
var helpCommand = Command{ |
||||||
|
Name: "help", |
||||||
|
ShortName: "h", |
||||||
|
Usage: "Shows a list of commands or help for one command", |
||||||
|
Action: func(c *Context) { |
||||||
|
args := c.Args() |
||||||
|
if args.Present() { |
||||||
|
ShowCommandHelp(c, args.First()) |
||||||
|
} else { |
||||||
|
ShowAppHelp(c) |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
var helpSubcommand = Command{ |
||||||
|
Name: "help", |
||||||
|
ShortName: "h", |
||||||
|
Usage: "Shows a list of commands or help for one command", |
||||||
|
Action: func(c *Context) { |
||||||
|
args := c.Args() |
||||||
|
if args.Present() { |
||||||
|
ShowCommandHelp(c, args.First()) |
||||||
|
} else { |
||||||
|
ShowSubcommandHelp(c) |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// Prints help for the App
|
||||||
|
type helpPrinter func(templ string, data interface{}) |
||||||
|
|
||||||
|
var HelpPrinter helpPrinter = nil |
||||||
|
|
||||||
|
// Prints version for the App
|
||||||
|
var VersionPrinter = printVersion |
||||||
|
|
||||||
|
func ShowAppHelp(c *Context) { |
||||||
|
HelpPrinter(AppHelpTemplate, c.App) |
||||||
|
} |
||||||
|
|
||||||
|
// Prints the list of subcommands as the default app completion method
|
||||||
|
func DefaultAppComplete(c *Context) { |
||||||
|
for _, command := range c.App.Commands { |
||||||
|
fmt.Fprintln(c.App.Writer, command.Name) |
||||||
|
if command.ShortName != "" { |
||||||
|
fmt.Fprintln(c.App.Writer, command.ShortName) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Prints help for the given command
|
||||||
|
func ShowCommandHelp(c *Context, command string) { |
||||||
|
for _, c := range c.App.Commands { |
||||||
|
if c.HasName(command) { |
||||||
|
HelpPrinter(CommandHelpTemplate, c) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if c.App.CommandNotFound != nil { |
||||||
|
c.App.CommandNotFound(c, command) |
||||||
|
} else { |
||||||
|
fmt.Fprintf(c.App.Writer, "No help topic for '%v'\n", command) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Prints help for the given subcommand
|
||||||
|
func ShowSubcommandHelp(c *Context) { |
||||||
|
ShowCommandHelp(c, c.Command.Name) |
||||||
|
} |
||||||
|
|
||||||
|
// Prints the version number of the App
|
||||||
|
func ShowVersion(c *Context) { |
||||||
|
VersionPrinter(c) |
||||||
|
} |
||||||
|
|
||||||
|
func printVersion(c *Context) { |
||||||
|
fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) |
||||||
|
} |
||||||
|
|
||||||
|
// Prints the lists of commands within a given context
|
||||||
|
func ShowCompletions(c *Context) { |
||||||
|
a := c.App |
||||||
|
if a != nil && a.BashComplete != nil { |
||||||
|
a.BashComplete(c) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Prints the custom completions for a given command
|
||||||
|
func ShowCommandCompletions(ctx *Context, command string) { |
||||||
|
c := ctx.App.Command(command) |
||||||
|
if c != nil && c.BashComplete != nil { |
||||||
|
c.BashComplete(ctx) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func checkVersion(c *Context) bool { |
||||||
|
if c.GlobalBool("version") { |
||||||
|
ShowVersion(c) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func checkHelp(c *Context) bool { |
||||||
|
if c.GlobalBool("h") || c.GlobalBool("help") { |
||||||
|
ShowAppHelp(c) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func checkCommandHelp(c *Context, name string) bool { |
||||||
|
if c.Bool("h") || c.Bool("help") { |
||||||
|
ShowCommandHelp(c, name) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func checkSubcommandHelp(c *Context) bool { |
||||||
|
if c.GlobalBool("h") || c.GlobalBool("help") { |
||||||
|
ShowSubcommandHelp(c) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func checkCompletions(c *Context) bool { |
||||||
|
if (c.GlobalBool(BashCompletionFlag.Name) || c.Bool(BashCompletionFlag.Name)) && c.App.EnableBashCompletion { |
||||||
|
ShowCompletions(c) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func checkCommandCompletions(c *Context, name string) bool { |
||||||
|
if c.Bool(BashCompletionFlag.Name) && c.App.EnableBashCompletion { |
||||||
|
ShowCommandCompletions(c, name) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
return false |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
package cli_test |
||||||
|
|
||||||
|
import ( |
||||||
|
"reflect" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
/* Test Helpers */ |
||||||
|
func expect(t *testing.T, a interface{}, b interface{}) { |
||||||
|
if a != b { |
||||||
|
t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func refute(t *testing.T, a interface{}, b interface{}) { |
||||||
|
if a == b { |
||||||
|
t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a)) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,10 @@ |
|||||||
|
.idea/ |
||||||
|
.DS_Store |
||||||
|
*/**/*un~ |
||||||
|
.vagrant/ |
||||||
|
*.pyc |
||||||
|
build/ |
||||||
|
pyethash.egg-info/ |
||||||
|
*.so |
||||||
|
*~ |
||||||
|
*.swp |
@ -0,0 +1,6 @@ |
|||||||
|
before_install: |
||||||
|
- sudo apt-get update -qq |
||||||
|
- sudo apt-get install -qq wget cmake gcc bash libboost-test-dev nodejs python-pip python-dev |
||||||
|
- sudo pip install virtualenv -q |
||||||
|
|
||||||
|
script: "./test/test.sh" |
@ -0,0 +1,15 @@ |
|||||||
|
cmake_minimum_required(VERSION 2.8.7) |
||||||
|
project(ethash) |
||||||
|
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/Modules/") |
||||||
|
set(ETHHASH_LIBS ethash) |
||||||
|
|
||||||
|
if (WIN32 AND WANT_CRYPTOPP) |
||||||
|
add_subdirectory(cryptopp) |
||||||
|
endif() |
||||||
|
|
||||||
|
add_subdirectory(src/libethash) |
||||||
|
# bin2h.cmake doesn't work |
||||||
|
#add_subdirectory(src/libethash-cl EXCLUDE_FROM_ALL) |
||||||
|
add_subdirectory(src/benchmark EXCLUDE_FROM_ALL) |
||||||
|
add_subdirectory(test/c EXCLUDE_FROM_ALL) |
@ -0,0 +1,3 @@ |
|||||||
|
.PHONY: clean |
||||||
|
clean: |
||||||
|
rm -rf *.so pyethash.egg-info/ build/ test/python/python-virtual-env/ test/c/build/ pyethash/*.{so,pyc}
|
@ -0,0 +1,7 @@ |
|||||||
|
[![Build Status](https://travis-ci.org/ethereum/ethash.svg?branch=master)](https://travis-ci.org/ethereum/ethash) |
||||||
|
|
||||||
|
|
||||||
|
# Ethash |
||||||
|
|
||||||
|
For details on this project, please see the Ethereum wiki: |
||||||
|
https://github.com/ethereum/wiki/wiki/Ethash |
@ -0,0 +1,7 @@ |
|||||||
|
# -*- mode: ruby -*- |
||||||
|
# vi: set ft=ruby : |
||||||
|
|
||||||
|
Vagrant.configure(2) do |config| |
||||||
|
config.vm.box = "Ubuntu 12.04" |
||||||
|
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/precise/current/precise-server-cloudimg-amd64-vagrant-disk1.box" |
||||||
|
end |
161
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/CMakeParseArguments.cmake
generated
vendored
161
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/CMakeParseArguments.cmake
generated
vendored
@ -0,0 +1,161 @@ |
|||||||
|
#.rst: |
||||||
|
# CMakeParseArguments |
||||||
|
# ------------------- |
||||||
|
# |
||||||
|
# |
||||||
|
# |
||||||
|
# CMAKE_PARSE_ARGUMENTS(<prefix> <options> <one_value_keywords> |
||||||
|
# <multi_value_keywords> args...) |
||||||
|
# |
||||||
|
# CMAKE_PARSE_ARGUMENTS() is intended to be used in macros or functions |
||||||
|
# for parsing the arguments given to that macro or function. It |
||||||
|
# processes the arguments and defines a set of variables which hold the |
||||||
|
# values of the respective options. |
||||||
|
# |
||||||
|
# The <options> argument contains all options for the respective macro, |
||||||
|
# i.e. keywords which can be used when calling the macro without any |
||||||
|
# value following, like e.g. the OPTIONAL keyword of the install() |
||||||
|
# command. |
||||||
|
# |
||||||
|
# The <one_value_keywords> argument contains all keywords for this macro |
||||||
|
# which are followed by one value, like e.g. DESTINATION keyword of the |
||||||
|
# install() command. |
||||||
|
# |
||||||
|
# The <multi_value_keywords> argument contains all keywords for this |
||||||
|
# macro which can be followed by more than one value, like e.g. the |
||||||
|
# TARGETS or FILES keywords of the install() command. |
||||||
|
# |
||||||
|
# When done, CMAKE_PARSE_ARGUMENTS() will have defined for each of the |
||||||
|
# keywords listed in <options>, <one_value_keywords> and |
||||||
|
# <multi_value_keywords> a variable composed of the given <prefix> |
||||||
|
# followed by "_" and the name of the respective keyword. These |
||||||
|
# variables will then hold the respective value from the argument list. |
||||||
|
# For the <options> keywords this will be TRUE or FALSE. |
||||||
|
# |
||||||
|
# All remaining arguments are collected in a variable |
||||||
|
# <prefix>_UNPARSED_ARGUMENTS, this can be checked afterwards to see |
||||||
|
# whether your macro was called with unrecognized parameters. |
||||||
|
# |
||||||
|
# As an example here a my_install() macro, which takes similar arguments |
||||||
|
# as the real install() command: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# function(MY_INSTALL) |
||||||
|
# set(options OPTIONAL FAST) |
||||||
|
# set(oneValueArgs DESTINATION RENAME) |
||||||
|
# set(multiValueArgs TARGETS CONFIGURATIONS) |
||||||
|
# cmake_parse_arguments(MY_INSTALL "${options}" "${oneValueArgs}" |
||||||
|
# "${multiValueArgs}" ${ARGN} ) |
||||||
|
# ... |
||||||
|
# |
||||||
|
# |
||||||
|
# |
||||||
|
# Assume my_install() has been called like this: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# my_install(TARGETS foo bar DESTINATION bin OPTIONAL blub) |
||||||
|
# |
||||||
|
# |
||||||
|
# |
||||||
|
# After the cmake_parse_arguments() call the macro will have set the |
||||||
|
# following variables: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# MY_INSTALL_OPTIONAL = TRUE |
||||||
|
# MY_INSTALL_FAST = FALSE (this option was not used when calling my_install() |
||||||
|
# MY_INSTALL_DESTINATION = "bin" |
||||||
|
# MY_INSTALL_RENAME = "" (was not used) |
||||||
|
# MY_INSTALL_TARGETS = "foo;bar" |
||||||
|
# MY_INSTALL_CONFIGURATIONS = "" (was not used) |
||||||
|
# MY_INSTALL_UNPARSED_ARGUMENTS = "blub" (no value expected after "OPTIONAL" |
||||||
|
# |
||||||
|
# |
||||||
|
# |
||||||
|
# You can then continue and process these variables. |
||||||
|
# |
||||||
|
# Keywords terminate lists of values, e.g. if directly after a |
||||||
|
# one_value_keyword another recognized keyword follows, this is |
||||||
|
# interpreted as the beginning of the new option. E.g. |
||||||
|
# my_install(TARGETS foo DESTINATION OPTIONAL) would result in |
||||||
|
# MY_INSTALL_DESTINATION set to "OPTIONAL", but MY_INSTALL_DESTINATION |
||||||
|
# would be empty and MY_INSTALL_OPTIONAL would be set to TRUE therefor. |
||||||
|
|
||||||
|
#============================================================================= |
||||||
|
# Copyright 2010 Alexander Neundorf <neundorf@kde.org> |
||||||
|
# |
||||||
|
# Distributed under the OSI-approved BSD License (the "License"); |
||||||
|
# see accompanying file Copyright.txt for details. |
||||||
|
# |
||||||
|
# This software is distributed WITHOUT ANY WARRANTY; without even the |
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||||
|
# See the License for more information. |
||||||
|
#============================================================================= |
||||||
|
# (To distribute this file outside of CMake, substitute the full |
||||||
|
# License text for the above reference.) |
||||||
|
|
||||||
|
|
||||||
|
if(__CMAKE_PARSE_ARGUMENTS_INCLUDED) |
||||||
|
return() |
||||||
|
endif() |
||||||
|
set(__CMAKE_PARSE_ARGUMENTS_INCLUDED TRUE) |
||||||
|
|
||||||
|
|
||||||
|
function(CMAKE_PARSE_ARGUMENTS prefix _optionNames _singleArgNames _multiArgNames) |
||||||
|
# first set all result variables to empty/FALSE |
||||||
|
foreach(arg_name ${_singleArgNames} ${_multiArgNames}) |
||||||
|
set(${prefix}_${arg_name}) |
||||||
|
endforeach() |
||||||
|
|
||||||
|
foreach(option ${_optionNames}) |
||||||
|
set(${prefix}_${option} FALSE) |
||||||
|
endforeach() |
||||||
|
|
||||||
|
set(${prefix}_UNPARSED_ARGUMENTS) |
||||||
|
|
||||||
|
set(insideValues FALSE) |
||||||
|
set(currentArgName) |
||||||
|
|
||||||
|
# now iterate over all arguments and fill the result variables |
||||||
|
foreach(currentArg ${ARGN}) |
||||||
|
list(FIND _optionNames "${currentArg}" optionIndex) # ... then this marks the end of the arguments belonging to this keyword |
||||||
|
list(FIND _singleArgNames "${currentArg}" singleArgIndex) # ... then this marks the end of the arguments belonging to this keyword |
||||||
|
list(FIND _multiArgNames "${currentArg}" multiArgIndex) # ... then this marks the end of the arguments belonging to this keyword |
||||||
|
|
||||||
|
if(${optionIndex} EQUAL -1 AND ${singleArgIndex} EQUAL -1 AND ${multiArgIndex} EQUAL -1) |
||||||
|
if(insideValues) |
||||||
|
if("${insideValues}" STREQUAL "SINGLE") |
||||||
|
set(${prefix}_${currentArgName} ${currentArg}) |
||||||
|
set(insideValues FALSE) |
||||||
|
elseif("${insideValues}" STREQUAL "MULTI") |
||||||
|
list(APPEND ${prefix}_${currentArgName} ${currentArg}) |
||||||
|
endif() |
||||||
|
else() |
||||||
|
list(APPEND ${prefix}_UNPARSED_ARGUMENTS ${currentArg}) |
||||||
|
endif() |
||||||
|
else() |
||||||
|
if(NOT ${optionIndex} EQUAL -1) |
||||||
|
set(${prefix}_${currentArg} TRUE) |
||||||
|
set(insideValues FALSE) |
||||||
|
elseif(NOT ${singleArgIndex} EQUAL -1) |
||||||
|
set(currentArgName ${currentArg}) |
||||||
|
set(${prefix}_${currentArgName}) |
||||||
|
set(insideValues "SINGLE") |
||||||
|
elseif(NOT ${multiArgIndex} EQUAL -1) |
||||||
|
set(currentArgName ${currentArg}) |
||||||
|
set(${prefix}_${currentArgName}) |
||||||
|
set(insideValues "MULTI") |
||||||
|
endif() |
||||||
|
endif() |
||||||
|
|
||||||
|
endforeach() |
||||||
|
|
||||||
|
# propagate the result variables to the caller: |
||||||
|
foreach(arg_name ${_singleArgNames} ${_multiArgNames} ${_optionNames}) |
||||||
|
set(${prefix}_${arg_name} ${${prefix}_${arg_name}} PARENT_SCOPE) |
||||||
|
endforeach() |
||||||
|
set(${prefix}_UNPARSED_ARGUMENTS ${${prefix}_UNPARSED_ARGUMENTS} PARENT_SCOPE) |
||||||
|
|
||||||
|
endfunction() |
108
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/FindCryptoPP.cmake
generated
vendored
108
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/FindCryptoPP.cmake
generated
vendored
@ -0,0 +1,108 @@ |
|||||||
|
# Module for locating the Crypto++ encryption library. |
||||||
|
# |
||||||
|
# Customizable variables: |
||||||
|
# CRYPTOPP_ROOT_DIR |
||||||
|
# This variable points to the CryptoPP root directory. On Windows the |
||||||
|
# library location typically will have to be provided explicitly using the |
||||||
|
# -D command-line option. The directory should include the include/cryptopp, |
||||||
|
# lib and/or bin sub-directories. |
||||||
|
# |
||||||
|
# Read-only variables: |
||||||
|
# CRYPTOPP_FOUND |
||||||
|
# Indicates whether the library has been found. |
||||||
|
# |
||||||
|
# CRYPTOPP_INCLUDE_DIRS |
||||||
|
# Points to the CryptoPP include directory. |
||||||
|
# |
||||||
|
# CRYPTOPP_LIBRARIES |
||||||
|
# Points to the CryptoPP libraries that should be passed to |
||||||
|
# target_link_libararies. |
||||||
|
# |
||||||
|
# |
||||||
|
# Copyright (c) 2012 Sergiu Dotenco |
||||||
|
# |
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
# of this software and associated documentation files (the "Software"), to deal |
||||||
|
# in the Software without restriction, including without limitation the rights |
||||||
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
# copies of the Software, and to permit persons to whom the Software is |
||||||
|
# furnished to do so, subject to the following conditions: |
||||||
|
# |
||||||
|
# The above copyright notice and this permission notice shall be included in all |
||||||
|
# copies or substantial portions of the Software. |
||||||
|
# |
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
# SOFTWARE. |
||||||
|
|
||||||
|
INCLUDE (FindPackageHandleStandardArgs) |
||||||
|
|
||||||
|
FIND_PATH (CRYPTOPP_ROOT_DIR |
||||||
|
NAMES cryptopp/cryptlib.h include/cryptopp/cryptlib.h |
||||||
|
PATHS ENV CRYPTOPPROOT |
||||||
|
DOC "CryptoPP root directory") |
||||||
|
|
||||||
|
# Re-use the previous path: |
||||||
|
FIND_PATH (CRYPTOPP_INCLUDE_DIR |
||||||
|
NAMES cryptopp/cryptlib.h |
||||||
|
HINTS ${CRYPTOPP_ROOT_DIR} |
||||||
|
PATH_SUFFIXES include |
||||||
|
DOC "CryptoPP include directory") |
||||||
|
|
||||||
|
FIND_LIBRARY (CRYPTOPP_LIBRARY_DEBUG |
||||||
|
NAMES cryptlibd cryptoppd |
||||||
|
HINTS ${CRYPTOPP_ROOT_DIR} |
||||||
|
PATH_SUFFIXES lib |
||||||
|
DOC "CryptoPP debug library") |
||||||
|
|
||||||
|
FIND_LIBRARY (CRYPTOPP_LIBRARY_RELEASE |
||||||
|
NAMES cryptlib cryptopp |
||||||
|
HINTS ${CRYPTOPP_ROOT_DIR} |
||||||
|
PATH_SUFFIXES lib |
||||||
|
DOC "CryptoPP release library") |
||||||
|
|
||||||
|
IF (CRYPTOPP_LIBRARY_DEBUG AND CRYPTOPP_LIBRARY_RELEASE) |
||||||
|
SET (CRYPTOPP_LIBRARY |
||||||
|
optimized ${CRYPTOPP_LIBRARY_RELEASE} |
||||||
|
debug ${CRYPTOPP_LIBRARY_DEBUG} CACHE DOC "CryptoPP library") |
||||||
|
ELSEIF (CRYPTOPP_LIBRARY_RELEASE) |
||||||
|
SET (CRYPTOPP_LIBRARY ${CRYPTOPP_LIBRARY_RELEASE} CACHE DOC |
||||||
|
"CryptoPP library") |
||||||
|
ENDIF (CRYPTOPP_LIBRARY_DEBUG AND CRYPTOPP_LIBRARY_RELEASE) |
||||||
|
|
||||||
|
IF (CRYPTOPP_INCLUDE_DIR) |
||||||
|
SET (_CRYPTOPP_VERSION_HEADER ${CRYPTOPP_INCLUDE_DIR}/cryptopp/config.h) |
||||||
|
|
||||||
|
IF (EXISTS ${_CRYPTOPP_VERSION_HEADER}) |
||||||
|
FILE (STRINGS ${_CRYPTOPP_VERSION_HEADER} _CRYPTOPP_VERSION_TMP REGEX |
||||||
|
"^#define CRYPTOPP_VERSION[ \t]+[0-9]+$") |
||||||
|
|
||||||
|
STRING (REGEX REPLACE |
||||||
|
"^#define CRYPTOPP_VERSION[ \t]+([0-9]+)" "\\1" _CRYPTOPP_VERSION_TMP |
||||||
|
${_CRYPTOPP_VERSION_TMP}) |
||||||
|
|
||||||
|
STRING (REGEX REPLACE "([0-9]+)[0-9][0-9]" "\\1" CRYPTOPP_VERSION_MAJOR |
||||||
|
${_CRYPTOPP_VERSION_TMP}) |
||||||
|
STRING (REGEX REPLACE "[0-9]([0-9])[0-9]" "\\1" CRYPTOPP_VERSION_MINOR |
||||||
|
${_CRYPTOPP_VERSION_TMP}) |
||||||
|
STRING (REGEX REPLACE "[0-9][0-9]([0-9])" "\\1" CRYPTOPP_VERSION_PATCH |
||||||
|
${_CRYPTOPP_VERSION_TMP}) |
||||||
|
|
||||||
|
SET (CRYPTOPP_VERSION_COUNT 3) |
||||||
|
SET (CRYPTOPP_VERSION |
||||||
|
${CRYPTOPP_VERSION_MAJOR}.${CRYPTOPP_VERSION_MINOR}.${CRYPTOPP_VERSION_PATCH}) |
||||||
|
ENDIF (EXISTS ${_CRYPTOPP_VERSION_HEADER}) |
||||||
|
ENDIF (CRYPTOPP_INCLUDE_DIR) |
||||||
|
|
||||||
|
SET (CRYPTOPP_INCLUDE_DIRS ${CRYPTOPP_INCLUDE_DIR}) |
||||||
|
SET (CRYPTOPP_LIBRARIES ${CRYPTOPP_LIBRARY}) |
||||||
|
|
||||||
|
MARK_AS_ADVANCED (CRYPTOPP_INCLUDE_DIR CRYPTOPP_LIBRARY CRYPTOPP_LIBRARY_DEBUG |
||||||
|
CRYPTOPP_LIBRARY_RELEASE) |
||||||
|
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS (CryptoPP REQUIRED_VARS CRYPTOPP_ROOT_DIR |
||||||
|
CRYPTOPP_INCLUDE_DIR CRYPTOPP_LIBRARY VERSION_VAR CRYPTOPP_VERSION) |
136
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/FindOpenCL.cmake
generated
vendored
136
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/FindOpenCL.cmake
generated
vendored
@ -0,0 +1,136 @@ |
|||||||
|
#.rst: |
||||||
|
# FindOpenCL |
||||||
|
# ---------- |
||||||
|
# |
||||||
|
# Try to find OpenCL |
||||||
|
# |
||||||
|
# Once done this will define:: |
||||||
|
# |
||||||
|
# OpenCL_FOUND - True if OpenCL was found |
||||||
|
# OpenCL_INCLUDE_DIRS - include directories for OpenCL |
||||||
|
# OpenCL_LIBRARIES - link against this library to use OpenCL |
||||||
|
# OpenCL_VERSION_STRING - Highest supported OpenCL version (eg. 1.2) |
||||||
|
# OpenCL_VERSION_MAJOR - The major version of the OpenCL implementation |
||||||
|
# OpenCL_VERSION_MINOR - The minor version of the OpenCL implementation |
||||||
|
# |
||||||
|
# The module will also define two cache variables:: |
||||||
|
# |
||||||
|
# OpenCL_INCLUDE_DIR - the OpenCL include directory |
||||||
|
# OpenCL_LIBRARY - the path to the OpenCL library |
||||||
|
# |
||||||
|
|
||||||
|
#============================================================================= |
||||||
|
# Copyright 2014 Matthaeus G. Chajdas |
||||||
|
# |
||||||
|
# Distributed under the OSI-approved BSD License (the "License"); |
||||||
|
# see accompanying file Copyright.txt for details. |
||||||
|
# |
||||||
|
# This software is distributed WITHOUT ANY WARRANTY; without even the |
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||||
|
# See the License for more information. |
||||||
|
#============================================================================= |
||||||
|
# (To distribute this file outside of CMake, substitute the full |
||||||
|
# License text for the above reference.) |
||||||
|
|
||||||
|
function(_FIND_OPENCL_VERSION) |
||||||
|
include(CheckSymbolExists) |
||||||
|
include(CMakePushCheckState) |
||||||
|
set(CMAKE_REQUIRED_QUIET ${OpenCL_FIND_QUIETLY}) |
||||||
|
|
||||||
|
CMAKE_PUSH_CHECK_STATE() |
||||||
|
foreach(VERSION "2_0" "1_2" "1_1" "1_0") |
||||||
|
set(CMAKE_REQUIRED_INCLUDES "${OpenCL_INCLUDE_DIR}") |
||||||
|
|
||||||
|
if(APPLE) |
||||||
|
CHECK_SYMBOL_EXISTS( |
||||||
|
CL_VERSION_${VERSION} |
||||||
|
"${OpenCL_INCLUDE_DIR}/OpenCL/cl.h" |
||||||
|
OPENCL_VERSION_${VERSION}) |
||||||
|
else() |
||||||
|
CHECK_SYMBOL_EXISTS( |
||||||
|
CL_VERSION_${VERSION} |
||||||
|
"${OpenCL_INCLUDE_DIR}/CL/cl.h" |
||||||
|
OPENCL_VERSION_${VERSION}) |
||||||
|
endif() |
||||||
|
|
||||||
|
if(OPENCL_VERSION_${VERSION}) |
||||||
|
string(REPLACE "_" "." VERSION "${VERSION}") |
||||||
|
set(OpenCL_VERSION_STRING ${VERSION} PARENT_SCOPE) |
||||||
|
string(REGEX MATCHALL "[0-9]+" version_components "${VERSION}") |
||||||
|
list(GET version_components 0 major_version) |
||||||
|
list(GET version_components 1 minor_version) |
||||||
|
set(OpenCL_VERSION_MAJOR ${major_version} PARENT_SCOPE) |
||||||
|
set(OpenCL_VERSION_MINOR ${minor_version} PARENT_SCOPE) |
||||||
|
break() |
||||||
|
endif() |
||||||
|
endforeach() |
||||||
|
CMAKE_POP_CHECK_STATE() |
||||||
|
endfunction() |
||||||
|
|
||||||
|
find_path(OpenCL_INCLUDE_DIR |
||||||
|
NAMES |
||||||
|
CL/cl.h OpenCL/cl.h |
||||||
|
PATHS |
||||||
|
ENV "PROGRAMFILES(X86)" |
||||||
|
ENV AMDAPPSDKROOT |
||||||
|
ENV INTELOCLSDKROOT |
||||||
|
ENV NVSDKCOMPUTE_ROOT |
||||||
|
ENV CUDA_PATH |
||||||
|
ENV ATISTREAMSDKROOT |
||||||
|
PATH_SUFFIXES |
||||||
|
include |
||||||
|
OpenCL/common/inc |
||||||
|
"AMD APP/include") |
||||||
|
|
||||||
|
_FIND_OPENCL_VERSION() |
||||||
|
|
||||||
|
if(WIN32) |
||||||
|
if(CMAKE_SIZEOF_VOID_P EQUAL 4) |
||||||
|
find_library(OpenCL_LIBRARY |
||||||
|
NAMES OpenCL |
||||||
|
PATHS |
||||||
|
ENV "PROGRAMFILES(X86)" |
||||||
|
ENV AMDAPPSDKROOT |
||||||
|
ENV INTELOCLSDKROOT |
||||||
|
ENV CUDA_PATH |
||||||
|
ENV NVSDKCOMPUTE_ROOT |
||||||
|
ENV ATISTREAMSDKROOT |
||||||
|
PATH_SUFFIXES |
||||||
|
"AMD APP/lib/x86" |
||||||
|
lib/x86 |
||||||
|
lib/Win32 |
||||||
|
OpenCL/common/lib/Win32) |
||||||
|
elseif(CMAKE_SIZEOF_VOID_P EQUAL 8) |
||||||
|
find_library(OpenCL_LIBRARY |
||||||
|
NAMES OpenCL |
||||||
|
PATHS |
||||||
|
ENV "PROGRAMFILES(X86)" |
||||||
|
ENV AMDAPPSDKROOT |
||||||
|
ENV INTELOCLSDKROOT |
||||||
|
ENV CUDA_PATH |
||||||
|
ENV NVSDKCOMPUTE_ROOT |
||||||
|
ENV ATISTREAMSDKROOT |
||||||
|
PATH_SUFFIXES |
||||||
|
"AMD APP/lib/x86_64" |
||||||
|
lib/x86_64 |
||||||
|
lib/x64 |
||||||
|
OpenCL/common/lib/x64) |
||||||
|
endif() |
||||||
|
else() |
||||||
|
find_library(OpenCL_LIBRARY |
||||||
|
NAMES OpenCL) |
||||||
|
endif() |
||||||
|
|
||||||
|
set(OpenCL_LIBRARIES ${OpenCL_LIBRARY}) |
||||||
|
set(OpenCL_INCLUDE_DIRS ${OpenCL_INCLUDE_DIR}) |
||||||
|
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageHandleStandardArgs.cmake) |
||||||
|
find_package_handle_standard_args( |
||||||
|
OpenCL |
||||||
|
FOUND_VAR OpenCL_FOUND |
||||||
|
REQUIRED_VARS OpenCL_LIBRARY OpenCL_INCLUDE_DIR |
||||||
|
VERSION_VAR OpenCL_VERSION_STRING) |
||||||
|
|
||||||
|
mark_as_advanced( |
||||||
|
OpenCL_INCLUDE_DIR |
||||||
|
OpenCL_LIBRARY) |
@ -0,0 +1,382 @@ |
|||||||
|
#.rst: |
||||||
|
# FindPackageHandleStandardArgs |
||||||
|
# ----------------------------- |
||||||
|
# |
||||||
|
# |
||||||
|
# |
||||||
|
# FIND_PACKAGE_HANDLE_STANDARD_ARGS(<name> ... ) |
||||||
|
# |
||||||
|
# This function is intended to be used in FindXXX.cmake modules files. |
||||||
|
# It handles the REQUIRED, QUIET and version-related arguments to |
||||||
|
# find_package(). It also sets the <packagename>_FOUND variable. The |
||||||
|
# package is considered found if all variables <var1>... listed contain |
||||||
|
# valid results, e.g. valid filepaths. |
||||||
|
# |
||||||
|
# There are two modes of this function. The first argument in both |
||||||
|
# modes is the name of the Find-module where it is called (in original |
||||||
|
# casing). |
||||||
|
# |
||||||
|
# The first simple mode looks like this: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# FIND_PACKAGE_HANDLE_STANDARD_ARGS(<name> |
||||||
|
# (DEFAULT_MSG|"Custom failure message") <var1>...<varN> ) |
||||||
|
# |
||||||
|
# If the variables <var1> to <varN> are all valid, then |
||||||
|
# <UPPERCASED_NAME>_FOUND will be set to TRUE. If DEFAULT_MSG is given |
||||||
|
# as second argument, then the function will generate itself useful |
||||||
|
# success and error messages. You can also supply a custom error |
||||||
|
# message for the failure case. This is not recommended. |
||||||
|
# |
||||||
|
# The second mode is more powerful and also supports version checking: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# FIND_PACKAGE_HANDLE_STANDARD_ARGS(NAME |
||||||
|
# [FOUND_VAR <resultVar>] |
||||||
|
# [REQUIRED_VARS <var1>...<varN>] |
||||||
|
# [VERSION_VAR <versionvar>] |
||||||
|
# [HANDLE_COMPONENTS] |
||||||
|
# [CONFIG_MODE] |
||||||
|
# [FAIL_MESSAGE "Custom failure message"] ) |
||||||
|
# |
||||||
|
# In this mode, the name of the result-variable can be set either to |
||||||
|
# either <UPPERCASED_NAME>_FOUND or <OriginalCase_Name>_FOUND using the |
||||||
|
# FOUND_VAR option. Other names for the result-variable are not |
||||||
|
# allowed. So for a Find-module named FindFooBar.cmake, the two |
||||||
|
# possible names are FooBar_FOUND and FOOBAR_FOUND. It is recommended |
||||||
|
# to use the original case version. If the FOUND_VAR option is not |
||||||
|
# used, the default is <UPPERCASED_NAME>_FOUND. |
||||||
|
# |
||||||
|
# As in the simple mode, if <var1> through <varN> are all valid, |
||||||
|
# <packagename>_FOUND will be set to TRUE. After REQUIRED_VARS the |
||||||
|
# variables which are required for this package are listed. Following |
||||||
|
# VERSION_VAR the name of the variable can be specified which holds the |
||||||
|
# version of the package which has been found. If this is done, this |
||||||
|
# version will be checked against the (potentially) specified required |
||||||
|
# version used in the find_package() call. The EXACT keyword is also |
||||||
|
# handled. The default messages include information about the required |
||||||
|
# version and the version which has been actually found, both if the |
||||||
|
# version is ok or not. If the package supports components, use the |
||||||
|
# HANDLE_COMPONENTS option to enable handling them. In this case, |
||||||
|
# find_package_handle_standard_args() will report which components have |
||||||
|
# been found and which are missing, and the <packagename>_FOUND variable |
||||||
|
# will be set to FALSE if any of the required components (i.e. not the |
||||||
|
# ones listed after OPTIONAL_COMPONENTS) are missing. Use the option |
||||||
|
# CONFIG_MODE if your FindXXX.cmake module is a wrapper for a |
||||||
|
# find_package(... NO_MODULE) call. In this case VERSION_VAR will be |
||||||
|
# set to <NAME>_VERSION and the macro will automatically check whether |
||||||
|
# the Config module was found. Via FAIL_MESSAGE a custom failure |
||||||
|
# message can be specified, if this is not used, the default message |
||||||
|
# will be displayed. |
||||||
|
# |
||||||
|
# Example for mode 1: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# find_package_handle_standard_args(LibXml2 DEFAULT_MSG |
||||||
|
# LIBXML2_LIBRARY LIBXML2_INCLUDE_DIR) |
||||||
|
# |
||||||
|
# |
||||||
|
# |
||||||
|
# LibXml2 is considered to be found, if both LIBXML2_LIBRARY and |
||||||
|
# LIBXML2_INCLUDE_DIR are valid. Then also LIBXML2_FOUND is set to |
||||||
|
# TRUE. If it is not found and REQUIRED was used, it fails with |
||||||
|
# FATAL_ERROR, independent whether QUIET was used or not. If it is |
||||||
|
# found, success will be reported, including the content of <var1>. On |
||||||
|
# repeated Cmake runs, the same message won't be printed again. |
||||||
|
# |
||||||
|
# Example for mode 2: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# find_package_handle_standard_args(LibXslt |
||||||
|
# FOUND_VAR LibXslt_FOUND |
||||||
|
# REQUIRED_VARS LibXslt_LIBRARIES LibXslt_INCLUDE_DIRS |
||||||
|
# VERSION_VAR LibXslt_VERSION_STRING) |
||||||
|
# |
||||||
|
# In this case, LibXslt is considered to be found if the variable(s) |
||||||
|
# listed after REQUIRED_VAR are all valid, i.e. LibXslt_LIBRARIES and |
||||||
|
# LibXslt_INCLUDE_DIRS in this case. The result will then be stored in |
||||||
|
# LibXslt_FOUND . Also the version of LibXslt will be checked by using |
||||||
|
# the version contained in LibXslt_VERSION_STRING. Since no |
||||||
|
# FAIL_MESSAGE is given, the default messages will be printed. |
||||||
|
# |
||||||
|
# Another example for mode 2: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# find_package(Automoc4 QUIET NO_MODULE HINTS /opt/automoc4) |
||||||
|
# find_package_handle_standard_args(Automoc4 CONFIG_MODE) |
||||||
|
# |
||||||
|
# In this case, FindAutmoc4.cmake wraps a call to find_package(Automoc4 |
||||||
|
# NO_MODULE) and adds an additional search directory for automoc4. Here |
||||||
|
# the result will be stored in AUTOMOC4_FOUND. The following |
||||||
|
# FIND_PACKAGE_HANDLE_STANDARD_ARGS() call produces a proper |
||||||
|
# success/error message. |
||||||
|
|
||||||
|
#============================================================================= |
||||||
|
# Copyright 2007-2009 Kitware, Inc. |
||||||
|
# |
||||||
|
# Distributed under the OSI-approved BSD License (the "License"); |
||||||
|
# see accompanying file Copyright.txt for details. |
||||||
|
# |
||||||
|
# This software is distributed WITHOUT ANY WARRANTY; without even the |
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||||
|
# See the License for more information. |
||||||
|
#============================================================================= |
||||||
|
# (To distribute this file outside of CMake, substitute the full |
||||||
|
# License text for the above reference.) |
||||||
|
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/FindPackageMessage.cmake) |
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/CMakeParseArguments.cmake) |
||||||
|
|
||||||
|
# internal helper macro |
||||||
|
macro(_FPHSA_FAILURE_MESSAGE _msg) |
||||||
|
if (${_NAME}_FIND_REQUIRED) |
||||||
|
message(FATAL_ERROR "${_msg}") |
||||||
|
else () |
||||||
|
if (NOT ${_NAME}_FIND_QUIETLY) |
||||||
|
message(STATUS "${_msg}") |
||||||
|
endif () |
||||||
|
endif () |
||||||
|
endmacro() |
||||||
|
|
||||||
|
|
||||||
|
# internal helper macro to generate the failure message when used in CONFIG_MODE: |
||||||
|
macro(_FPHSA_HANDLE_FAILURE_CONFIG_MODE) |
||||||
|
# <name>_CONFIG is set, but FOUND is false, this means that some other of the REQUIRED_VARS was not found: |
||||||
|
if(${_NAME}_CONFIG) |
||||||
|
_FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: missing: ${MISSING_VARS} (found ${${_NAME}_CONFIG} ${VERSION_MSG})") |
||||||
|
else() |
||||||
|
# If _CONSIDERED_CONFIGS is set, the config-file has been found, but no suitable version. |
||||||
|
# List them all in the error message: |
||||||
|
if(${_NAME}_CONSIDERED_CONFIGS) |
||||||
|
set(configsText "") |
||||||
|
list(LENGTH ${_NAME}_CONSIDERED_CONFIGS configsCount) |
||||||
|
math(EXPR configsCount "${configsCount} - 1") |
||||||
|
foreach(currentConfigIndex RANGE ${configsCount}) |
||||||
|
list(GET ${_NAME}_CONSIDERED_CONFIGS ${currentConfigIndex} filename) |
||||||
|
list(GET ${_NAME}_CONSIDERED_VERSIONS ${currentConfigIndex} version) |
||||||
|
set(configsText "${configsText} ${filename} (version ${version})\n") |
||||||
|
endforeach() |
||||||
|
if (${_NAME}_NOT_FOUND_MESSAGE) |
||||||
|
set(configsText "${configsText} Reason given by package: ${${_NAME}_NOT_FOUND_MESSAGE}\n") |
||||||
|
endif() |
||||||
|
_FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} ${VERSION_MSG}, checked the following files:\n${configsText}") |
||||||
|
|
||||||
|
else() |
||||||
|
# Simple case: No Config-file was found at all: |
||||||
|
_FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: found neither ${_NAME}Config.cmake nor ${_NAME_LOWER}-config.cmake ${VERSION_MSG}") |
||||||
|
endif() |
||||||
|
endif() |
||||||
|
endmacro() |
||||||
|
|
||||||
|
|
||||||
|
function(FIND_PACKAGE_HANDLE_STANDARD_ARGS _NAME _FIRST_ARG) |
||||||
|
|
||||||
|
# set up the arguments for CMAKE_PARSE_ARGUMENTS and check whether we are in |
||||||
|
# new extended or in the "old" mode: |
||||||
|
set(options CONFIG_MODE HANDLE_COMPONENTS) |
||||||
|
set(oneValueArgs FAIL_MESSAGE VERSION_VAR FOUND_VAR) |
||||||
|
set(multiValueArgs REQUIRED_VARS) |
||||||
|
set(_KEYWORDS_FOR_EXTENDED_MODE ${options} ${oneValueArgs} ${multiValueArgs} ) |
||||||
|
list(FIND _KEYWORDS_FOR_EXTENDED_MODE "${_FIRST_ARG}" INDEX) |
||||||
|
|
||||||
|
if(${INDEX} EQUAL -1) |
||||||
|
set(FPHSA_FAIL_MESSAGE ${_FIRST_ARG}) |
||||||
|
set(FPHSA_REQUIRED_VARS ${ARGN}) |
||||||
|
set(FPHSA_VERSION_VAR) |
||||||
|
else() |
||||||
|
|
||||||
|
CMAKE_PARSE_ARGUMENTS(FPHSA "${options}" "${oneValueArgs}" "${multiValueArgs}" ${_FIRST_ARG} ${ARGN}) |
||||||
|
|
||||||
|
if(FPHSA_UNPARSED_ARGUMENTS) |
||||||
|
message(FATAL_ERROR "Unknown keywords given to FIND_PACKAGE_HANDLE_STANDARD_ARGS(): \"${FPHSA_UNPARSED_ARGUMENTS}\"") |
||||||
|
endif() |
||||||
|
|
||||||
|
if(NOT FPHSA_FAIL_MESSAGE) |
||||||
|
set(FPHSA_FAIL_MESSAGE "DEFAULT_MSG") |
||||||
|
endif() |
||||||
|
endif() |
||||||
|
|
||||||
|
# now that we collected all arguments, process them |
||||||
|
|
||||||
|
if("x${FPHSA_FAIL_MESSAGE}" STREQUAL "xDEFAULT_MSG") |
||||||
|
set(FPHSA_FAIL_MESSAGE "Could NOT find ${_NAME}") |
||||||
|
endif() |
||||||
|
|
||||||
|
# In config-mode, we rely on the variable <package>_CONFIG, which is set by find_package() |
||||||
|
# when it successfully found the config-file, including version checking: |
||||||
|
if(FPHSA_CONFIG_MODE) |
||||||
|
list(INSERT FPHSA_REQUIRED_VARS 0 ${_NAME}_CONFIG) |
||||||
|
list(REMOVE_DUPLICATES FPHSA_REQUIRED_VARS) |
||||||
|
set(FPHSA_VERSION_VAR ${_NAME}_VERSION) |
||||||
|
endif() |
||||||
|
|
||||||
|
if(NOT FPHSA_REQUIRED_VARS) |
||||||
|
message(FATAL_ERROR "No REQUIRED_VARS specified for FIND_PACKAGE_HANDLE_STANDARD_ARGS()") |
||||||
|
endif() |
||||||
|
|
||||||
|
list(GET FPHSA_REQUIRED_VARS 0 _FIRST_REQUIRED_VAR) |
||||||
|
|
||||||
|
string(TOUPPER ${_NAME} _NAME_UPPER) |
||||||
|
string(TOLOWER ${_NAME} _NAME_LOWER) |
||||||
|
|
||||||
|
if(FPHSA_FOUND_VAR) |
||||||
|
if(FPHSA_FOUND_VAR MATCHES "^${_NAME}_FOUND$" OR FPHSA_FOUND_VAR MATCHES "^${_NAME_UPPER}_FOUND$") |
||||||
|
set(_FOUND_VAR ${FPHSA_FOUND_VAR}) |
||||||
|
else() |
||||||
|
message(FATAL_ERROR "The argument for FOUND_VAR is \"${FPHSA_FOUND_VAR}\", but only \"${_NAME}_FOUND\" and \"${_NAME_UPPER}_FOUND\" are valid names.") |
||||||
|
endif() |
||||||
|
else() |
||||||
|
set(_FOUND_VAR ${_NAME_UPPER}_FOUND) |
||||||
|
endif() |
||||||
|
|
||||||
|
# collect all variables which were not found, so they can be printed, so the |
||||||
|
# user knows better what went wrong (#6375) |
||||||
|
set(MISSING_VARS "") |
||||||
|
set(DETAILS "") |
||||||
|
# check if all passed variables are valid |
||||||
|
unset(${_FOUND_VAR}) |
||||||
|
foreach(_CURRENT_VAR ${FPHSA_REQUIRED_VARS}) |
||||||
|
if(NOT ${_CURRENT_VAR}) |
||||||
|
set(${_FOUND_VAR} FALSE) |
||||||
|
set(MISSING_VARS "${MISSING_VARS} ${_CURRENT_VAR}") |
||||||
|
else() |
||||||
|
set(DETAILS "${DETAILS}[${${_CURRENT_VAR}}]") |
||||||
|
endif() |
||||||
|
endforeach() |
||||||
|
if(NOT "${${_FOUND_VAR}}" STREQUAL "FALSE") |
||||||
|
set(${_FOUND_VAR} TRUE) |
||||||
|
endif() |
||||||
|
|
||||||
|
# component handling |
||||||
|
unset(FOUND_COMPONENTS_MSG) |
||||||
|
unset(MISSING_COMPONENTS_MSG) |
||||||
|
|
||||||
|
if(FPHSA_HANDLE_COMPONENTS) |
||||||
|
foreach(comp ${${_NAME}_FIND_COMPONENTS}) |
||||||
|
if(${_NAME}_${comp}_FOUND) |
||||||
|
|
||||||
|
if(NOT DEFINED FOUND_COMPONENTS_MSG) |
||||||
|
set(FOUND_COMPONENTS_MSG "found components: ") |
||||||
|
endif() |
||||||
|
set(FOUND_COMPONENTS_MSG "${FOUND_COMPONENTS_MSG} ${comp}") |
||||||
|
|
||||||
|
else() |
||||||
|
|
||||||
|
if(NOT DEFINED MISSING_COMPONENTS_MSG) |
||||||
|
set(MISSING_COMPONENTS_MSG "missing components: ") |
||||||
|
endif() |
||||||
|
set(MISSING_COMPONENTS_MSG "${MISSING_COMPONENTS_MSG} ${comp}") |
||||||
|
|
||||||
|
if(${_NAME}_FIND_REQUIRED_${comp}) |
||||||
|
set(${_FOUND_VAR} FALSE) |
||||||
|
set(MISSING_VARS "${MISSING_VARS} ${comp}") |
||||||
|
endif() |
||||||
|
|
||||||
|
endif() |
||||||
|
endforeach() |
||||||
|
set(COMPONENT_MSG "${FOUND_COMPONENTS_MSG} ${MISSING_COMPONENTS_MSG}") |
||||||
|
set(DETAILS "${DETAILS}[c${COMPONENT_MSG}]") |
||||||
|
endif() |
||||||
|
|
||||||
|
# version handling: |
||||||
|
set(VERSION_MSG "") |
||||||
|
set(VERSION_OK TRUE) |
||||||
|
set(VERSION ${${FPHSA_VERSION_VAR}}) |
||||||
|
|
||||||
|
# check with DEFINED here as the requested or found version may be "0" |
||||||
|
if (DEFINED ${_NAME}_FIND_VERSION) |
||||||
|
if(DEFINED ${FPHSA_VERSION_VAR}) |
||||||
|
|
||||||
|
if(${_NAME}_FIND_VERSION_EXACT) # exact version required |
||||||
|
# count the dots in the version string |
||||||
|
string(REGEX REPLACE "[^.]" "" _VERSION_DOTS "${VERSION}") |
||||||
|
# add one dot because there is one dot more than there are components |
||||||
|
string(LENGTH "${_VERSION_DOTS}." _VERSION_DOTS) |
||||||
|
if (_VERSION_DOTS GREATER ${_NAME}_FIND_VERSION_COUNT) |
||||||
|
# Because of the C++ implementation of find_package() ${_NAME}_FIND_VERSION_COUNT |
||||||
|
# is at most 4 here. Therefore a simple lookup table is used. |
||||||
|
if (${_NAME}_FIND_VERSION_COUNT EQUAL 1) |
||||||
|
set(_VERSION_REGEX "[^.]*") |
||||||
|
elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 2) |
||||||
|
set(_VERSION_REGEX "[^.]*\\.[^.]*") |
||||||
|
elseif (${_NAME}_FIND_VERSION_COUNT EQUAL 3) |
||||||
|
set(_VERSION_REGEX "[^.]*\\.[^.]*\\.[^.]*") |
||||||
|
else () |
||||||
|
set(_VERSION_REGEX "[^.]*\\.[^.]*\\.[^.]*\\.[^.]*") |
||||||
|
endif () |
||||||
|
string(REGEX REPLACE "^(${_VERSION_REGEX})\\..*" "\\1" _VERSION_HEAD "${VERSION}") |
||||||
|
unset(_VERSION_REGEX) |
||||||
|
if (NOT ${_NAME}_FIND_VERSION VERSION_EQUAL _VERSION_HEAD) |
||||||
|
set(VERSION_MSG "Found unsuitable version \"${VERSION}\", but required is exact version \"${${_NAME}_FIND_VERSION}\"") |
||||||
|
set(VERSION_OK FALSE) |
||||||
|
else () |
||||||
|
set(VERSION_MSG "(found suitable exact version \"${VERSION}\")") |
||||||
|
endif () |
||||||
|
unset(_VERSION_HEAD) |
||||||
|
else () |
||||||
|
if (NOT "${${_NAME}_FIND_VERSION}" VERSION_EQUAL "${VERSION}") |
||||||
|
set(VERSION_MSG "Found unsuitable version \"${VERSION}\", but required is exact version \"${${_NAME}_FIND_VERSION}\"") |
||||||
|
set(VERSION_OK FALSE) |
||||||
|
else () |
||||||
|
set(VERSION_MSG "(found suitable exact version \"${VERSION}\")") |
||||||
|
endif () |
||||||
|
endif () |
||||||
|
unset(_VERSION_DOTS) |
||||||
|
|
||||||
|
else() # minimum version specified: |
||||||
|
if ("${${_NAME}_FIND_VERSION}" VERSION_GREATER "${VERSION}") |
||||||
|
set(VERSION_MSG "Found unsuitable version \"${VERSION}\", but required is at least \"${${_NAME}_FIND_VERSION}\"") |
||||||
|
set(VERSION_OK FALSE) |
||||||
|
else () |
||||||
|
set(VERSION_MSG "(found suitable version \"${VERSION}\", minimum required is \"${${_NAME}_FIND_VERSION}\")") |
||||||
|
endif () |
||||||
|
endif() |
||||||
|
|
||||||
|
else() |
||||||
|
|
||||||
|
# if the package was not found, but a version was given, add that to the output: |
||||||
|
if(${_NAME}_FIND_VERSION_EXACT) |
||||||
|
set(VERSION_MSG "(Required is exact version \"${${_NAME}_FIND_VERSION}\")") |
||||||
|
else() |
||||||
|
set(VERSION_MSG "(Required is at least version \"${${_NAME}_FIND_VERSION}\")") |
||||||
|
endif() |
||||||
|
|
||||||
|
endif() |
||||||
|
else () |
||||||
|
if(VERSION) |
||||||
|
set(VERSION_MSG "(found version \"${VERSION}\")") |
||||||
|
endif() |
||||||
|
endif () |
||||||
|
|
||||||
|
if(VERSION_OK) |
||||||
|
set(DETAILS "${DETAILS}[v${VERSION}(${${_NAME}_FIND_VERSION})]") |
||||||
|
else() |
||||||
|
set(${_FOUND_VAR} FALSE) |
||||||
|
endif() |
||||||
|
|
||||||
|
|
||||||
|
# print the result: |
||||||
|
if (${_FOUND_VAR}) |
||||||
|
FIND_PACKAGE_MESSAGE(${_NAME} "Found ${_NAME}: ${${_FIRST_REQUIRED_VAR}} ${VERSION_MSG} ${COMPONENT_MSG}" "${DETAILS}") |
||||||
|
else () |
||||||
|
|
||||||
|
if(FPHSA_CONFIG_MODE) |
||||||
|
_FPHSA_HANDLE_FAILURE_CONFIG_MODE() |
||||||
|
else() |
||||||
|
if(NOT VERSION_OK) |
||||||
|
_FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE}: ${VERSION_MSG} (found ${${_FIRST_REQUIRED_VAR}})") |
||||||
|
else() |
||||||
|
_FPHSA_FAILURE_MESSAGE("${FPHSA_FAIL_MESSAGE} (missing: ${MISSING_VARS}) ${VERSION_MSG}") |
||||||
|
endif() |
||||||
|
endif() |
||||||
|
|
||||||
|
endif () |
||||||
|
|
||||||
|
set(${_FOUND_VAR} ${${_FOUND_VAR}} PARENT_SCOPE) |
||||||
|
|
||||||
|
endfunction() |
57
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/FindPackageMessage.cmake
generated
vendored
57
Godeps/_workspace/src/github.com/ethereum/ethash/cmake/modules/FindPackageMessage.cmake
generated
vendored
@ -0,0 +1,57 @@ |
|||||||
|
#.rst: |
||||||
|
# FindPackageMessage |
||||||
|
# ------------------ |
||||||
|
# |
||||||
|
# |
||||||
|
# |
||||||
|
# FIND_PACKAGE_MESSAGE(<name> "message for user" "find result details") |
||||||
|
# |
||||||
|
# This macro is intended to be used in FindXXX.cmake modules files. It |
||||||
|
# will print a message once for each unique find result. This is useful |
||||||
|
# for telling the user where a package was found. The first argument |
||||||
|
# specifies the name (XXX) of the package. The second argument |
||||||
|
# specifies the message to display. The third argument lists details |
||||||
|
# about the find result so that if they change the message will be |
||||||
|
# displayed again. The macro also obeys the QUIET argument to the |
||||||
|
# find_package command. |
||||||
|
# |
||||||
|
# Example: |
||||||
|
# |
||||||
|
# :: |
||||||
|
# |
||||||
|
# if(X11_FOUND) |
||||||
|
# FIND_PACKAGE_MESSAGE(X11 "Found X11: ${X11_X11_LIB}" |
||||||
|
# "[${X11_X11_LIB}][${X11_INCLUDE_DIR}]") |
||||||
|
# else() |
||||||
|
# ... |
||||||
|
# endif() |
||||||
|
|
||||||
|
#============================================================================= |
||||||
|
# Copyright 2008-2009 Kitware, Inc. |
||||||
|
# |
||||||
|
# Distributed under the OSI-approved BSD License (the "License"); |
||||||
|
# see accompanying file Copyright.txt for details. |
||||||
|
# |
||||||
|
# This software is distributed WITHOUT ANY WARRANTY; without even the |
||||||
|
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
||||||
|
# See the License for more information. |
||||||
|
#============================================================================= |
||||||
|
# (To distribute this file outside of CMake, substitute the full |
||||||
|
# License text for the above reference.) |
||||||
|
|
||||||
|
function(FIND_PACKAGE_MESSAGE pkg msg details) |
||||||
|
# Avoid printing a message repeatedly for the same find result. |
||||||
|
if(NOT ${pkg}_FIND_QUIETLY) |
||||||
|
string(REPLACE "\n" "" details "${details}") |
||||||
|
set(DETAILS_VAR FIND_PACKAGE_MESSAGE_DETAILS_${pkg}) |
||||||
|
if(NOT "${details}" STREQUAL "${${DETAILS_VAR}}") |
||||||
|
# The message has not yet been printed. |
||||||
|
message(STATUS "${msg}") |
||||||
|
|
||||||
|
# Save the find details in the cache to avoid printing the same |
||||||
|
# message again. |
||||||
|
set("${DETAILS_VAR}" "${details}" |
||||||
|
CACHE INTERNAL "Details about finding ${pkg}") |
||||||
|
endif() |
||||||
|
endif() |
||||||
|
endfunction() |
@ -0,0 +1,13 @@ |
|||||||
|
set(LIBRARY cryptopp) |
||||||
|
|
||||||
|
include_directories(../../cryptopp) |
||||||
|
|
||||||
|
# todo, subset |
||||||
|
file(GLOB HEADERS "../../cryptopp/*.h") |
||||||
|
file(GLOB SOURCE "../../cryptopp/*.cpp") |
||||||
|
|
||||||
|
add_library(${LIBRARY} ${HEADERS} ${SOURCE}) |
||||||
|
|
||||||
|
set(CRYPTOPP_INCLUDE_DIRS "../.." PARENT_SCOPE) |
||||||
|
set(CRYPTOPP_LIBRARIES ${LIBRARY} PARENT_SCOPE) |
||||||
|
set(CRYPTOPP_FOUND TRUE PARENT_SCOPE) |
@ -0,0 +1,351 @@ |
|||||||
|
package ethash |
||||||
|
|
||||||
|
/* |
||||||
|
#cgo CFLAGS: -std=gnu99 -Wall |
||||||
|
#include "src/libethash/util.c" |
||||||
|
#include "src/libethash/internal.c" |
||||||
|
#include "src/libethash/sha3.c" |
||||||
|
*/ |
||||||
|
import "C" |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding/binary" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"math/big" |
||||||
|
"math/rand" |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
"unsafe" |
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/ethutil" |
||||||
|
"github.com/ethereum/go-ethereum/logger" |
||||||
|
"github.com/ethereum/go-ethereum/pow" |
||||||
|
) |
||||||
|
|
||||||
|
var tt256 = new(big.Int).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)) |
||||||
|
|
||||||
|
var powlogger = logger.NewLogger("POW") |
||||||
|
|
||||||
|
type DAG struct { |
||||||
|
SeedBlockNum uint64 |
||||||
|
dag unsafe.Pointer // full GB of memory for dag
|
||||||
|
file bool |
||||||
|
} |
||||||
|
|
||||||
|
type ParamsAndCache struct { |
||||||
|
params *C.ethash_params |
||||||
|
cache *C.ethash_cache |
||||||
|
SeedBlockNum uint64 |
||||||
|
} |
||||||
|
|
||||||
|
type Ethash struct { |
||||||
|
turbo bool |
||||||
|
HashRate int64 |
||||||
|
chainManager pow.ChainManager |
||||||
|
dag *DAG |
||||||
|
paramsAndCache *ParamsAndCache |
||||||
|
nextdag unsafe.Pointer |
||||||
|
ret *C.ethash_return_value |
||||||
|
dagMutex *sync.Mutex |
||||||
|
cacheMutex *sync.Mutex |
||||||
|
} |
||||||
|
|
||||||
|
func parseNonce(nonce []byte) (uint64, error) { |
||||||
|
nonceBuf := bytes.NewBuffer(nonce) |
||||||
|
nonceInt, err := binary.ReadUvarint(nonceBuf) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
return nonceInt, nil |
||||||
|
} |
||||||
|
|
||||||
|
const epochLength uint64 = 30000 |
||||||
|
|
||||||
|
func GetSeedBlockNum(blockNum uint64) uint64 { |
||||||
|
var seedBlockNum uint64 = 0 |
||||||
|
if blockNum > epochLength { |
||||||
|
seedBlockNum = ((blockNum - 1) / epochLength) * epochLength |
||||||
|
} |
||||||
|
return seedBlockNum |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
XXX THIS DOESN'T WORK!! NEEDS FIXING |
||||||
|
blockEpoch will underflow and wrap around causing massive issues |
||||||
|
func GetSeedBlockNum(blockNum uint64) uint64 { |
||||||
|
var seedBlockNum uint64 = 0 |
||||||
|
if blockNum > epochLength { |
||||||
|
seedBlockNum = ((blockNum - 1) / epochLength) * epochLength |
||||||
|
} |
||||||
|
return seedBlockNum |
||||||
|
} |
||||||
|
*/ |
||||||
|
|
||||||
|
func makeParamsAndCache(chainManager pow.ChainManager, blockNum uint64) *ParamsAndCache { |
||||||
|
seedBlockNum := GetSeedBlockNum(blockNum) |
||||||
|
paramsAndCache := &ParamsAndCache{ |
||||||
|
params: new(C.ethash_params), |
||||||
|
cache: new(C.ethash_cache), |
||||||
|
SeedBlockNum: seedBlockNum, |
||||||
|
} |
||||||
|
C.ethash_params_init(paramsAndCache.params, C.uint32_t(seedBlockNum)) |
||||||
|
paramsAndCache.cache.mem = C.malloc(paramsAndCache.params.cache_size) |
||||||
|
seedHash := chainManager.GetBlockByNumber(seedBlockNum).SeedHash() |
||||||
|
|
||||||
|
log.Println("Making Cache") |
||||||
|
start := time.Now() |
||||||
|
C.ethash_mkcache(paramsAndCache.cache, paramsAndCache.params, (*C.uint8_t)(unsafe.Pointer(&seedHash[0]))) |
||||||
|
log.Println("Took:", time.Since(start)) |
||||||
|
|
||||||
|
return paramsAndCache |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) updateCache() { |
||||||
|
pow.cacheMutex.Lock() |
||||||
|
seedNum := GetSeedBlockNum(pow.chainManager.CurrentBlock().NumberU64()) |
||||||
|
if pow.paramsAndCache.SeedBlockNum != seedNum { |
||||||
|
pow.paramsAndCache = makeParamsAndCache(pow.chainManager, pow.chainManager.CurrentBlock().NumberU64()) |
||||||
|
} |
||||||
|
pow.cacheMutex.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
func makeDAG(p *ParamsAndCache) *DAG { |
||||||
|
d := &DAG{ |
||||||
|
dag: C.malloc(p.params.full_size), |
||||||
|
SeedBlockNum: p.SeedBlockNum, |
||||||
|
} |
||||||
|
C.ethash_compute_full_data(d.dag, p.params, p.cache) |
||||||
|
return d |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) writeDagToDisk(dag *DAG, seedNum uint64) *os.File { |
||||||
|
data := C.GoBytes(unsafe.Pointer(dag.dag), C.int(pow.paramsAndCache.params.full_size)) |
||||||
|
file, err := os.Create("/tmp/dag") |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
num := make([]byte, 8) |
||||||
|
binary.BigEndian.PutUint64(num, seedNum) |
||||||
|
|
||||||
|
file.Write(num) |
||||||
|
file.Write(data) |
||||||
|
|
||||||
|
return file |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) UpdateDAG() { |
||||||
|
pow.cacheMutex.Lock() |
||||||
|
pow.dagMutex.Lock() |
||||||
|
|
||||||
|
seedNum := GetSeedBlockNum(pow.chainManager.CurrentBlock().NumberU64()) |
||||||
|
if pow.dag == nil || pow.dag.SeedBlockNum != seedNum { |
||||||
|
if pow.dag != nil && pow.dag.dag != nil { |
||||||
|
C.free(pow.dag.dag) |
||||||
|
pow.dag.dag = nil |
||||||
|
} |
||||||
|
|
||||||
|
path := path.Join("/", "tmp", "dag") |
||||||
|
pow.dag = nil |
||||||
|
log.Println("Generating dag") |
||||||
|
start := time.Now() |
||||||
|
|
||||||
|
file, err := os.Open(path) |
||||||
|
if err != nil { |
||||||
|
log.Printf("No dag found in '%s'. Generating new dago(takes a while)...", path) |
||||||
|
pow.dag = makeDAG(pow.paramsAndCache) |
||||||
|
file = pow.writeDagToDisk(pow.dag, seedNum) |
||||||
|
} else { |
||||||
|
data, err := ioutil.ReadAll(file) |
||||||
|
if err != nil { |
||||||
|
panic(err) |
||||||
|
} |
||||||
|
|
||||||
|
num := binary.BigEndian.Uint64(data[0:8]) |
||||||
|
if num < seedNum { |
||||||
|
log.Printf("Old found. Generating new dag (takes a while)...") |
||||||
|
pow.dag = makeDAG(pow.paramsAndCache) |
||||||
|
file = pow.writeDagToDisk(pow.dag, seedNum) |
||||||
|
} else { |
||||||
|
data = data[8:] |
||||||
|
pow.dag = &DAG{ |
||||||
|
dag: unsafe.Pointer(&data[0]), |
||||||
|
file: true, |
||||||
|
SeedBlockNum: pow.paramsAndCache.SeedBlockNum, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
log.Println("Took:", time.Since(start)) |
||||||
|
|
||||||
|
file.Close() |
||||||
|
} |
||||||
|
|
||||||
|
pow.dagMutex.Unlock() |
||||||
|
pow.cacheMutex.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
func New(chainManager pow.ChainManager) *Ethash { |
||||||
|
return &Ethash{ |
||||||
|
turbo: true, |
||||||
|
paramsAndCache: makeParamsAndCache(chainManager, chainManager.CurrentBlock().NumberU64()), |
||||||
|
chainManager: chainManager, |
||||||
|
dag: nil, |
||||||
|
cacheMutex: new(sync.Mutex), |
||||||
|
dagMutex: new(sync.Mutex), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) DAGSize() uint64 { |
||||||
|
return uint64(pow.paramsAndCache.params.full_size) |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) CacheSize() uint64 { |
||||||
|
return uint64(pow.paramsAndCache.params.cache_size) |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) GetSeedHash(blockNum uint64) []byte { |
||||||
|
seednum := GetSeedBlockNum(blockNum) |
||||||
|
return pow.chainManager.GetBlockByNumber(seednum).SeedHash() |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) Stop() { |
||||||
|
pow.cacheMutex.Lock() |
||||||
|
pow.dagMutex.Lock() |
||||||
|
defer pow.dagMutex.Unlock() |
||||||
|
defer pow.cacheMutex.Unlock() |
||||||
|
|
||||||
|
if pow.paramsAndCache.cache != nil { |
||||||
|
C.free(pow.paramsAndCache.cache.mem) |
||||||
|
} |
||||||
|
if pow.dag.dag != nil && !pow.dag.file { |
||||||
|
C.free(pow.dag.dag) |
||||||
|
} |
||||||
|
pow.dag.dag = nil |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) Search(block pow.Block, stop <-chan struct{}) (uint64, []byte, []byte) { |
||||||
|
//pow.UpdateDAG()
|
||||||
|
|
||||||
|
// Not very elegant, multiple mining instances are not supported
|
||||||
|
//pow.dagMutex.Lock()
|
||||||
|
//pow.cacheMutex.Lock()
|
||||||
|
//defer pow.cacheMutex.Unlock()
|
||||||
|
//defer pow.dagMutex.Unlock()
|
||||||
|
|
||||||
|
r := rand.New(rand.NewSource(time.Now().UnixNano())) |
||||||
|
miningHash := block.HashNoNonce() |
||||||
|
diff := block.Difficulty() |
||||||
|
|
||||||
|
i := int64(0) |
||||||
|
starti := i |
||||||
|
start := time.Now().UnixNano() |
||||||
|
|
||||||
|
nonce := uint64(r.Int63()) |
||||||
|
cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash[0])) |
||||||
|
target := new(big.Int).Div(tt256, diff) |
||||||
|
|
||||||
|
var ret C.ethash_return_value |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-stop: |
||||||
|
powlogger.Infoln("Breaking from mining") |
||||||
|
pow.HashRate = 0 |
||||||
|
return 0, nil, nil |
||||||
|
default: |
||||||
|
i++ |
||||||
|
|
||||||
|
elapsed := time.Now().UnixNano() - start |
||||||
|
hashes := ((float64(1e9) / float64(elapsed)) * float64(i-starti)) / 1000 |
||||||
|
pow.HashRate = int64(hashes) |
||||||
|
|
||||||
|
C.ethash_full(&ret, pow.dag.dag, pow.paramsAndCache.params, cMiningHash, C.uint64_t(nonce)) |
||||||
|
result := ethutil.Bytes2Big(C.GoBytes(unsafe.Pointer(&ret.result[0]), C.int(32))) |
||||||
|
|
||||||
|
if result.Cmp(target) <= 0 { |
||||||
|
mixDigest := C.GoBytes(unsafe.Pointer(&ret.mix_hash[0]), C.int(32)) |
||||||
|
|
||||||
|
return nonce, mixDigest, pow.GetSeedHash(block.NumberU64()) |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
nonce += 1 |
||||||
|
} |
||||||
|
|
||||||
|
if !pow.turbo { |
||||||
|
time.Sleep(20 * time.Microsecond) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) Verify(block pow.Block) bool { |
||||||
|
// Make sure the SeedHash is set correctly
|
||||||
|
if bytes.Compare(block.SeedHash(), pow.GetSeedHash(block.NumberU64())) != 0 { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return pow.verify(block.HashNoNonce(), block.MixDigest(), block.Difficulty(), block.NumberU64(), block.Nonce()) |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) verify(hash []byte, mixDigest []byte, difficulty *big.Int, blockNum uint64, nonce uint64) bool { |
||||||
|
// First check: make sure header, mixDigest, nonce are correct without hitting the DAG
|
||||||
|
// This is to prevent DOS attacks
|
||||||
|
chash := (*C.uint8_t)(unsafe.Pointer(&hash[0])) |
||||||
|
cnonce := C.uint64_t(nonce) |
||||||
|
target := new(big.Int).Div(tt256, difficulty) |
||||||
|
|
||||||
|
var pAc *ParamsAndCache |
||||||
|
// If its an old block (doesn't use the current cache)
|
||||||
|
// get the cache for it but don't update (so we don't need the mutex)
|
||||||
|
// Otherwise, it's the current block or a future.
|
||||||
|
// If current, updateCache will do nothing.
|
||||||
|
if GetSeedBlockNum(blockNum) < pow.paramsAndCache.SeedBlockNum { |
||||||
|
pAc = makeParamsAndCache(pow.chainManager, blockNum) |
||||||
|
} else { |
||||||
|
pow.updateCache() |
||||||
|
pow.cacheMutex.Lock() |
||||||
|
defer pow.cacheMutex.Unlock() |
||||||
|
pAc = pow.paramsAndCache |
||||||
|
} |
||||||
|
|
||||||
|
ret := new(C.ethash_return_value) |
||||||
|
|
||||||
|
C.ethash_light(ret, pAc.cache, pAc.params, chash, cnonce) |
||||||
|
|
||||||
|
result := ethutil.Bytes2Big(C.GoBytes(unsafe.Pointer(&ret.result[0]), C.int(32))) |
||||||
|
return result.Cmp(target) <= 0 |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) GetHashrate() int64 { |
||||||
|
return pow.HashRate |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) Turbo(on bool) { |
||||||
|
pow.turbo = on |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) FullHash(nonce uint64, miningHash []byte) []byte { |
||||||
|
pow.UpdateDAG() |
||||||
|
pow.dagMutex.Lock() |
||||||
|
defer pow.dagMutex.Unlock() |
||||||
|
cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash[0])) |
||||||
|
cnonce := C.uint64_t(nonce) |
||||||
|
ret := new(C.ethash_return_value) |
||||||
|
// pow.hash is the output/return of ethash_full
|
||||||
|
C.ethash_full(ret, pow.dag.dag, pow.paramsAndCache.params, cMiningHash, cnonce) |
||||||
|
ghash_full := C.GoBytes(unsafe.Pointer(&ret.result), 32) |
||||||
|
return ghash_full |
||||||
|
} |
||||||
|
|
||||||
|
func (pow *Ethash) LightHash(nonce uint64, miningHash []byte) []byte { |
||||||
|
cMiningHash := (*C.uint8_t)(unsafe.Pointer(&miningHash[0])) |
||||||
|
cnonce := C.uint64_t(nonce) |
||||||
|
ret := new(C.ethash_return_value) |
||||||
|
C.ethash_light(ret, pow.paramsAndCache.cache, pow.paramsAndCache.params, cMiningHash, cnonce) |
||||||
|
ghash_light := C.GoBytes(unsafe.Pointer(&ret.result), 32) |
||||||
|
return ghash_light |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
The MIT License (MIT) |
||||||
|
|
||||||
|
Copyright (c) 2015 Tim Hughes |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||||
|
of this software and associated documentation files (the "Software"), to deal |
||||||
|
in the Software without restriction, including without limitation the rights |
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||||
|
copies of the Software, and to permit persons to whom the Software is |
||||||
|
furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all |
||||||
|
copies or substantial portions of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||||
|
SOFTWARE. |
||||||
|
|
@ -0,0 +1,190 @@ |
|||||||
|
// ethash.js
|
||||||
|
// Tim Hughes <tim@twistedfury.com>
|
||||||
|
// Revision 19
|
||||||
|
|
||||||
|
/*jslint node: true, shadow:true */ |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
var Keccak = require('./keccak'); |
||||||
|
var util = require('./util'); |
||||||
|
|
||||||
|
// 32-bit unsigned modulo
|
||||||
|
function mod32(x, n) |
||||||
|
{ |
||||||
|
return (x>>>0) % (n>>>0); |
||||||
|
} |
||||||
|
|
||||||
|
function fnv(x, y) |
||||||
|
{ |
||||||
|
// js integer multiply by 0x01000193 will lose precision
|
||||||
|
return ((x*0x01000000 | 0) + (x*0x193 | 0)) ^ y;
|
||||||
|
} |
||||||
|
|
||||||
|
function computeCache(params, seedWords) |
||||||
|
{ |
||||||
|
var cache = new Uint32Array(params.cacheSize >> 2); |
||||||
|
var cacheNodeCount = params.cacheSize >> 6; |
||||||
|
|
||||||
|
// Initialize cache
|
||||||
|
var keccak = new Keccak(); |
||||||
|
keccak.digestWords(cache, 0, 16, seedWords, 0, seedWords.length); |
||||||
|
for (var n = 1; n < cacheNodeCount; ++n) |
||||||
|
{ |
||||||
|
keccak.digestWords(cache, n<<4, 16, cache, (n-1)<<4, 16); |
||||||
|
} |
||||||
|
|
||||||
|
var tmp = new Uint32Array(16); |
||||||
|
|
||||||
|
// Do randmemohash passes
|
||||||
|
for (var r = 0; r < params.cacheRounds; ++r) |
||||||
|
{ |
||||||
|
for (var n = 0; n < cacheNodeCount; ++n) |
||||||
|
{ |
||||||
|
var p0 = mod32(n + cacheNodeCount - 1, cacheNodeCount) << 4; |
||||||
|
var p1 = mod32(cache[n<<4|0], cacheNodeCount) << 4; |
||||||
|
|
||||||
|
for (var w = 0; w < 16; w=(w+1)|0) |
||||||
|
{ |
||||||
|
tmp[w] = cache[p0 | w] ^ cache[p1 | w]; |
||||||
|
} |
||||||
|
|
||||||
|
keccak.digestWords(cache, n<<4, 16, tmp, 0, tmp.length); |
||||||
|
} |
||||||
|
}
|
||||||
|
return cache; |
||||||
|
} |
||||||
|
|
||||||
|
function computeDagNode(o_node, params, cache, keccak, nodeIndex) |
||||||
|
{ |
||||||
|
var cacheNodeCount = params.cacheSize >> 6; |
||||||
|
var dagParents = params.dagParents; |
||||||
|
|
||||||
|
var c = (nodeIndex % cacheNodeCount) << 4; |
||||||
|
var mix = o_node; |
||||||
|
for (var w = 0; w < 16; ++w) |
||||||
|
{ |
||||||
|
mix[w] = cache[c|w]; |
||||||
|
} |
||||||
|
mix[0] ^= nodeIndex; |
||||||
|
keccak.digestWords(mix, 0, 16, mix, 0, 16); |
||||||
|
|
||||||
|
for (var p = 0; p < dagParents; ++p) |
||||||
|
{ |
||||||
|
// compute cache node (word) index
|
||||||
|
c = mod32(fnv(nodeIndex ^ p, mix[p&15]), cacheNodeCount) << 4; |
||||||
|
|
||||||
|
for (var w = 0; w < 16; ++w) |
||||||
|
{ |
||||||
|
mix[w] = fnv(mix[w], cache[c|w]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
keccak.digestWords(mix, 0, 16, mix, 0, 16); |
||||||
|
} |
||||||
|
|
||||||
|
function computeHashInner(mix, params, cache, keccak, tempNode) |
||||||
|
{ |
||||||
|
var mixParents = params.mixParents|0; |
||||||
|
var mixWordCount = params.mixSize >> 2; |
||||||
|
var mixNodeCount = mixWordCount >> 4; |
||||||
|
var dagPageCount = (params.dagSize / params.mixSize) >> 0; |
||||||
|
|
||||||
|
// grab initial first word
|
||||||
|
var s0 = mix[0]; |
||||||
|
|
||||||
|
// initialise mix from initial 64 bytes
|
||||||
|
for (var w = 16; w < mixWordCount; ++w) |
||||||
|
{ |
||||||
|
mix[w] = mix[w & 15]; |
||||||
|
} |
||||||
|
|
||||||
|
for (var a = 0; a < mixParents; ++a) |
||||||
|
{ |
||||||
|
var p = mod32(fnv(s0 ^ a, mix[a & (mixWordCount-1)]), dagPageCount); |
||||||
|
var d = (p * mixNodeCount)|0; |
||||||
|
|
||||||
|
for (var n = 0, w = 0; n < mixNodeCount; ++n, w += 16) |
||||||
|
{ |
||||||
|
computeDagNode(tempNode, params, cache, keccak, (d + n)|0); |
||||||
|
|
||||||
|
for (var v = 0; v < 16; ++v) |
||||||
|
{ |
||||||
|
mix[w|v] = fnv(mix[w|v], tempNode[v]); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function convertSeed(seed) |
||||||
|
{ |
||||||
|
// todo, reconcile with spec, byte ordering?
|
||||||
|
// todo, big-endian conversion
|
||||||
|
var newSeed = util.toWords(seed); |
||||||
|
if (newSeed === null) |
||||||
|
throw Error("Invalid seed '" + seed + "'"); |
||||||
|
return newSeed; |
||||||
|
} |
||||||
|
|
||||||
|
exports.defaultParams = function() |
||||||
|
{ |
||||||
|
return { |
||||||
|
cacheSize: 1048384, |
||||||
|
cacheRounds: 3, |
||||||
|
dagSize: 1073739904, |
||||||
|
dagParents: 256, |
||||||
|
mixSize: 128, |
||||||
|
mixParents: 64, |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
exports.Ethash = function(params, seed) |
||||||
|
{ |
||||||
|
// precompute cache and related values
|
||||||
|
seed = convertSeed(seed); |
||||||
|
var cache = computeCache(params, seed); |
||||||
|
|
||||||
|
// preallocate buffers/etc
|
||||||
|
var initBuf = new ArrayBuffer(96); |
||||||
|
var initBytes = new Uint8Array(initBuf); |
||||||
|
var initWords = new Uint32Array(initBuf); |
||||||
|
var mixWords = new Uint32Array(params.mixSize / 4); |
||||||
|
var tempNode = new Uint32Array(16); |
||||||
|
var keccak = new Keccak(); |
||||||
|
|
||||||
|
var retWords = new Uint32Array(8); |
||||||
|
var retBytes = new Uint8Array(retWords.buffer); // supposedly read-only
|
||||||
|
|
||||||
|
this.hash = function(header, nonce) |
||||||
|
{ |
||||||
|
// compute initial hash
|
||||||
|
initBytes.set(header, 0); |
||||||
|
initBytes.set(nonce, 32); |
||||||
|
keccak.digestWords(initWords, 0, 16, initWords, 0, 8 + nonce.length/4); |
||||||
|
|
||||||
|
// compute mix
|
||||||
|
for (var i = 0; i != 16; ++i) |
||||||
|
{ |
||||||
|
mixWords[i] = initWords[i]; |
||||||
|
} |
||||||
|
computeHashInner(mixWords, params, cache, keccak, tempNode); |
||||||
|
|
||||||
|
// compress mix and append to initWords
|
||||||
|
for (var i = 0; i != mixWords.length; i += 4) |
||||||
|
{ |
||||||
|
initWords[16 + i/4] = fnv(fnv(fnv(mixWords[i], mixWords[i+1]), mixWords[i+2]), mixWords[i+3]); |
||||||
|
} |
||||||
|
|
||||||
|
// final Keccak hashes
|
||||||
|
keccak.digestWords(retWords, 0, 8, initWords, 0, 24); // Keccak-256(s + cmix)
|
||||||
|
return retBytes; |
||||||
|
}; |
||||||
|
|
||||||
|
this.cacheDigest = function() |
||||||
|
{ |
||||||
|
return keccak.digest(32, new Uint8Array(cache.buffer)); |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,404 @@ |
|||||||
|
// keccak.js
|
||||||
|
// Tim Hughes <tim@twistedfury.com>
|
||||||
|
// derived from Markku-Juhani O. Saarinen's C code (http://keccak.noekeon.org/readable_code.html)
|
||||||
|
|
||||||
|
/*jslint node: true, shadow:true */ |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
var Keccak_f1600_RC = new Uint32Array([ |
||||||
|
0x00000001, 0x00000000, |
||||||
|
0x00008082, 0x00000000, |
||||||
|
0x0000808a, 0x80000000, |
||||||
|
0x80008000, 0x80000000, |
||||||
|
0x0000808b, 0x00000000, |
||||||
|
0x80000001, 0x00000000, |
||||||
|
0x80008081, 0x80000000, |
||||||
|
0x00008009, 0x80000000, |
||||||
|
0x0000008a, 0x00000000, |
||||||
|
0x00000088, 0x00000000, |
||||||
|
0x80008009, 0x00000000, |
||||||
|
0x8000000a, 0x00000000, |
||||||
|
0x8000808b, 0x00000000, |
||||||
|
0x0000008b, 0x80000000, |
||||||
|
0x00008089, 0x80000000, |
||||||
|
0x00008003, 0x80000000, |
||||||
|
0x00008002, 0x80000000, |
||||||
|
0x00000080, 0x80000000, |
||||||
|
0x0000800a, 0x00000000, |
||||||
|
0x8000000a, 0x80000000, |
||||||
|
0x80008081, 0x80000000, |
||||||
|
0x00008080, 0x80000000, |
||||||
|
0x80000001, 0x00000000, |
||||||
|
0x80008008, 0x80000000 |
||||||
|
]); |
||||||
|
|
||||||
|
function keccak_f1600(outState, outOffset, outSize, inState) |
||||||
|
{ |
||||||
|
// todo, handle big endian loads
|
||||||
|
var a00l = inState[0]|0; |
||||||
|
var a00h = inState[1]|0; |
||||||
|
var a01l = inState[2]|0; |
||||||
|
var a01h = inState[3]|0; |
||||||
|
var a02l = inState[4]|0; |
||||||
|
var a02h = inState[5]|0; |
||||||
|
var a03l = inState[6]|0; |
||||||
|
var a03h = inState[7]|0; |
||||||
|
var a04l = inState[8]|0; |
||||||
|
var a04h = inState[9]|0; |
||||||
|
var a05l = inState[10]|0; |
||||||
|
var a05h = inState[11]|0; |
||||||
|
var a06l = inState[12]|0; |
||||||
|
var a06h = inState[13]|0; |
||||||
|
var a07l = inState[14]|0; |
||||||
|
var a07h = inState[15]|0; |
||||||
|
var a08l = inState[16]|0; |
||||||
|
var a08h = inState[17]|0; |
||||||
|
var a09l = inState[18]|0; |
||||||
|
var a09h = inState[19]|0; |
||||||
|
var a10l = inState[20]|0; |
||||||
|
var a10h = inState[21]|0; |
||||||
|
var a11l = inState[22]|0; |
||||||
|
var a11h = inState[23]|0; |
||||||
|
var a12l = inState[24]|0; |
||||||
|
var a12h = inState[25]|0; |
||||||
|
var a13l = inState[26]|0; |
||||||
|
var a13h = inState[27]|0; |
||||||
|
var a14l = inState[28]|0; |
||||||
|
var a14h = inState[29]|0; |
||||||
|
var a15l = inState[30]|0; |
||||||
|
var a15h = inState[31]|0; |
||||||
|
var a16l = inState[32]|0; |
||||||
|
var a16h = inState[33]|0; |
||||||
|
var a17l = inState[34]|0; |
||||||
|
var a17h = inState[35]|0; |
||||||
|
var a18l = inState[36]|0; |
||||||
|
var a18h = inState[37]|0; |
||||||
|
var a19l = inState[38]|0; |
||||||
|
var a19h = inState[39]|0; |
||||||
|
var a20l = inState[40]|0; |
||||||
|
var a20h = inState[41]|0; |
||||||
|
var a21l = inState[42]|0; |
||||||
|
var a21h = inState[43]|0; |
||||||
|
var a22l = inState[44]|0; |
||||||
|
var a22h = inState[45]|0; |
||||||
|
var a23l = inState[46]|0; |
||||||
|
var a23h = inState[47]|0; |
||||||
|
var a24l = inState[48]|0; |
||||||
|
var a24h = inState[49]|0; |
||||||
|
var b00l, b00h, b01l, b01h, b02l, b02h, b03l, b03h, b04l, b04h; |
||||||
|
var b05l, b05h, b06l, b06h, b07l, b07h, b08l, b08h, b09l, b09h; |
||||||
|
var b10l, b10h, b11l, b11h, b12l, b12h, b13l, b13h, b14l, b14h; |
||||||
|
var b15l, b15h, b16l, b16h, b17l, b17h, b18l, b18h, b19l, b19h; |
||||||
|
var b20l, b20h, b21l, b21h, b22l, b22h, b23l, b23h, b24l, b24h; |
||||||
|
var tl, nl; |
||||||
|
var th, nh; |
||||||
|
|
||||||
|
for (var r = 0; r < 48; r = (r+2)|0) |
||||||
|
{ |
||||||
|
// Theta
|
||||||
|
b00l = a00l ^ a05l ^ a10l ^ a15l ^ a20l; |
||||||
|
b00h = a00h ^ a05h ^ a10h ^ a15h ^ a20h; |
||||||
|
b01l = a01l ^ a06l ^ a11l ^ a16l ^ a21l; |
||||||
|
b01h = a01h ^ a06h ^ a11h ^ a16h ^ a21h; |
||||||
|
b02l = a02l ^ a07l ^ a12l ^ a17l ^ a22l; |
||||||
|
b02h = a02h ^ a07h ^ a12h ^ a17h ^ a22h; |
||||||
|
b03l = a03l ^ a08l ^ a13l ^ a18l ^ a23l; |
||||||
|
b03h = a03h ^ a08h ^ a13h ^ a18h ^ a23h; |
||||||
|
b04l = a04l ^ a09l ^ a14l ^ a19l ^ a24l; |
||||||
|
b04h = a04h ^ a09h ^ a14h ^ a19h ^ a24h; |
||||||
|
tl = b04l ^ (b01l << 1 | b01h >>> 31); |
||||||
|
th = b04h ^ (b01h << 1 | b01l >>> 31); |
||||||
|
a00l ^= tl; |
||||||
|
a00h ^= th; |
||||||
|
a05l ^= tl; |
||||||
|
a05h ^= th; |
||||||
|
a10l ^= tl; |
||||||
|
a10h ^= th; |
||||||
|
a15l ^= tl; |
||||||
|
a15h ^= th; |
||||||
|
a20l ^= tl; |
||||||
|
a20h ^= th; |
||||||
|
tl = b00l ^ (b02l << 1 | b02h >>> 31); |
||||||
|
th = b00h ^ (b02h << 1 | b02l >>> 31); |
||||||
|
a01l ^= tl; |
||||||
|
a01h ^= th; |
||||||
|
a06l ^= tl; |
||||||
|
a06h ^= th; |
||||||
|
a11l ^= tl; |
||||||
|
a11h ^= th; |
||||||
|
a16l ^= tl; |
||||||
|
a16h ^= th; |
||||||
|
a21l ^= tl; |
||||||
|
a21h ^= th; |
||||||
|
tl = b01l ^ (b03l << 1 | b03h >>> 31); |
||||||
|
th = b01h ^ (b03h << 1 | b03l >>> 31); |
||||||
|
a02l ^= tl; |
||||||
|
a02h ^= th; |
||||||
|
a07l ^= tl; |
||||||
|
a07h ^= th; |
||||||
|
a12l ^= tl; |
||||||
|
a12h ^= th; |
||||||
|
a17l ^= tl; |
||||||
|
a17h ^= th; |
||||||
|
a22l ^= tl; |
||||||
|
a22h ^= th; |
||||||
|
tl = b02l ^ (b04l << 1 | b04h >>> 31); |
||||||
|
th = b02h ^ (b04h << 1 | b04l >>> 31); |
||||||
|
a03l ^= tl; |
||||||
|
a03h ^= th; |
||||||
|
a08l ^= tl; |
||||||
|
a08h ^= th; |
||||||
|
a13l ^= tl; |
||||||
|
a13h ^= th; |
||||||
|
a18l ^= tl; |
||||||
|
a18h ^= th; |
||||||
|
a23l ^= tl; |
||||||
|
a23h ^= th; |
||||||
|
tl = b03l ^ (b00l << 1 | b00h >>> 31); |
||||||
|
th = b03h ^ (b00h << 1 | b00l >>> 31); |
||||||
|
a04l ^= tl; |
||||||
|
a04h ^= th; |
||||||
|
a09l ^= tl; |
||||||
|
a09h ^= th; |
||||||
|
a14l ^= tl; |
||||||
|
a14h ^= th; |
||||||
|
a19l ^= tl; |
||||||
|
a19h ^= th; |
||||||
|
a24l ^= tl; |
||||||
|
a24h ^= th; |
||||||
|
|
||||||
|
// Rho Pi
|
||||||
|
b00l = a00l; |
||||||
|
b00h = a00h; |
||||||
|
b10l = a01l << 1 | a01h >>> 31; |
||||||
|
b10h = a01h << 1 | a01l >>> 31; |
||||||
|
b07l = a10l << 3 | a10h >>> 29; |
||||||
|
b07h = a10h << 3 | a10l >>> 29; |
||||||
|
b11l = a07l << 6 | a07h >>> 26; |
||||||
|
b11h = a07h << 6 | a07l >>> 26; |
||||||
|
b17l = a11l << 10 | a11h >>> 22; |
||||||
|
b17h = a11h << 10 | a11l >>> 22; |
||||||
|
b18l = a17l << 15 | a17h >>> 17; |
||||||
|
b18h = a17h << 15 | a17l >>> 17; |
||||||
|
b03l = a18l << 21 | a18h >>> 11; |
||||||
|
b03h = a18h << 21 | a18l >>> 11; |
||||||
|
b05l = a03l << 28 | a03h >>> 4; |
||||||
|
b05h = a03h << 28 | a03l >>> 4; |
||||||
|
b16l = a05h << 4 | a05l >>> 28; |
||||||
|
b16h = a05l << 4 | a05h >>> 28; |
||||||
|
b08l = a16h << 13 | a16l >>> 19; |
||||||
|
b08h = a16l << 13 | a16h >>> 19; |
||||||
|
b21l = a08h << 23 | a08l >>> 9; |
||||||
|
b21h = a08l << 23 | a08h >>> 9; |
||||||
|
b24l = a21l << 2 | a21h >>> 30; |
||||||
|
b24h = a21h << 2 | a21l >>> 30; |
||||||
|
b04l = a24l << 14 | a24h >>> 18; |
||||||
|
b04h = a24h << 14 | a24l >>> 18; |
||||||
|
b15l = a04l << 27 | a04h >>> 5; |
||||||
|
b15h = a04h << 27 | a04l >>> 5; |
||||||
|
b23l = a15h << 9 | a15l >>> 23; |
||||||
|
b23h = a15l << 9 | a15h >>> 23; |
||||||
|
b19l = a23h << 24 | a23l >>> 8; |
||||||
|
b19h = a23l << 24 | a23h >>> 8; |
||||||
|
b13l = a19l << 8 | a19h >>> 24; |
||||||
|
b13h = a19h << 8 | a19l >>> 24; |
||||||
|
b12l = a13l << 25 | a13h >>> 7; |
||||||
|
b12h = a13h << 25 | a13l >>> 7; |
||||||
|
b02l = a12h << 11 | a12l >>> 21; |
||||||
|
b02h = a12l << 11 | a12h >>> 21; |
||||||
|
b20l = a02h << 30 | a02l >>> 2; |
||||||
|
b20h = a02l << 30 | a02h >>> 2; |
||||||
|
b14l = a20l << 18 | a20h >>> 14; |
||||||
|
b14h = a20h << 18 | a20l >>> 14; |
||||||
|
b22l = a14h << 7 | a14l >>> 25; |
||||||
|
b22h = a14l << 7 | a14h >>> 25; |
||||||
|
b09l = a22h << 29 | a22l >>> 3; |
||||||
|
b09h = a22l << 29 | a22h >>> 3; |
||||||
|
b06l = a09l << 20 | a09h >>> 12; |
||||||
|
b06h = a09h << 20 | a09l >>> 12; |
||||||
|
b01l = a06h << 12 | a06l >>> 20; |
||||||
|
b01h = a06l << 12 | a06h >>> 20; |
||||||
|
|
||||||
|
// Chi
|
||||||
|
a00l = b00l ^ ~b01l & b02l; |
||||||
|
a00h = b00h ^ ~b01h & b02h; |
||||||
|
a01l = b01l ^ ~b02l & b03l; |
||||||
|
a01h = b01h ^ ~b02h & b03h; |
||||||
|
a02l = b02l ^ ~b03l & b04l; |
||||||
|
a02h = b02h ^ ~b03h & b04h; |
||||||
|
a03l = b03l ^ ~b04l & b00l; |
||||||
|
a03h = b03h ^ ~b04h & b00h; |
||||||
|
a04l = b04l ^ ~b00l & b01l; |
||||||
|
a04h = b04h ^ ~b00h & b01h; |
||||||
|
a05l = b05l ^ ~b06l & b07l; |
||||||
|
a05h = b05h ^ ~b06h & b07h; |
||||||
|
a06l = b06l ^ ~b07l & b08l; |
||||||
|
a06h = b06h ^ ~b07h & b08h; |
||||||
|
a07l = b07l ^ ~b08l & b09l; |
||||||
|
a07h = b07h ^ ~b08h & b09h; |
||||||
|
a08l = b08l ^ ~b09l & b05l; |
||||||
|
a08h = b08h ^ ~b09h & b05h; |
||||||
|
a09l = b09l ^ ~b05l & b06l; |
||||||
|
a09h = b09h ^ ~b05h & b06h; |
||||||
|
a10l = b10l ^ ~b11l & b12l; |
||||||
|
a10h = b10h ^ ~b11h & b12h; |
||||||
|
a11l = b11l ^ ~b12l & b13l; |
||||||
|
a11h = b11h ^ ~b12h & b13h; |
||||||
|
a12l = b12l ^ ~b13l & b14l; |
||||||
|
a12h = b12h ^ ~b13h & b14h; |
||||||
|
a13l = b13l ^ ~b14l & b10l; |
||||||
|
a13h = b13h ^ ~b14h & b10h; |
||||||
|
a14l = b14l ^ ~b10l & b11l; |
||||||
|
a14h = b14h ^ ~b10h & b11h; |
||||||
|
a15l = b15l ^ ~b16l & b17l; |
||||||
|
a15h = b15h ^ ~b16h & b17h; |
||||||
|
a16l = b16l ^ ~b17l & b18l; |
||||||
|
a16h = b16h ^ ~b17h & b18h; |
||||||
|
a17l = b17l ^ ~b18l & b19l; |
||||||
|
a17h = b17h ^ ~b18h & b19h; |
||||||
|
a18l = b18l ^ ~b19l & b15l; |
||||||
|
a18h = b18h ^ ~b19h & b15h; |
||||||
|
a19l = b19l ^ ~b15l & b16l; |
||||||
|
a19h = b19h ^ ~b15h & b16h; |
||||||
|
a20l = b20l ^ ~b21l & b22l; |
||||||
|
a20h = b20h ^ ~b21h & b22h; |
||||||
|
a21l = b21l ^ ~b22l & b23l; |
||||||
|
a21h = b21h ^ ~b22h & b23h; |
||||||
|
a22l = b22l ^ ~b23l & b24l; |
||||||
|
a22h = b22h ^ ~b23h & b24h; |
||||||
|
a23l = b23l ^ ~b24l & b20l; |
||||||
|
a23h = b23h ^ ~b24h & b20h; |
||||||
|
a24l = b24l ^ ~b20l & b21l; |
||||||
|
a24h = b24h ^ ~b20h & b21h; |
||||||
|
|
||||||
|
// Iota
|
||||||
|
a00l ^= Keccak_f1600_RC[r|0]; |
||||||
|
a00h ^= Keccak_f1600_RC[r|1]; |
||||||
|
} |
||||||
|
|
||||||
|
// todo, handle big-endian stores
|
||||||
|
outState[outOffset|0] = a00l; |
||||||
|
outState[outOffset|1] = a00h; |
||||||
|
outState[outOffset|2] = a01l; |
||||||
|
outState[outOffset|3] = a01h; |
||||||
|
outState[outOffset|4] = a02l; |
||||||
|
outState[outOffset|5] = a02h; |
||||||
|
outState[outOffset|6] = a03l; |
||||||
|
outState[outOffset|7] = a03h; |
||||||
|
if (outSize == 8) |
||||||
|
return; |
||||||
|
outState[outOffset|8] = a04l; |
||||||
|
outState[outOffset|9] = a04h; |
||||||
|
outState[outOffset|10] = a05l; |
||||||
|
outState[outOffset|11] = a05h; |
||||||
|
outState[outOffset|12] = a06l; |
||||||
|
outState[outOffset|13] = a06h; |
||||||
|
outState[outOffset|14] = a07l; |
||||||
|
outState[outOffset|15] = a07h; |
||||||
|
if (outSize == 16) |
||||||
|
return; |
||||||
|
outState[outOffset|16] = a08l; |
||||||
|
outState[outOffset|17] = a08h; |
||||||
|
outState[outOffset|18] = a09l; |
||||||
|
outState[outOffset|19] = a09h; |
||||||
|
outState[outOffset|20] = a10l; |
||||||
|
outState[outOffset|21] = a10h; |
||||||
|
outState[outOffset|22] = a11l; |
||||||
|
outState[outOffset|23] = a11h; |
||||||
|
outState[outOffset|24] = a12l; |
||||||
|
outState[outOffset|25] = a12h; |
||||||
|
outState[outOffset|26] = a13l; |
||||||
|
outState[outOffset|27] = a13h; |
||||||
|
outState[outOffset|28] = a14l; |
||||||
|
outState[outOffset|29] = a14h; |
||||||
|
outState[outOffset|30] = a15l; |
||||||
|
outState[outOffset|31] = a15h; |
||||||
|
outState[outOffset|32] = a16l; |
||||||
|
outState[outOffset|33] = a16h; |
||||||
|
outState[outOffset|34] = a17l; |
||||||
|
outState[outOffset|35] = a17h; |
||||||
|
outState[outOffset|36] = a18l; |
||||||
|
outState[outOffset|37] = a18h; |
||||||
|
outState[outOffset|38] = a19l; |
||||||
|
outState[outOffset|39] = a19h; |
||||||
|
outState[outOffset|40] = a20l; |
||||||
|
outState[outOffset|41] = a20h; |
||||||
|
outState[outOffset|42] = a21l; |
||||||
|
outState[outOffset|43] = a21h; |
||||||
|
outState[outOffset|44] = a22l; |
||||||
|
outState[outOffset|45] = a22h; |
||||||
|
outState[outOffset|46] = a23l; |
||||||
|
outState[outOffset|47] = a23h; |
||||||
|
outState[outOffset|48] = a24l; |
||||||
|
outState[outOffset|49] = a24h; |
||||||
|
} |
||||||
|
|
||||||
|
var Keccak = function() |
||||||
|
{ |
||||||
|
var stateBuf = new ArrayBuffer(200); |
||||||
|
var stateBytes = new Uint8Array(stateBuf); |
||||||
|
var stateWords = new Uint32Array(stateBuf); |
||||||
|
|
||||||
|
this.digest = function(oSize, iBytes) |
||||||
|
{ |
||||||
|
for (var i = 0; i < 50; ++i) |
||||||
|
{ |
||||||
|
stateWords[i] = 0; |
||||||
|
} |
||||||
|
|
||||||
|
var r = 200 - oSize*2; |
||||||
|
var iLength = iBytes.length; |
||||||
|
var iOffset = 0;
|
||||||
|
for ( ; ;) |
||||||
|
{ |
||||||
|
var len = iLength < r ? iLength : r; |
||||||
|
for (i = 0; i < len; ++i, ++iOffset) |
||||||
|
{ |
||||||
|
stateBytes[i] ^= iBytes[iOffset]; |
||||||
|
} |
||||||
|
|
||||||
|
if (iLength < r) |
||||||
|
break; |
||||||
|
iLength -= len; |
||||||
|
|
||||||
|
keccak_f1600(stateWords, 0, 50, stateWords); |
||||||
|
} |
||||||
|
|
||||||
|
stateBytes[iLength] ^= 1; |
||||||
|
stateBytes[r-1] ^= 0x80; |
||||||
|
keccak_f1600(stateWords, 0, 50, stateWords); |
||||||
|
return stateBytes.subarray(0, oSize); |
||||||
|
}; |
||||||
|
|
||||||
|
this.digestWords = function(oWords, oOffset, oLength, iWords, iOffset, iLength) |
||||||
|
{ |
||||||
|
for (var i = 0; i < 50; ++i) |
||||||
|
{ |
||||||
|
stateWords[i] = 0; |
||||||
|
} |
||||||
|
|
||||||
|
var r = 50 - oLength*2; |
||||||
|
for (; ; ) |
||||||
|
{ |
||||||
|
var len = iLength < r ? iLength : r; |
||||||
|
for (i = 0; i < len; ++i, ++iOffset) |
||||||
|
{ |
||||||
|
stateWords[i] ^= iWords[iOffset]; |
||||||
|
} |
||||||
|
|
||||||
|
if (iLength < r) |
||||||
|
break; |
||||||
|
iLength -= len; |
||||||
|
|
||||||
|
keccak_f1600(stateWords, 0, 50, stateWords); |
||||||
|
} |
||||||
|
|
||||||
|
stateBytes[iLength<<2] ^= 1; |
||||||
|
stateBytes[(r<<2) - 1] ^= 0x80; |
||||||
|
keccak_f1600(oWords, oOffset, oLength, stateWords); |
||||||
|
}; |
||||||
|
}; |
||||||
|
|
||||||
|
module.exports = Keccak; |
||||||
|
|
||||||
|
|
@ -0,0 +1,201 @@ |
|||||||
|
#!/usr/bin/env node
|
||||||
|
// makekeccak.js
|
||||||
|
// Tim Hughes <tim@twistedfury.com>
|
||||||
|
|
||||||
|
/*jslint node: true, shadow:true */ |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
var Keccak_f1600_Rho = [ |
||||||
|
1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
|
||||||
|
27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 |
||||||
|
]; |
||||||
|
|
||||||
|
var Keccak_f1600_Pi= [ |
||||||
|
10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
|
||||||
|
15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1
|
||||||
|
]; |
||||||
|
|
||||||
|
var Keccak_f1600_RC = [
|
||||||
|
0x00000001, 0x00000000, |
||||||
|
0x00008082, 0x00000000, |
||||||
|
0x0000808a, 0x80000000, |
||||||
|
0x80008000, 0x80000000, |
||||||
|
0x0000808b, 0x00000000, |
||||||
|
0x80000001, 0x00000000, |
||||||
|
0x80008081, 0x80000000, |
||||||
|
0x00008009, 0x80000000, |
||||||
|
0x0000008a, 0x00000000, |
||||||
|
0x00000088, 0x00000000, |
||||||
|
0x80008009, 0x00000000, |
||||||
|
0x8000000a, 0x00000000, |
||||||
|
0x8000808b, 0x00000000, |
||||||
|
0x0000008b, 0x80000000, |
||||||
|
0x00008089, 0x80000000, |
||||||
|
0x00008003, 0x80000000, |
||||||
|
0x00008002, 0x80000000, |
||||||
|
0x00000080, 0x80000000, |
||||||
|
0x0000800a, 0x00000000, |
||||||
|
0x8000000a, 0x80000000, |
||||||
|
0x80008081, 0x80000000, |
||||||
|
0x00008080, 0x80000000, |
||||||
|
0x80000001, 0x00000000, |
||||||
|
0x80008008, 0x80000000, |
||||||
|
]; |
||||||
|
|
||||||
|
function makeRotLow(lo, hi, n) |
||||||
|
{ |
||||||
|
if (n === 0 || n === 32) throw Error("unsupported"); |
||||||
|
if ((n & 0x20) !== 0) |
||||||
|
{ |
||||||
|
n &= ~0x20; |
||||||
|
var t = hi; |
||||||
|
hi = lo; |
||||||
|
lo = t; |
||||||
|
} |
||||||
|
var hir = hi + " >>> " + (32 - n); |
||||||
|
var los = lo + " << " + n; |
||||||
|
return los + " | " + hir; |
||||||
|
} |
||||||
|
|
||||||
|
function makeRotHigh(lo, hi, n) |
||||||
|
{ |
||||||
|
if (n === 0 || n === 32) throw Error("unsupported"); |
||||||
|
if ((n & 0x20) !== 0) |
||||||
|
{ |
||||||
|
n &= ~0x20; |
||||||
|
var t = hi; |
||||||
|
hi = lo; |
||||||
|
lo = t; |
||||||
|
} |
||||||
|
var his = hi + " << " + n; |
||||||
|
var lor = lo + " >>> " + (32 - n); |
||||||
|
return his + " | " + lor; |
||||||
|
} |
||||||
|
|
||||||
|
function makeKeccak_f1600() |
||||||
|
{ |
||||||
|
var format = function(n) |
||||||
|
{ |
||||||
|
return n < 10 ? "0"+n : ""+n; |
||||||
|
}; |
||||||
|
|
||||||
|
var a = function(n, w) |
||||||
|
{ |
||||||
|
return "a" + format(n) + (w !== 0?'h':'l'); |
||||||
|
}; |
||||||
|
|
||||||
|
var b = function(n, w) |
||||||
|
{ |
||||||
|
return "b" + format(n) + (w !== 0?'h':'l'); |
||||||
|
}; |
||||||
|
|
||||||
|
var str = "";
|
||||||
|
str += "function keccak_f1600(outState, outOffset, outSize, inState)\n"; |
||||||
|
str += "{\n"; |
||||||
|
|
||||||
|
for (var i = 0; i < 25; ++i) |
||||||
|
{ |
||||||
|
for (var w = 0; w <= 1; ++w) |
||||||
|
{ |
||||||
|
str += "\tvar " + a(i,w) + " = inState["+(i<<1|w)+"]|0;\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (var j = 0; j < 5; ++j) |
||||||
|
{ |
||||||
|
str += "\tvar "; |
||||||
|
for (var i = 0; i < 5; ++i) |
||||||
|
{ |
||||||
|
if (i !== 0) |
||||||
|
str += ", "; |
||||||
|
str += b(j*5+i,0) + ", " + b(j*5+i,1); |
||||||
|
} |
||||||
|
str += ";\n"; |
||||||
|
} |
||||||
|
|
||||||
|
str += "\tvar tl, th;\n"; |
||||||
|
str += "\n"; |
||||||
|
str += "\tfor (var r = 0; r < 48; r = (r+2)|0)\n"; |
||||||
|
str += "\t{\n"; |
||||||
|
|
||||||
|
|
||||||
|
// Theta
|
||||||
|
str += "\t\t// Theta\n"; |
||||||
|
for (var i = 0; i < 5; ++i) |
||||||
|
{ |
||||||
|
for (var w = 0; w <= 1; ++w) |
||||||
|
{ |
||||||
|
str += "\t\t" + b(i,w) + " = " + a(i,w) + " ^ " + a(i+5,w) + " ^ " + a(i+10,w) + " ^ " + a(i+15,w) + " ^ " + a(i+20,w) + ";\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
for (var i = 0; i < 5; ++i) |
||||||
|
{ |
||||||
|
var i4 = (i + 4) % 5; |
||||||
|
var i1 = (i + 1) % 5; |
||||||
|
str += "\t\ttl = " + b(i4,0) + " ^ (" + b(i1,0) + " << 1 | " + b(i1,1) + " >>> 31);\n"; |
||||||
|
str += "\t\tth = " + b(i4,1) + " ^ (" + b(i1,1) + " << 1 | " + b(i1,0) + " >>> 31);\n"; |
||||||
|
|
||||||
|
for (var j = 0; j < 25; j = (j+5)|0) |
||||||
|
{ |
||||||
|
str += "\t\t" + a((j+i),0) + " ^= tl;\n"; |
||||||
|
str += "\t\t" + a((j+i),1) + " ^= th;\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// Rho Pi
|
||||||
|
str += "\n\t\t// Rho Pi\n"; |
||||||
|
for (var w = 0; w <= 1; ++w) |
||||||
|
{ |
||||||
|
str += "\t\t" + b(0,w) + " = " + a(0,w) + ";\n"; |
||||||
|
} |
||||||
|
var opi = 1; |
||||||
|
for (var i = 0; i < 24; ++i) |
||||||
|
{ |
||||||
|
var pi = Keccak_f1600_Pi[i]; |
||||||
|
str += "\t\t" + b(pi,0) + " = " + makeRotLow(a(opi,0), a(opi,1), Keccak_f1600_Rho[i]) + ";\n"; |
||||||
|
str += "\t\t" + b(pi,1) + " = " + makeRotHigh(a(opi,0), a(opi,1), Keccak_f1600_Rho[i]) + ";\n"; |
||||||
|
opi = pi; |
||||||
|
} |
||||||
|
|
||||||
|
// Chi
|
||||||
|
str += "\n\t\t// Chi\n"; |
||||||
|
for (var j = 0; j < 25; j += 5) |
||||||
|
{ |
||||||
|
for (var i = 0; i < 5; ++i) |
||||||
|
{ |
||||||
|
for (var w = 0; w <= 1; ++w) |
||||||
|
{ |
||||||
|
str += "\t\t" + a(j+i,w) + " = " + b(j+i,w) + " ^ ~" + b(j+(i+1)%5,w) + " & " + b(j+(i+2)%5,w) + ";\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Iota
|
||||||
|
str += "\n\t\t// Iota\n"; |
||||||
|
for (var w = 0; w <= 1; ++w) |
||||||
|
{ |
||||||
|
str += "\t\t" + a(0,w) + " ^= Keccak_f1600_RC[r|" + w + "];\n"; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
str += "\t}\n"; |
||||||
|
|
||||||
|
for (var i = 0; i < 25; ++i) |
||||||
|
{ |
||||||
|
if (i == 4 || i == 8) |
||||||
|
{ |
||||||
|
str += "\tif (outSize == " + i*2 + ")\n\t\treturn;\n"; |
||||||
|
} |
||||||
|
for (var w = 0; w <= 1; ++w) |
||||||
|
{ |
||||||
|
str += "\toutState[outOffset|"+(i<<1|w)+"] = " + a(i,w) + ";\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
str += "}\n"; |
||||||
|
|
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
console.log(makeKeccak_f1600()); |
@ -0,0 +1,53 @@ |
|||||||
|
// test.js
|
||||||
|
// Tim Hughes <tim@twistedfury.com>
|
||||||
|
|
||||||
|
/*jslint node: true, shadow:true */ |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
var ethash = require('./ethash'); |
||||||
|
var util = require('./util'); |
||||||
|
var Keccak = require('./keccak'); |
||||||
|
|
||||||
|
// sanity check hash functions
|
||||||
|
var src = util.stringToBytes(""); |
||||||
|
if (util.bytesToHexString(new Keccak().digest(32, src)) != "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") throw Error("Keccak-256 failed"); |
||||||
|
if (util.bytesToHexString(new Keccak().digest(64, src)) != "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e") throw Error("Keccak-512 failed"); |
||||||
|
|
||||||
|
src = new Uint32Array(src.buffer); |
||||||
|
var dst = new Uint32Array(8); |
||||||
|
new Keccak().digestWords(dst, 0, dst.length, src, 0, src.length); |
||||||
|
if (util.wordsToHexString(dst) != "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") throw Error("Keccak-256 Fast failed"); |
||||||
|
|
||||||
|
var dst = new Uint32Array(16); |
||||||
|
new Keccak().digestWords(dst, 0, dst.length, src, 0, src.length); |
||||||
|
if (util.wordsToHexString(dst) != "0eab42de4c3ceb9235fc91acffe746b29c29a8c366b7c60e4e67c466f36a4304c00fa9caf9d87976ba469bcbe06713b435f091ef2769fb160cdab33d3670680e") throw Error("Keccak-512 Fast failed"); |
||||||
|
|
||||||
|
|
||||||
|
// init params
|
||||||
|
var ethashParams = ethash.defaultParams(); |
||||||
|
//ethashParams.cacheRounds = 0;
|
||||||
|
|
||||||
|
// create hasher
|
||||||
|
var seed = util.hexStringToBytes("9410b944535a83d9adf6bbdcc80e051f30676173c16ca0d32d6f1263fc246466") |
||||||
|
var startTime = new Date().getTime(); |
||||||
|
var hasher = new ethash.Ethash(ethashParams, seed); |
||||||
|
console.log('Ethash startup took: '+(new Date().getTime() - startTime) + "ms"); |
||||||
|
console.log('Ethash cache hash: ' + util.bytesToHexString(hasher.cacheDigest())); |
||||||
|
|
||||||
|
var testHexString = "c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"; |
||||||
|
if (testHexString != util.bytesToHexString(util.hexStringToBytes(testHexString))) |
||||||
|
throw Error("bytesToHexString or hexStringToBytes broken"); |
||||||
|
|
||||||
|
|
||||||
|
var header = util.hexStringToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"); |
||||||
|
var nonce = util.hexStringToBytes("0000000000000000"); |
||||||
|
var hash; |
||||||
|
|
||||||
|
startTime = new Date().getTime(); |
||||||
|
var trials = 10; |
||||||
|
for (var i = 0; i < trials; ++i) |
||||||
|
{ |
||||||
|
hash = hasher.hash(header, nonce); |
||||||
|
} |
||||||
|
console.log("Light client hashes averaged: " + (new Date().getTime() - startTime)/trials + "ms"); |
||||||
|
console.log("Hash = " + util.bytesToHexString(hash)); |
@ -0,0 +1,100 @@ |
|||||||
|
// util.js
|
||||||
|
// Tim Hughes <tim@twistedfury.com>
|
||||||
|
|
||||||
|
/*jslint node: true, shadow:true */ |
||||||
|
"use strict"; |
||||||
|
|
||||||
|
function nibbleToChar(nibble) |
||||||
|
{ |
||||||
|
return String.fromCharCode((nibble < 10 ? 48 : 87) + nibble); |
||||||
|
} |
||||||
|
|
||||||
|
function charToNibble(chr) |
||||||
|
{ |
||||||
|
if (chr >= 48 && chr <= 57) |
||||||
|
{ |
||||||
|
return chr - 48; |
||||||
|
} |
||||||
|
if (chr >= 65 && chr <= 70) |
||||||
|
{ |
||||||
|
return chr - 65 + 10; |
||||||
|
} |
||||||
|
if (chr >= 97 && chr <= 102) |
||||||
|
{ |
||||||
|
return chr - 97 + 10; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
function stringToBytes(str) |
||||||
|
{ |
||||||
|
var bytes = new Uint8Array(str.length); |
||||||
|
for (var i = 0; i != str.length; ++i) |
||||||
|
{ |
||||||
|
bytes[i] = str.charCodeAt(i); |
||||||
|
} |
||||||
|
return bytes; |
||||||
|
} |
||||||
|
|
||||||
|
function hexStringToBytes(str) |
||||||
|
{ |
||||||
|
var bytes = new Uint8Array(str.length>>>1); |
||||||
|
for (var i = 0; i != bytes.length; ++i) |
||||||
|
{ |
||||||
|
bytes[i] = charToNibble(str.charCodeAt(i<<1 | 0)) << 4; |
||||||
|
bytes[i] |= charToNibble(str.charCodeAt(i<<1 | 1)); |
||||||
|
} |
||||||
|
return bytes; |
||||||
|
} |
||||||
|
|
||||||
|
function bytesToHexString(bytes) |
||||||
|
{ |
||||||
|
var str = ""; |
||||||
|
for (var i = 0; i != bytes.length; ++i) |
||||||
|
{ |
||||||
|
str += nibbleToChar(bytes[i] >>> 4); |
||||||
|
str += nibbleToChar(bytes[i] & 0xf); |
||||||
|
} |
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
function wordsToHexString(words) |
||||||
|
{ |
||||||
|
return bytesToHexString(new Uint8Array(words.buffer)); |
||||||
|
} |
||||||
|
|
||||||
|
function uint32ToHexString(num) |
||||||
|
{ |
||||||
|
var buf = new Uint8Array(4); |
||||||
|
buf[0] = (num >> 24) & 0xff; |
||||||
|
buf[1] = (num >> 16) & 0xff; |
||||||
|
buf[2] = (num >> 8) & 0xff; |
||||||
|
buf[3] = (num >> 0) & 0xff; |
||||||
|
return bytesToHexString(buf); |
||||||
|
} |
||||||
|
|
||||||
|
function toWords(input) |
||||||
|
{ |
||||||
|
if (input instanceof Uint32Array) |
||||||
|
{ |
||||||
|
return input; |
||||||
|
} |
||||||
|
else if (input instanceof Uint8Array) |
||||||
|
{ |
||||||
|
var tmp = new Uint8Array((input.length + 3) & ~3); |
||||||
|
tmp.set(input); |
||||||
|
return new Uint32Array(tmp.buffer); |
||||||
|
} |
||||||
|
else if (typeof input === typeof "") |
||||||
|
{ |
||||||
|
return toWords(stringToBytes(input)); |
||||||
|
} |
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
exports.stringToBytes = stringToBytes; |
||||||
|
exports.hexStringToBytes = hexStringToBytes; |
||||||
|
exports.bytesToHexString = bytesToHexString; |
||||||
|
exports.wordsToHexString = wordsToHexString; |
||||||
|
exports.uint32ToHexString = uint32ToHexString; |
||||||
|
exports.toWords = toWords; |
@ -0,0 +1,2 @@ |
|||||||
|
pyethash.egg-info/ |
||||||
|
*.so |
@ -0,0 +1,3 @@ |
|||||||
|
import pyethash.core |
||||||
|
core = pyethash.core |
||||||
|
EPOCH_LENGTH = 30000 |
@ -0,0 +1,21 @@ |
|||||||
|
#!/usr/bin/env python |
||||||
|
from distutils.core import setup, Extension |
||||||
|
|
||||||
|
pyethash_core = Extension('pyethash.core', |
||||||
|
sources = [ |
||||||
|
'src/python/core.c', |
||||||
|
'src/libethash/util.c', |
||||||
|
'src/libethash/internal.c', |
||||||
|
'src/libethash/sha3.c' |
||||||
|
], |
||||||
|
extra_compile_args = ["-std=gnu99"]) |
||||||
|
|
||||||
|
setup ( |
||||||
|
name = 'pyethash', |
||||||
|
author = "Matthew Wampler-Doty", |
||||||
|
author_email = "matthew.wampler.doty@gmail.com", |
||||||
|
license = 'GPL', |
||||||
|
version = '1.0', |
||||||
|
description = 'Python wrappers for ethash, the ethereum proof of work hashing function', |
||||||
|
ext_modules = [pyethash_core], |
||||||
|
) |
@ -0,0 +1,53 @@ |
|||||||
|
include_directories(..) |
||||||
|
|
||||||
|
set(CMAKE_BUILD_TYPE Release) |
||||||
|
|
||||||
|
if (MSVC) |
||||||
|
add_definitions("/openmp") |
||||||
|
endif() |
||||||
|
|
||||||
|
if (NOT MPI_FOUND) |
||||||
|
find_package(MPI) |
||||||
|
endif() |
||||||
|
|
||||||
|
if (NOT CRYPTOPP_FOUND) |
||||||
|
find_package(CryptoPP 5.6.2) |
||||||
|
endif() |
||||||
|
|
||||||
|
if (CRYPTOPP_FOUND) |
||||||
|
add_definitions(-DWITH_CRYPTOPP) |
||||||
|
endif() |
||||||
|
|
||||||
|
if (NOT OpenCL_FOUND) |
||||||
|
find_package(OpenCL) |
||||||
|
endif() |
||||||
|
if (OpenCL_FOUND) |
||||||
|
add_definitions(-DWITH_OPENCL) |
||||||
|
include_directories(${OpenCL_INCLUDE_DIRS}) |
||||||
|
list(APPEND FILES ethash_cl_miner.cpp ethash_cl_miner.h) |
||||||
|
endif() |
||||||
|
|
||||||
|
if (MPI_FOUND) |
||||||
|
include_directories(${MPI_INCLUDE_PATH}) |
||||||
|
add_executable (Benchmark_MPI_FULL benchmark.cpp) |
||||||
|
target_link_libraries (Benchmark_MPI_FULL ${ETHHASH_LIBS} ${MPI_LIBRARIES}) |
||||||
|
SET_TARGET_PROPERTIES(Benchmark_MPI_FULL PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} ${MPI_COMPILE_FLAGS} -DFULL -DMPI") |
||||||
|
|
||||||
|
add_executable (Benchmark_MPI_LIGHT benchmark.cpp) |
||||||
|
target_link_libraries (Benchmark_MPI_LIGHT ${ETHHASH_LIBS} ${MPI_LIBRARIES}) |
||||||
|
SET_TARGET_PROPERTIES(Benchmark_MPI_LIGHT PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} ${MPI_COMPILE_FLAGS} -DMPI") |
||||||
|
endif() |
||||||
|
|
||||||
|
add_executable (Benchmark_FULL benchmark.cpp) |
||||||
|
target_link_libraries (Benchmark_FULL ${ETHHASH_LIBS}) |
||||||
|
SET_TARGET_PROPERTIES(Benchmark_FULL PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -DFULL") |
||||||
|
|
||||||
|
add_executable (Benchmark_LIGHT benchmark.cpp) |
||||||
|
target_link_libraries (Benchmark_LIGHT ${ETHHASH_LIBS}) |
||||||
|
|
||||||
|
if (OpenCL_FOUND) |
||||||
|
add_executable (Benchmark_CL benchmark.cpp) |
||||||
|
target_link_libraries (Benchmark_CL ${ETHHASH_LIBS} ethash-cl) |
||||||
|
SET_TARGET_PROPERTIES(Benchmark_CL PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -DOPENCL") |
||||||
|
endif() |
||||||
|
|
@ -0,0 +1,260 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
/** @file benchmark.cpp
|
||||||
|
* @author Tim Hughes <tim@twistedfury.com> |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <time.h> |
||||||
|
#include <libethash/ethash.h> |
||||||
|
#include <libethash/util.h> |
||||||
|
#ifdef OPENCL |
||||||
|
#include <libethash-cl/ethash_cl_miner.h> |
||||||
|
#endif |
||||||
|
#include <vector> |
||||||
|
#include <algorithm> |
||||||
|
|
||||||
|
#ifdef WITH_CRYPTOPP |
||||||
|
#include <libethash/SHA3_cryptopp.h> |
||||||
|
#include <string> |
||||||
|
|
||||||
|
#else |
||||||
|
#include "libethash/sha3.h" |
||||||
|
#endif // WITH_CRYPTOPP
|
||||||
|
|
||||||
|
#undef min |
||||||
|
#undef max |
||||||
|
|
||||||
|
#if defined(OPENCL) |
||||||
|
const unsigned trials = 1024*1024*32; |
||||||
|
#elif defined(FULL) |
||||||
|
const unsigned trials = 1024*1024/8; |
||||||
|
#else |
||||||
|
const unsigned trials = 1024*1024/1024; |
||||||
|
#endif |
||||||
|
uint8_t g_hashes[1024*32]; |
||||||
|
|
||||||
|
static char nibbleToChar(unsigned nibble) |
||||||
|
{ |
||||||
|
return (char) ((nibble >= 10 ? 'a'-10 : '0') + nibble); |
||||||
|
} |
||||||
|
|
||||||
|
static uint8_t charToNibble(char chr) |
||||||
|
{ |
||||||
|
if (chr >= '0' && chr <= '9') |
||||||
|
{ |
||||||
|
return (uint8_t) (chr - '0'); |
||||||
|
} |
||||||
|
if (chr >= 'a' && chr <= 'z') |
||||||
|
{ |
||||||
|
return (uint8_t) (chr - 'a' + 10); |
||||||
|
} |
||||||
|
if (chr >= 'A' && chr <= 'Z') |
||||||
|
{ |
||||||
|
return (uint8_t) (chr - 'A' + 10); |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
static std::vector<uint8_t> hexStringToBytes(char const* str) |
||||||
|
{ |
||||||
|
std::vector<uint8_t> bytes(strlen(str) >> 1); |
||||||
|
for (unsigned i = 0; i != bytes.size(); ++i) |
||||||
|
{ |
||||||
|
bytes[i] = charToNibble(str[i*2 | 0]) << 4; |
||||||
|
bytes[i] |= charToNibble(str[i*2 | 1]); |
||||||
|
} |
||||||
|
return bytes; |
||||||
|
} |
||||||
|
|
||||||
|
static std::string bytesToHexString(uint8_t const* bytes, unsigned size) |
||||||
|
{ |
||||||
|
std::string str; |
||||||
|
for (unsigned i = 0; i != size; ++i) |
||||||
|
{ |
||||||
|
str += nibbleToChar(bytes[i] >> 4); |
||||||
|
str += nibbleToChar(bytes[i] & 0xf); |
||||||
|
} |
||||||
|
return str; |
||||||
|
} |
||||||
|
|
||||||
|
extern "C" int main(void) |
||||||
|
{ |
||||||
|
// params for ethash
|
||||||
|
ethash_params params; |
||||||
|
ethash_params_init(¶ms, 0); |
||||||
|
//params.full_size = 262147 * 4096; // 1GBish;
|
||||||
|
//params.full_size = 32771 * 4096; // 128MBish;
|
||||||
|
//params.full_size = 8209 * 4096; // 8MBish;
|
||||||
|
//params.cache_size = 8209*4096;
|
||||||
|
//params.cache_size = 2053*4096;
|
||||||
|
uint8_t seed[32], previous_hash[32]; |
||||||
|
|
||||||
|
memcpy(seed, hexStringToBytes("9410b944535a83d9adf6bbdcc80e051f30676173c16ca0d32d6f1263fc246466").data(), 32); |
||||||
|
memcpy(previous_hash, hexStringToBytes("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").data(), 32); |
||||||
|
|
||||||
|
// allocate page aligned buffer for dataset
|
||||||
|
#ifdef FULL |
||||||
|
void* full_mem_buf = malloc(params.full_size + 4095); |
||||||
|
void* full_mem = (void*)((uintptr_t(full_mem_buf) + 4095) & ~4095); |
||||||
|
#endif |
||||||
|
void* cache_mem_buf = malloc(params.cache_size + 63); |
||||||
|
void* cache_mem = (void*)((uintptr_t(cache_mem_buf) + 63) & ~63); |
||||||
|
|
||||||
|
ethash_cache cache; |
||||||
|
cache.mem = cache_mem; |
||||||
|
|
||||||
|
// compute cache or full data
|
||||||
|
{ |
||||||
|
clock_t startTime = clock(); |
||||||
|
ethash_mkcache(&cache, ¶ms, seed); |
||||||
|
clock_t time = clock() - startTime; |
||||||
|
|
||||||
|
uint8_t cache_hash[32]; |
||||||
|
SHA3_256(cache_hash, (uint8_t const*)cache_mem, params.cache_size); |
||||||
|
debugf("ethash_mkcache: %ums, sha3: %s\n", (unsigned)((time*1000)/CLOCKS_PER_SEC), bytesToHexString(cache_hash,sizeof(cache_hash)).data()); |
||||||
|
|
||||||
|
// print a couple of test hashes
|
||||||
|
{ |
||||||
|
const clock_t startTime = clock(); |
||||||
|
ethash_return_value hash; |
||||||
|
ethash_light(&hash, &cache, ¶ms, previous_hash, 0); |
||||||
|
const clock_t time = clock() - startTime; |
||||||
|
debugf("ethash_light test: %ums, %s\n", (unsigned)((time*1000)/CLOCKS_PER_SEC), bytesToHexString(hash.result, 32).data()); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef FULL |
||||||
|
startTime = clock(); |
||||||
|
ethash_compute_full_data(full_mem, ¶ms, &cache); |
||||||
|
time = clock() - startTime; |
||||||
|
debugf("ethash_compute_full_data: %ums\n", (unsigned)((time*1000)/CLOCKS_PER_SEC)); |
||||||
|
#endif // FULL
|
||||||
|
} |
||||||
|
|
||||||
|
#ifdef OPENCL |
||||||
|
ethash_cl_miner miner; |
||||||
|
{ |
||||||
|
const clock_t startTime = clock(); |
||||||
|
if (!miner.init(params, seed)) |
||||||
|
exit(-1); |
||||||
|
const clock_t time = clock() - startTime; |
||||||
|
debugf("ethash_cl_miner init: %ums\n", (unsigned)((time*1000)/CLOCKS_PER_SEC)); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
|
||||||
|
#ifdef FULL |
||||||
|
{ |
||||||
|
const clock_t startTime = clock(); |
||||||
|
ethash_return_value hash; |
||||||
|
ethash_full(&hash, full_mem, ¶ms, previous_hash, 0); |
||||||
|
const clock_t time = clock() - startTime; |
||||||
|
debugf("ethash_full test: %uns, %s\n", (unsigned)((time*1000000)/CLOCKS_PER_SEC), bytesToHexString(hash.result, 32).data()); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifdef OPENCL |
||||||
|
// validate 1024 hashes against CPU
|
||||||
|
miner.hash(g_hashes, previous_hash, 0, 1024); |
||||||
|
for (unsigned i = 0; i != 1024; ++i) |
||||||
|
{ |
||||||
|
ethash_return_value hash; |
||||||
|
ethash_light(&hash, &cache, ¶ms, previous_hash, i); |
||||||
|
if (memcmp(hash.result, g_hashes + 32*i, 32) != 0) |
||||||
|
{ |
||||||
|
debugf("nonce %u failed: %s %s\n", i, bytesToHexString(g_hashes + 32*i, 32).c_str(), bytesToHexString(hash.result, 32).c_str()); |
||||||
|
static unsigned c = 0; |
||||||
|
if (++c == 16) |
||||||
|
{ |
||||||
|
exit(-1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
|
||||||
|
clock_t startTime = clock(); |
||||||
|
unsigned hash_count = trials; |
||||||
|
|
||||||
|
#ifdef OPENCL |
||||||
|
{ |
||||||
|
struct search_hook : ethash_cl_miner::search_hook |
||||||
|
{ |
||||||
|
unsigned hash_count; |
||||||
|
std::vector<uint64_t> nonce_vec; |
||||||
|
|
||||||
|
virtual bool found(uint64_t const* nonces, uint32_t count) |
||||||
|
{ |
||||||
|
nonce_vec.assign(nonces, nonces + count); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
virtual bool searched(uint64_t start_nonce, uint32_t count) |
||||||
|
{ |
||||||
|
// do nothing
|
||||||
|
hash_count += count; |
||||||
|
return hash_count >= trials; |
||||||
|
} |
||||||
|
}; |
||||||
|
search_hook hook; |
||||||
|
hook.hash_count = 0; |
||||||
|
|
||||||
|
miner.search(previous_hash, 0x000000ffffffffff, hook); |
||||||
|
|
||||||
|
for (unsigned i = 0; i != hook.nonce_vec.size(); ++i) |
||||||
|
{ |
||||||
|
uint64_t nonce = hook.nonce_vec[i]; |
||||||
|
ethash_return_value hash; |
||||||
|
ethash_light(&hash, &cache, ¶ms, previous_hash, nonce); |
||||||
|
debugf("found: %.8x%.8x -> %s\n", unsigned(nonce>>32), unsigned(nonce), bytesToHexString(hash.result, 32).c_str()); |
||||||
|
} |
||||||
|
|
||||||
|
hash_count = hook.hash_count; |
||||||
|
} |
||||||
|
#else |
||||||
|
{ |
||||||
|
//#pragma omp parallel for
|
||||||
|
for (int nonce = 0; nonce < trials; ++nonce) |
||||||
|
{ |
||||||
|
ethash_return_value hash; |
||||||
|
#ifdef FULL |
||||||
|
ethash_full(&hash, full_mem, ¶ms, previous_hash, nonce); |
||||||
|
#else |
||||||
|
ethash_light(&hash, &cache, ¶ms, previous_hash, nonce); |
||||||
|
#endif // FULL
|
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
clock_t time = std::max((clock_t)1u, clock() - startTime); |
||||||
|
|
||||||
|
unsigned read_size = ACCESSES * MIX_BYTES; |
||||||
|
debugf(
|
||||||
|
"hashrate: %8u, bw: %6u MB/s\n", |
||||||
|
(unsigned)(((uint64_t)hash_count*CLOCKS_PER_SEC)/time), |
||||||
|
(unsigned)((((uint64_t)hash_count*read_size*CLOCKS_PER_SEC)/time) / (1024*1024)) |
||||||
|
); |
||||||
|
|
||||||
|
free(cache_mem_buf); |
||||||
|
#ifdef FULL |
||||||
|
free(full_mem_buf); |
||||||
|
#endif |
||||||
|
|
||||||
|
return 0; |
||||||
|
} |
15
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/CMakeLists.txt
generated
vendored
15
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/CMakeLists.txt
generated
vendored
@ -0,0 +1,15 @@ |
|||||||
|
set(LIBRARY ethash-cl) |
||||||
|
set(CMAKE_BUILD_TYPE Release) |
||||||
|
|
||||||
|
include(bin2h.cmake) |
||||||
|
bin2h(SOURCE_FILE ethash_cl_miner_kernel.cl VARIABLE_NAME ethash_cl_miner_kernel HEADER_FILE ${CMAKE_CURRENT_BINARY_DIR}/ethash_cl_miner_kernel.h) |
||||||
|
|
||||||
|
if (NOT OpenCL_FOUND) |
||||||
|
find_package(OpenCL) |
||||||
|
endif() |
||||||
|
if (OpenCL_FOUND) |
||||||
|
include_directories(${OpenCL_INCLUDE_DIRS} ${CMAKE_CURRENT_BINARY_DIR}) |
||||||
|
include_directories(..) |
||||||
|
add_library(${LIBRARY} ethash_cl_miner.cpp ethash_cl_miner.h) |
||||||
|
TARGET_LINK_LIBRARIES(${LIBRARY} ${OpenCL_LIBRARIES} ethash) |
||||||
|
endif() |
@ -0,0 +1,87 @@ |
|||||||
|
# https://gist.github.com/sivachandran/3a0de157dccef822a230 |
||||||
|
include(CMakeParseArguments) |
||||||
|
|
||||||
|
# Function to wrap a given string into multiple lines at the given column position. |
||||||
|
# Parameters: |
||||||
|
# VARIABLE - The name of the CMake variable holding the string. |
||||||
|
# AT_COLUMN - The column position at which string will be wrapped. |
||||||
|
function(WRAP_STRING) |
||||||
|
set(oneValueArgs VARIABLE AT_COLUMN) |
||||||
|
cmake_parse_arguments(WRAP_STRING "${options}" "${oneValueArgs}" "" ${ARGN}) |
||||||
|
|
||||||
|
string(LENGTH ${${WRAP_STRING_VARIABLE}} stringLength) |
||||||
|
math(EXPR offset "0") |
||||||
|
|
||||||
|
while(stringLength GREATER 0) |
||||||
|
|
||||||
|
if(stringLength GREATER ${WRAP_STRING_AT_COLUMN}) |
||||||
|
math(EXPR length "${WRAP_STRING_AT_COLUMN}") |
||||||
|
else() |
||||||
|
math(EXPR length "${stringLength}") |
||||||
|
endif() |
||||||
|
|
||||||
|
string(SUBSTRING ${${WRAP_STRING_VARIABLE}} ${offset} ${length} line) |
||||||
|
set(lines "${lines}\n${line}") |
||||||
|
|
||||||
|
math(EXPR stringLength "${stringLength} - ${length}") |
||||||
|
math(EXPR offset "${offset} + ${length}") |
||||||
|
endwhile() |
||||||
|
|
||||||
|
set(${WRAP_STRING_VARIABLE} "${lines}" PARENT_SCOPE) |
||||||
|
endfunction() |
||||||
|
|
||||||
|
# Function to embed contents of a file as byte array in C/C++ header file(.h). The header file |
||||||
|
# will contain a byte array and integer variable holding the size of the array. |
||||||
|
# Parameters |
||||||
|
# SOURCE_FILE - The path of source file whose contents will be embedded in the header file. |
||||||
|
# VARIABLE_NAME - The name of the variable for the byte array. The string "_SIZE" will be append |
||||||
|
# to this name and will be used a variable name for size variable. |
||||||
|
# HEADER_FILE - The path of header file. |
||||||
|
# APPEND - If specified appends to the header file instead of overwriting it |
||||||
|
# NULL_TERMINATE - If specified a null byte(zero) will be append to the byte array. This will be |
||||||
|
# useful if the source file is a text file and we want to use the file contents |
||||||
|
# as string. But the size variable holds size of the byte array without this |
||||||
|
# null byte. |
||||||
|
# Usage: |
||||||
|
# bin2h(SOURCE_FILE "Logo.png" HEADER_FILE "Logo.h" VARIABLE_NAME "LOGO_PNG") |
||||||
|
function(BIN2H) |
||||||
|
set(options APPEND NULL_TERMINATE) |
||||||
|
set(oneValueArgs SOURCE_FILE VARIABLE_NAME HEADER_FILE) |
||||||
|
cmake_parse_arguments(BIN2H "${options}" "${oneValueArgs}" "" ${ARGN}) |
||||||
|
|
||||||
|
# reads source file contents as hex string |
||||||
|
file(READ ${BIN2H_SOURCE_FILE} hexString HEX) |
||||||
|
string(LENGTH ${hexString} hexStringLength) |
||||||
|
|
||||||
|
# appends null byte if asked |
||||||
|
if(BIN2H_NULL_TERMINATE) |
||||||
|
set(hexString "${hexString}00") |
||||||
|
endif() |
||||||
|
|
||||||
|
# wraps the hex string into multiple lines at column 32(i.e. 16 bytes per line) |
||||||
|
wrap_string(VARIABLE hexString AT_COLUMN 32) |
||||||
|
math(EXPR arraySize "${hexStringLength} / 2") |
||||||
|
|
||||||
|
# adds '0x' prefix and comma suffix before and after every byte respectively |
||||||
|
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " arrayValues ${hexString}) |
||||||
|
# removes trailing comma |
||||||
|
string(REGEX REPLACE ", $" "" arrayValues ${arrayValues}) |
||||||
|
|
||||||
|
# converts the variable name into proper C identifier |
||||||
|
# TODO: fix for legacy cmake |
||||||
|
IF (${CMAKE_VERSION} GREATER 2.8.10) |
||||||
|
string(MAKE_C_IDENTIFIER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) |
||||||
|
ENDIF() |
||||||
|
string(TOUPPER "${BIN2H_VARIABLE_NAME}" BIN2H_VARIABLE_NAME) |
||||||
|
|
||||||
|
# declares byte array and the length variables |
||||||
|
set(arrayDefinition "const unsigned char ${BIN2H_VARIABLE_NAME}[] = { ${arrayValues} };") |
||||||
|
set(arraySizeDefinition "const size_t ${BIN2H_VARIABLE_NAME}_SIZE = ${arraySize};") |
||||||
|
|
||||||
|
set(declarations "${arrayDefinition}\n\n${arraySizeDefinition}\n\n") |
||||||
|
if(BIN2H_APPEND) |
||||||
|
file(APPEND ${BIN2H_HEADER_FILE} "${declarations}") |
||||||
|
else() |
||||||
|
file(WRITE ${BIN2H_HEADER_FILE} "${declarations}") |
||||||
|
endif() |
||||||
|
endfunction() |
File diff suppressed because it is too large
Load Diff
289
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/ethash_cl_miner.cpp
generated
vendored
289
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/ethash_cl_miner.cpp
generated
vendored
@ -0,0 +1,289 @@ |
|||||||
|
/*
|
||||||
|
This file is part of c-ethash. |
||||||
|
|
||||||
|
c-ethash is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
c-ethash 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
/** @file ethash_cl_miner.cpp
|
||||||
|
* @author Tim Hughes <tim@twistedfury.com> |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
|
||||||
|
|
||||||
|
#define _CRT_SECURE_NO_WARNINGS |
||||||
|
|
||||||
|
#include <assert.h> |
||||||
|
#include <queue> |
||||||
|
#include "ethash_cl_miner.h" |
||||||
|
#include "ethash_cl_miner_kernel.h" |
||||||
|
#include <libethash/util.h> |
||||||
|
|
||||||
|
#undef min |
||||||
|
#undef max |
||||||
|
|
||||||
|
#define HASH_BYTES 32 |
||||||
|
|
||||||
|
static void add_definition(std::string& source, char const* id, unsigned value) |
||||||
|
{ |
||||||
|
char buf[256]; |
||||||
|
sprintf(buf, "#define %s %uu\n", id, value); |
||||||
|
source.insert(source.begin(), buf, buf + strlen(buf)); |
||||||
|
} |
||||||
|
|
||||||
|
ethash_cl_miner::ethash_cl_miner() |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
bool ethash_cl_miner::init(ethash_params const& params, const uint8_t seed[32], unsigned workgroup_size) |
||||||
|
{ |
||||||
|
// store params
|
||||||
|
m_params = params; |
||||||
|
|
||||||
|
// get all platforms
|
||||||
|
std::vector<cl::Platform> platforms; |
||||||
|
cl::Platform::get(&platforms); |
||||||
|
if (platforms.empty()) |
||||||
|
{ |
||||||
|
debugf("No OpenCL platforms found.\n"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// use default platform
|
||||||
|
debugf("Using platform: %s\n", platforms[0].getInfo<CL_PLATFORM_NAME>().c_str()); |
||||||
|
|
||||||
|
// get GPU device of the default platform
|
||||||
|
std::vector<cl::Device> devices; |
||||||
|
platforms[0].getDevices(CL_DEVICE_TYPE_ALL, &devices); |
||||||
|
if (devices.empty()) |
||||||
|
{ |
||||||
|
debugf("No OpenCL devices found.\n"); |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
// use default device
|
||||||
|
cl::Device& device = devices[0]; |
||||||
|
debugf("Using device: %s\n", device.getInfo<CL_DEVICE_NAME>().c_str()); |
||||||
|
|
||||||
|
// create context
|
||||||
|
m_context = cl::Context({device}); |
||||||
|
m_queue = cl::CommandQueue(m_context, device); |
||||||
|
|
||||||
|
// use requested workgroup size, but we require multiple of 8
|
||||||
|
m_workgroup_size = ((workgroup_size + 7) / 8) * 8; |
||||||
|
|
||||||
|
// patch source code
|
||||||
|
std::string code(ETHASH_CL_MINER_KERNEL, ETHASH_CL_MINER_KERNEL + ETHASH_CL_MINER_KERNEL_SIZE); |
||||||
|
add_definition(code, "GROUP_SIZE", m_workgroup_size); |
||||||
|
add_definition(code, "DAG_SIZE", (unsigned)(params.full_size / MIX_BYTES)); |
||||||
|
add_definition(code, "ACCESSES", ACCESSES); |
||||||
|
add_definition(code, "MAX_OUTPUTS", c_max_search_results); |
||||||
|
//debugf("%s", code.c_str());
|
||||||
|
|
||||||
|
// create miner OpenCL program
|
||||||
|
cl::Program::Sources sources; |
||||||
|
sources.push_back({code.c_str(), code.size()}); |
||||||
|
|
||||||
|
cl::Program program(m_context, sources); |
||||||
|
try |
||||||
|
{ |
||||||
|
program.build({device}); |
||||||
|
} |
||||||
|
catch (cl::Error err) |
||||||
|
{ |
||||||
|
debugf("%s\n", program.getBuildInfo<CL_PROGRAM_BUILD_LOG>(device).c_str()); |
||||||
|
return false; |
||||||
|
} |
||||||
|
m_hash_kernel = cl::Kernel(program, "ethash_hash"); |
||||||
|
m_search_kernel = cl::Kernel(program, "ethash_search"); |
||||||
|
|
||||||
|
// create buffer for dag
|
||||||
|
m_dag = cl::Buffer(m_context, CL_MEM_READ_ONLY, params.full_size); |
||||||
|
|
||||||
|
// create buffer for header
|
||||||
|
m_header = cl::Buffer(m_context, CL_MEM_READ_ONLY, 32); |
||||||
|
|
||||||
|
// compute dag on CPU
|
||||||
|
{ |
||||||
|
void* cache_mem = malloc(params.cache_size + 63); |
||||||
|
ethash_cache cache; |
||||||
|
cache.mem = (void*)(((uintptr_t)cache_mem + 63) & ~63); |
||||||
|
ethash_mkcache(&cache, ¶ms, seed); |
||||||
|
|
||||||
|
// if this throws then it's because we probably need to subdivide the dag uploads for compatibility
|
||||||
|
void* dag_ptr = m_queue.enqueueMapBuffer(m_dag, true, CL_MAP_WRITE_INVALIDATE_REGION, 0, params.full_size); |
||||||
|
ethash_compute_full_data(dag_ptr, ¶ms, &cache); |
||||||
|
m_queue.enqueueUnmapMemObject(m_dag, dag_ptr); |
||||||
|
|
||||||
|
free(cache_mem); |
||||||
|
} |
||||||
|
|
||||||
|
// create mining buffers
|
||||||
|
for (unsigned i = 0; i != c_num_buffers; ++i) |
||||||
|
{ |
||||||
|
m_hash_buf[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY | CL_MEM_HOST_READ_ONLY, 32*c_hash_batch_size); |
||||||
|
m_search_buf[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY, (c_max_search_results + 1) * sizeof(uint32_t)); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
void ethash_cl_miner::hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count) |
||||||
|
{ |
||||||
|
struct pending_batch |
||||||
|
{ |
||||||
|
unsigned base; |
||||||
|
unsigned count; |
||||||
|
unsigned buf; |
||||||
|
}; |
||||||
|
std::queue<pending_batch> pending; |
||||||
|
|
||||||
|
// update header constant buffer
|
||||||
|
m_queue.enqueueWriteBuffer(m_header, true, 0, 32, header); |
||||||
|
|
||||||
|
/*
|
||||||
|
__kernel void ethash_combined_hash( |
||||||
|
__global hash32_t* g_hashes, |
||||||
|
__constant hash32_t const* g_header, |
||||||
|
__global hash128_t const* g_dag, |
||||||
|
ulong start_nonce, |
||||||
|
uint isolate |
||||||
|
) |
||||||
|
*/ |
||||||
|
m_hash_kernel.setArg(1, m_header); |
||||||
|
m_hash_kernel.setArg(2, m_dag); |
||||||
|
m_hash_kernel.setArg(3, nonce); |
||||||
|
m_hash_kernel.setArg(4, ~0u); // have to pass this to stop the compile unrolling the loop
|
||||||
|
|
||||||
|
unsigned buf = 0; |
||||||
|
for (unsigned i = 0; i < count || !pending.empty(); ) |
||||||
|
{ |
||||||
|
// how many this batch
|
||||||
|
if (i < count) |
||||||
|
{ |
||||||
|
unsigned const this_count = std::min(count - i, c_hash_batch_size); |
||||||
|
unsigned const batch_count = std::max(this_count, m_workgroup_size); |
||||||
|
|
||||||
|
// supply output hash buffer to kernel
|
||||||
|
m_hash_kernel.setArg(0, m_hash_buf[buf]); |
||||||
|
|
||||||
|
// execute it!
|
||||||
|
clock_t start_time = clock(); |
||||||
|
m_queue.enqueueNDRangeKernel( |
||||||
|
m_hash_kernel, |
||||||
|
cl::NullRange, |
||||||
|
cl::NDRange(batch_count), |
||||||
|
cl::NDRange(m_workgroup_size) |
||||||
|
); |
||||||
|
m_queue.flush(); |
||||||
|
|
||||||
|
pending.push({i, this_count, buf}); |
||||||
|
i += this_count; |
||||||
|
buf = (buf + 1) % c_num_buffers; |
||||||
|
} |
||||||
|
|
||||||
|
// read results
|
||||||
|
if (i == count || pending.size() == c_num_buffers) |
||||||
|
{ |
||||||
|
pending_batch const& batch = pending.front(); |
||||||
|
|
||||||
|
// could use pinned host pointer instead, but this path isn't that important.
|
||||||
|
uint8_t* hashes = (uint8_t*)m_queue.enqueueMapBuffer(m_hash_buf[batch.buf], true, CL_MAP_READ, 0, batch.count * HASH_BYTES); |
||||||
|
memcpy(ret + batch.base*HASH_BYTES, hashes, batch.count*HASH_BYTES); |
||||||
|
m_queue.enqueueUnmapMemObject(m_hash_buf[batch.buf], hashes); |
||||||
|
|
||||||
|
pending.pop(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook& hook) |
||||||
|
{ |
||||||
|
struct pending_batch |
||||||
|
{ |
||||||
|
uint64_t start_nonce; |
||||||
|
unsigned buf; |
||||||
|
}; |
||||||
|
std::queue<pending_batch> pending; |
||||||
|
|
||||||
|
static uint32_t const c_zero = 0; |
||||||
|
|
||||||
|
// update header constant buffer
|
||||||
|
m_queue.enqueueWriteBuffer(m_header, false, 0, 32, header); |
||||||
|
for (unsigned i = 0; i != c_num_buffers; ++i) |
||||||
|
{ |
||||||
|
m_queue.enqueueWriteBuffer(m_search_buf[i], false, 0, 4, &c_zero); |
||||||
|
} |
||||||
|
cl::Event pre_return_event; |
||||||
|
m_queue.enqueueBarrierWithWaitList(NULL, &pre_return_event); |
||||||
|
|
||||||
|
/*
|
||||||
|
__kernel void ethash_combined_search( |
||||||
|
__global hash32_t* g_hashes, // 0
|
||||||
|
__constant hash32_t const* g_header, // 1
|
||||||
|
__global hash128_t const* g_dag, // 2
|
||||||
|
ulong start_nonce, // 3
|
||||||
|
ulong target, // 4
|
||||||
|
uint isolate // 5
|
||||||
|
) |
||||||
|
*/ |
||||||
|
m_search_kernel.setArg(1, m_header); |
||||||
|
m_search_kernel.setArg(2, m_dag); |
||||||
|
|
||||||
|
// pass these to stop the compiler unrolling the loops
|
||||||
|
m_search_kernel.setArg(4, target); |
||||||
|
m_search_kernel.setArg(5, ~0u); |
||||||
|
|
||||||
|
|
||||||
|
unsigned buf = 0; |
||||||
|
for (uint64_t start_nonce = 0; ; start_nonce += c_search_batch_size) |
||||||
|
{ |
||||||
|
// supply output buffer to kernel
|
||||||
|
m_search_kernel.setArg(0, m_search_buf[buf]); |
||||||
|
m_search_kernel.setArg(3, start_nonce); |
||||||
|
|
||||||
|
// execute it!
|
||||||
|
m_queue.enqueueNDRangeKernel(m_search_kernel, cl::NullRange, c_search_batch_size, m_workgroup_size); |
||||||
|
|
||||||
|
pending.push({start_nonce, buf}); |
||||||
|
buf = (buf + 1) % c_num_buffers; |
||||||
|
|
||||||
|
// read results
|
||||||
|
if (pending.size() == c_num_buffers) |
||||||
|
{ |
||||||
|
pending_batch const& batch = pending.front(); |
||||||
|
|
||||||
|
// could use pinned host pointer instead
|
||||||
|
uint32_t* results = (uint32_t*)m_queue.enqueueMapBuffer(m_search_buf[batch.buf], true, CL_MAP_READ, 0, (1+c_max_search_results) * sizeof(uint32_t)); |
||||||
|
unsigned num_found = std::min(results[0], c_max_search_results); |
||||||
|
|
||||||
|
uint64_t nonces[c_max_search_results]; |
||||||
|
for (unsigned i = 0; i != num_found; ++i) |
||||||
|
{ |
||||||
|
nonces[i] = batch.start_nonce + results[i+1]; |
||||||
|
} |
||||||
|
|
||||||
|
m_queue.enqueueUnmapMemObject(m_search_buf[batch.buf], results); |
||||||
|
|
||||||
|
bool exit = num_found && hook.found(nonces, num_found); |
||||||
|
exit |= hook.searched(batch.start_nonce, c_search_batch_size); // always report searched before exit
|
||||||
|
if (exit) |
||||||
|
break; |
||||||
|
|
||||||
|
pending.pop(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// not safe to return until this is ready
|
||||||
|
pre_return_event.wait(); |
||||||
|
} |
||||||
|
|
43
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/ethash_cl_miner.h
generated
vendored
43
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/ethash_cl_miner.h
generated
vendored
@ -0,0 +1,43 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#define __CL_ENABLE_EXCEPTIONS |
||||||
|
#define CL_USE_DEPRECATED_OPENCL_2_0_APIS |
||||||
|
#include "cl.hpp" |
||||||
|
#include <time.h> |
||||||
|
#include <libethash/ethash.h> |
||||||
|
|
||||||
|
class ethash_cl_miner |
||||||
|
{ |
||||||
|
public: |
||||||
|
struct search_hook |
||||||
|
{ |
||||||
|
// reports progress, return true to abort
|
||||||
|
virtual bool found(uint64_t const* nonces, uint32_t count) = 0; |
||||||
|
virtual bool searched(uint64_t start_nonce, uint32_t count) = 0; |
||||||
|
}; |
||||||
|
|
||||||
|
public: |
||||||
|
ethash_cl_miner(); |
||||||
|
|
||||||
|
bool init(ethash_params const& params, const uint8_t seed[32], unsigned workgroup_size = 64); |
||||||
|
|
||||||
|
void hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count); |
||||||
|
void search(uint8_t const* header, uint64_t target, search_hook& hook); |
||||||
|
|
||||||
|
private: |
||||||
|
static unsigned const c_max_search_results = 63; |
||||||
|
static unsigned const c_num_buffers = 2; |
||||||
|
static unsigned const c_hash_batch_size = 1024; |
||||||
|
static unsigned const c_search_batch_size = 1024*256; |
||||||
|
|
||||||
|
ethash_params m_params; |
||||||
|
cl::Context m_context; |
||||||
|
cl::CommandQueue m_queue; |
||||||
|
cl::Kernel m_hash_kernel; |
||||||
|
cl::Kernel m_search_kernel; |
||||||
|
cl::Buffer m_dag; |
||||||
|
cl::Buffer m_header; |
||||||
|
cl::Buffer m_hash_buf[c_num_buffers]; |
||||||
|
cl::Buffer m_search_buf[c_num_buffers]; |
||||||
|
unsigned m_workgroup_size; |
||||||
|
}; |
461
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/ethash_cl_miner_kernel.cl
generated
vendored
461
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash-cl/ethash_cl_miner_kernel.cl
generated
vendored
@ -0,0 +1,461 @@ |
|||||||
|
// author Tim Hughes <tim@twistedfury.com> |
||||||
|
// Tested on Radeon HD 7850 |
||||||
|
// Hashrate: 15940347 hashes/s |
||||||
|
// Bandwidth: 124533 MB/s |
||||||
|
// search kernel should fit in <= 84 VGPRS (3 wavefronts) |
||||||
|
|
||||||
|
#define THREADS_PER_HASH (128 / 16) |
||||||
|
#define HASHES_PER_LOOP (GROUP_SIZE / THREADS_PER_HASH) |
||||||
|
|
||||||
|
#define FNV_PRIME 0x01000193 |
||||||
|
|
||||||
|
__constant uint2 const Keccak_f1600_RC[24] = { |
||||||
|
(uint2)(0x00000001, 0x00000000), |
||||||
|
(uint2)(0x00008082, 0x00000000), |
||||||
|
(uint2)(0x0000808a, 0x80000000), |
||||||
|
(uint2)(0x80008000, 0x80000000), |
||||||
|
(uint2)(0x0000808b, 0x00000000), |
||||||
|
(uint2)(0x80000001, 0x00000000), |
||||||
|
(uint2)(0x80008081, 0x80000000), |
||||||
|
(uint2)(0x00008009, 0x80000000), |
||||||
|
(uint2)(0x0000008a, 0x00000000), |
||||||
|
(uint2)(0x00000088, 0x00000000), |
||||||
|
(uint2)(0x80008009, 0x00000000), |
||||||
|
(uint2)(0x8000000a, 0x00000000), |
||||||
|
(uint2)(0x8000808b, 0x00000000), |
||||||
|
(uint2)(0x0000008b, 0x80000000), |
||||||
|
(uint2)(0x00008089, 0x80000000), |
||||||
|
(uint2)(0x00008003, 0x80000000), |
||||||
|
(uint2)(0x00008002, 0x80000000), |
||||||
|
(uint2)(0x00000080, 0x80000000), |
||||||
|
(uint2)(0x0000800a, 0x00000000), |
||||||
|
(uint2)(0x8000000a, 0x80000000), |
||||||
|
(uint2)(0x80008081, 0x80000000), |
||||||
|
(uint2)(0x00008080, 0x80000000), |
||||||
|
(uint2)(0x80000001, 0x00000000), |
||||||
|
(uint2)(0x80008008, 0x80000000), |
||||||
|
}; |
||||||
|
|
||||||
|
void keccak_f1600_round(uint2* a, uint r, uint out_size) |
||||||
|
{ |
||||||
|
#if !__ENDIAN_LITTLE__ |
||||||
|
for (uint i = 0; i != 25; ++i) |
||||||
|
a[i] = a[i].yx; |
||||||
|
#endif |
||||||
|
|
||||||
|
uint2 b[25]; |
||||||
|
uint2 t; |
||||||
|
|
||||||
|
// Theta |
||||||
|
b[0] = a[0] ^ a[5] ^ a[10] ^ a[15] ^ a[20]; |
||||||
|
b[1] = a[1] ^ a[6] ^ a[11] ^ a[16] ^ a[21]; |
||||||
|
b[2] = a[2] ^ a[7] ^ a[12] ^ a[17] ^ a[22]; |
||||||
|
b[3] = a[3] ^ a[8] ^ a[13] ^ a[18] ^ a[23]; |
||||||
|
b[4] = a[4] ^ a[9] ^ a[14] ^ a[19] ^ a[24]; |
||||||
|
t = b[4] ^ (uint2)(b[1].x << 1 | b[1].y >> 31, b[1].y << 1 | b[1].x >> 31); |
||||||
|
a[0] ^= t; |
||||||
|
a[5] ^= t; |
||||||
|
a[10] ^= t; |
||||||
|
a[15] ^= t; |
||||||
|
a[20] ^= t; |
||||||
|
t = b[0] ^ (uint2)(b[2].x << 1 | b[2].y >> 31, b[2].y << 1 | b[2].x >> 31); |
||||||
|
a[1] ^= t; |
||||||
|
a[6] ^= t; |
||||||
|
a[11] ^= t; |
||||||
|
a[16] ^= t; |
||||||
|
a[21] ^= t; |
||||||
|
t = b[1] ^ (uint2)(b[3].x << 1 | b[3].y >> 31, b[3].y << 1 | b[3].x >> 31); |
||||||
|
a[2] ^= t; |
||||||
|
a[7] ^= t; |
||||||
|
a[12] ^= t; |
||||||
|
a[17] ^= t; |
||||||
|
a[22] ^= t; |
||||||
|
t = b[2] ^ (uint2)(b[4].x << 1 | b[4].y >> 31, b[4].y << 1 | b[4].x >> 31); |
||||||
|
a[3] ^= t; |
||||||
|
a[8] ^= t; |
||||||
|
a[13] ^= t; |
||||||
|
a[18] ^= t; |
||||||
|
a[23] ^= t; |
||||||
|
t = b[3] ^ (uint2)(b[0].x << 1 | b[0].y >> 31, b[0].y << 1 | b[0].x >> 31); |
||||||
|
a[4] ^= t; |
||||||
|
a[9] ^= t; |
||||||
|
a[14] ^= t; |
||||||
|
a[19] ^= t; |
||||||
|
a[24] ^= t; |
||||||
|
|
||||||
|
// Rho Pi |
||||||
|
b[0] = a[0]; |
||||||
|
b[10] = (uint2)(a[1].x << 1 | a[1].y >> 31, a[1].y << 1 | a[1].x >> 31); |
||||||
|
b[7] = (uint2)(a[10].x << 3 | a[10].y >> 29, a[10].y << 3 | a[10].x >> 29); |
||||||
|
b[11] = (uint2)(a[7].x << 6 | a[7].y >> 26, a[7].y << 6 | a[7].x >> 26); |
||||||
|
b[17] = (uint2)(a[11].x << 10 | a[11].y >> 22, a[11].y << 10 | a[11].x >> 22); |
||||||
|
b[18] = (uint2)(a[17].x << 15 | a[17].y >> 17, a[17].y << 15 | a[17].x >> 17); |
||||||
|
b[3] = (uint2)(a[18].x << 21 | a[18].y >> 11, a[18].y << 21 | a[18].x >> 11); |
||||||
|
b[5] = (uint2)(a[3].x << 28 | a[3].y >> 4, a[3].y << 28 | a[3].x >> 4); |
||||||
|
b[16] = (uint2)(a[5].y << 4 | a[5].x >> 28, a[5].x << 4 | a[5].y >> 28); |
||||||
|
b[8] = (uint2)(a[16].y << 13 | a[16].x >> 19, a[16].x << 13 | a[16].y >> 19); |
||||||
|
b[21] = (uint2)(a[8].y << 23 | a[8].x >> 9, a[8].x << 23 | a[8].y >> 9); |
||||||
|
b[24] = (uint2)(a[21].x << 2 | a[21].y >> 30, a[21].y << 2 | a[21].x >> 30); |
||||||
|
b[4] = (uint2)(a[24].x << 14 | a[24].y >> 18, a[24].y << 14 | a[24].x >> 18); |
||||||
|
b[15] = (uint2)(a[4].x << 27 | a[4].y >> 5, a[4].y << 27 | a[4].x >> 5); |
||||||
|
b[23] = (uint2)(a[15].y << 9 | a[15].x >> 23, a[15].x << 9 | a[15].y >> 23); |
||||||
|
b[19] = (uint2)(a[23].y << 24 | a[23].x >> 8, a[23].x << 24 | a[23].y >> 8); |
||||||
|
b[13] = (uint2)(a[19].x << 8 | a[19].y >> 24, a[19].y << 8 | a[19].x >> 24); |
||||||
|
b[12] = (uint2)(a[13].x << 25 | a[13].y >> 7, a[13].y << 25 | a[13].x >> 7); |
||||||
|
b[2] = (uint2)(a[12].y << 11 | a[12].x >> 21, a[12].x << 11 | a[12].y >> 21); |
||||||
|
b[20] = (uint2)(a[2].y << 30 | a[2].x >> 2, a[2].x << 30 | a[2].y >> 2); |
||||||
|
b[14] = (uint2)(a[20].x << 18 | a[20].y >> 14, a[20].y << 18 | a[20].x >> 14); |
||||||
|
b[22] = (uint2)(a[14].y << 7 | a[14].x >> 25, a[14].x << 7 | a[14].y >> 25); |
||||||
|
b[9] = (uint2)(a[22].y << 29 | a[22].x >> 3, a[22].x << 29 | a[22].y >> 3); |
||||||
|
b[6] = (uint2)(a[9].x << 20 | a[9].y >> 12, a[9].y << 20 | a[9].x >> 12); |
||||||
|
b[1] = (uint2)(a[6].y << 12 | a[6].x >> 20, a[6].x << 12 | a[6].y >> 20); |
||||||
|
|
||||||
|
// Chi |
||||||
|
a[0] = bitselect(b[0] ^ b[2], b[0], b[1]); |
||||||
|
a[1] = bitselect(b[1] ^ b[3], b[1], b[2]); |
||||||
|
a[2] = bitselect(b[2] ^ b[4], b[2], b[3]); |
||||||
|
a[3] = bitselect(b[3] ^ b[0], b[3], b[4]); |
||||||
|
if (out_size >= 4) |
||||||
|
{ |
||||||
|
a[4] = bitselect(b[4] ^ b[1], b[4], b[0]); |
||||||
|
a[5] = bitselect(b[5] ^ b[7], b[5], b[6]); |
||||||
|
a[6] = bitselect(b[6] ^ b[8], b[6], b[7]); |
||||||
|
a[7] = bitselect(b[7] ^ b[9], b[7], b[8]); |
||||||
|
a[8] = bitselect(b[8] ^ b[5], b[8], b[9]); |
||||||
|
if (out_size >= 8) |
||||||
|
{ |
||||||
|
a[9] = bitselect(b[9] ^ b[6], b[9], b[5]); |
||||||
|
a[10] = bitselect(b[10] ^ b[12], b[10], b[11]); |
||||||
|
a[11] = bitselect(b[11] ^ b[13], b[11], b[12]); |
||||||
|
a[12] = bitselect(b[12] ^ b[14], b[12], b[13]); |
||||||
|
a[13] = bitselect(b[13] ^ b[10], b[13], b[14]); |
||||||
|
a[14] = bitselect(b[14] ^ b[11], b[14], b[10]); |
||||||
|
a[15] = bitselect(b[15] ^ b[17], b[15], b[16]); |
||||||
|
a[16] = bitselect(b[16] ^ b[18], b[16], b[17]); |
||||||
|
a[17] = bitselect(b[17] ^ b[19], b[17], b[18]); |
||||||
|
a[18] = bitselect(b[18] ^ b[15], b[18], b[19]); |
||||||
|
a[19] = bitselect(b[19] ^ b[16], b[19], b[15]); |
||||||
|
a[20] = bitselect(b[20] ^ b[22], b[20], b[21]); |
||||||
|
a[21] = bitselect(b[21] ^ b[23], b[21], b[22]); |
||||||
|
a[22] = bitselect(b[22] ^ b[24], b[22], b[23]); |
||||||
|
a[23] = bitselect(b[23] ^ b[20], b[23], b[24]); |
||||||
|
a[24] = bitselect(b[24] ^ b[21], b[24], b[20]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Iota |
||||||
|
a[0] ^= Keccak_f1600_RC[r]; |
||||||
|
|
||||||
|
#if !__ENDIAN_LITTLE__ |
||||||
|
for (uint i = 0; i != 25; ++i) |
||||||
|
a[i] = a[i].yx; |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
void keccak_f1600_no_absorb(ulong* a, uint in_size, uint out_size, uint isolate) |
||||||
|
{ |
||||||
|
for (uint i = in_size; i != 25; ++i) |
||||||
|
{ |
||||||
|
a[i] = 0; |
||||||
|
} |
||||||
|
#if __ENDIAN_LITTLE__ |
||||||
|
a[in_size] ^= 0x0000000000000001; |
||||||
|
a[24-out_size*2] ^= 0x8000000000000000; |
||||||
|
#else |
||||||
|
a[in_size] ^= 0x0100000000000000; |
||||||
|
a[24-out_size*2] ^= 0x0000000000000080; |
||||||
|
#endif |
||||||
|
|
||||||
|
// Originally I unrolled the first and last rounds to interface |
||||||
|
// better with surrounding code, however I haven't done this |
||||||
|
// without causing the AMD compiler to blow up the VGPR usage. |
||||||
|
uint r = 0; |
||||||
|
do |
||||||
|
{ |
||||||
|
// This dynamic branch stops the AMD compiler unrolling the loop |
||||||
|
// and additionally saves about 33% of the VGPRs, enough to gain another |
||||||
|
// wavefront. Ideally we'd get 4 in flight, but 3 is the best I can |
||||||
|
// massage out of the compiler. It doesn't really seem to matter how |
||||||
|
// much we try and help the compiler save VGPRs because it seems to throw |
||||||
|
// that information away, hence the implementation of keccak here |
||||||
|
// doesn't bother. |
||||||
|
if (isolate) |
||||||
|
{ |
||||||
|
keccak_f1600_round((uint2*)a, r++, 25); |
||||||
|
} |
||||||
|
} |
||||||
|
while (r < 23); |
||||||
|
|
||||||
|
// final round optimised for digest size |
||||||
|
keccak_f1600_round((uint2*)a, r++, out_size); |
||||||
|
} |
||||||
|
|
||||||
|
#define copy(dst, src, count) for (uint i = 0; i != count; ++i) { (dst)[i] = (src)[i]; } |
||||||
|
|
||||||
|
#define countof(x) (sizeof(x) / sizeof(x[0])) |
||||||
|
|
||||||
|
uint fnv(uint x, uint y) |
||||||
|
{ |
||||||
|
return x * FNV_PRIME ^ y; |
||||||
|
} |
||||||
|
|
||||||
|
uint4 fnv4(uint4 x, uint4 y) |
||||||
|
{ |
||||||
|
return x * FNV_PRIME ^ y; |
||||||
|
} |
||||||
|
|
||||||
|
uint fnv_reduce(uint4 v) |
||||||
|
{ |
||||||
|
return fnv(fnv(fnv(v.x, v.y), v.z), v.w); |
||||||
|
} |
||||||
|
|
||||||
|
typedef union |
||||||
|
{ |
||||||
|
ulong ulongs[32 / sizeof(ulong)]; |
||||||
|
uint uints[32 / sizeof(uint)]; |
||||||
|
} hash32_t; |
||||||
|
|
||||||
|
typedef union |
||||||
|
{ |
||||||
|
ulong ulongs[64 / sizeof(ulong)]; |
||||||
|
uint4 uint4s[64 / sizeof(uint4)]; |
||||||
|
} hash64_t; |
||||||
|
|
||||||
|
typedef union |
||||||
|
{ |
||||||
|
uint uints[128 / sizeof(uint)]; |
||||||
|
uint4 uint4s[128 / sizeof(uint4)]; |
||||||
|
} hash128_t; |
||||||
|
|
||||||
|
hash64_t init_hash(__constant hash32_t const* header, ulong nonce, uint isolate) |
||||||
|
{ |
||||||
|
hash64_t init; |
||||||
|
uint const init_size = countof(init.ulongs); |
||||||
|
uint const hash_size = countof(header->ulongs); |
||||||
|
|
||||||
|
// sha3_512(header .. nonce) |
||||||
|
ulong state[25]; |
||||||
|
copy(state, header->ulongs, hash_size); |
||||||
|
state[hash_size] = nonce; |
||||||
|
keccak_f1600_no_absorb(state, hash_size + 1, init_size, isolate); |
||||||
|
|
||||||
|
copy(init.ulongs, state, init_size); |
||||||
|
return init; |
||||||
|
} |
||||||
|
|
||||||
|
uint inner_loop(uint4 init, uint thread_id, __local uint* share, __global hash128_t const* g_dag, uint isolate) |
||||||
|
{ |
||||||
|
uint4 mix = init; |
||||||
|
|
||||||
|
// share init0 |
||||||
|
if (thread_id == 0) |
||||||
|
*share = mix.x; |
||||||
|
barrier(CLK_LOCAL_MEM_FENCE); |
||||||
|
uint init0 = *share; |
||||||
|
|
||||||
|
uint a = 0; |
||||||
|
do |
||||||
|
{ |
||||||
|
bool update_share = thread_id == (a/4) % THREADS_PER_HASH; |
||||||
|
|
||||||
|
#pragma unroll |
||||||
|
for (uint i = 0; i != 4; ++i) |
||||||
|
{ |
||||||
|
if (update_share) |
||||||
|
{ |
||||||
|
uint m[4] = { mix.x, mix.y, mix.z, mix.w }; |
||||||
|
*share = fnv(init0 ^ (a+i), m[i]) % DAG_SIZE; |
||||||
|
} |
||||||
|
barrier(CLK_LOCAL_MEM_FENCE); |
||||||
|
|
||||||
|
mix = fnv4(mix, g_dag[*share].uint4s[thread_id]); |
||||||
|
} |
||||||
|
} |
||||||
|
while ((a += 4) != (ACCESSES & isolate)); |
||||||
|
|
||||||
|
return fnv_reduce(mix); |
||||||
|
} |
||||||
|
|
||||||
|
hash32_t final_hash(hash64_t const* init, hash32_t const* mix, uint isolate) |
||||||
|
{ |
||||||
|
ulong state[25]; |
||||||
|
|
||||||
|
hash32_t hash; |
||||||
|
uint const hash_size = countof(hash.ulongs); |
||||||
|
uint const init_size = countof(init->ulongs); |
||||||
|
uint const mix_size = countof(mix->ulongs); |
||||||
|
|
||||||
|
// keccak_256(keccak_512(header..nonce) .. mix); |
||||||
|
copy(state, init->ulongs, init_size); |
||||||
|
copy(state + init_size, mix->ulongs, mix_size); |
||||||
|
keccak_f1600_no_absorb(state, init_size+mix_size, hash_size, isolate); |
||||||
|
|
||||||
|
// copy out |
||||||
|
copy(hash.ulongs, state, hash_size); |
||||||
|
return hash; |
||||||
|
} |
||||||
|
|
||||||
|
hash32_t compute_hash_simple( |
||||||
|
__constant hash32_t const* g_header, |
||||||
|
__global hash128_t const* g_dag, |
||||||
|
ulong nonce, |
||||||
|
uint isolate |
||||||
|
) |
||||||
|
{ |
||||||
|
hash64_t init = init_hash(g_header, nonce, isolate); |
||||||
|
|
||||||
|
hash128_t mix; |
||||||
|
for (uint i = 0; i != countof(mix.uint4s); ++i) |
||||||
|
{ |
||||||
|
mix.uint4s[i] = init.uint4s[i % countof(init.uint4s)]; |
||||||
|
} |
||||||
|
|
||||||
|
uint mix_val = mix.uints[0]; |
||||||
|
uint init0 = mix.uints[0]; |
||||||
|
uint a = 0; |
||||||
|
do |
||||||
|
{ |
||||||
|
uint pi = fnv(init0 ^ a, mix_val) % DAG_SIZE; |
||||||
|
uint n = (a+1) % countof(mix.uints); |
||||||
|
|
||||||
|
#pragma unroll |
||||||
|
for (uint i = 0; i != countof(mix.uints); ++i) |
||||||
|
{ |
||||||
|
mix.uints[i] = fnv(mix.uints[i], g_dag[pi].uints[i]); |
||||||
|
mix_val = i == n ? mix.uints[i] : mix_val; |
||||||
|
} |
||||||
|
} |
||||||
|
while (++a != (ACCESSES & isolate)); |
||||||
|
|
||||||
|
// reduce to output |
||||||
|
hash32_t fnv_mix; |
||||||
|
for (uint i = 0; i != countof(fnv_mix.uints); ++i) |
||||||
|
{ |
||||||
|
fnv_mix.uints[i] = fnv_reduce(mix.uint4s[i]); |
||||||
|
} |
||||||
|
|
||||||
|
return final_hash(&init, &fnv_mix, isolate); |
||||||
|
} |
||||||
|
|
||||||
|
typedef union |
||||||
|
{ |
||||||
|
struct |
||||||
|
{ |
||||||
|
hash64_t init; |
||||||
|
uint pad; // avoid lds bank conflicts |
||||||
|
}; |
||||||
|
hash32_t mix; |
||||||
|
} compute_hash_share; |
||||||
|
|
||||||
|
hash32_t compute_hash( |
||||||
|
__local compute_hash_share* share, |
||||||
|
__constant hash32_t const* g_header, |
||||||
|
__global hash128_t const* g_dag, |
||||||
|
ulong nonce, |
||||||
|
uint isolate |
||||||
|
) |
||||||
|
{ |
||||||
|
uint const gid = get_global_id(0); |
||||||
|
|
||||||
|
// Compute one init hash per work item. |
||||||
|
hash64_t init = init_hash(g_header, nonce, isolate); |
||||||
|
|
||||||
|
// Threads work together in this phase in groups of 8. |
||||||
|
uint const thread_id = gid % THREADS_PER_HASH; |
||||||
|
uint const hash_id = (gid % GROUP_SIZE) / THREADS_PER_HASH; |
||||||
|
|
||||||
|
hash32_t mix; |
||||||
|
uint i = 0; |
||||||
|
do |
||||||
|
{ |
||||||
|
// share init with other threads |
||||||
|
if (i == thread_id) |
||||||
|
share[hash_id].init = init; |
||||||
|
barrier(CLK_LOCAL_MEM_FENCE); |
||||||
|
|
||||||
|
uint4 thread_init = share[hash_id].init.uint4s[thread_id % (64 / sizeof(uint4))]; |
||||||
|
barrier(CLK_LOCAL_MEM_FENCE); |
||||||
|
|
||||||
|
uint thread_mix = inner_loop(thread_init, thread_id, share[hash_id].mix.uints, g_dag, isolate); |
||||||
|
|
||||||
|
share[hash_id].mix.uints[thread_id] = thread_mix; |
||||||
|
barrier(CLK_LOCAL_MEM_FENCE); |
||||||
|
|
||||||
|
if (i == thread_id) |
||||||
|
mix = share[hash_id].mix; |
||||||
|
barrier(CLK_LOCAL_MEM_FENCE); |
||||||
|
} |
||||||
|
while (++i != (THREADS_PER_HASH & isolate)); |
||||||
|
|
||||||
|
return final_hash(&init, &mix, isolate); |
||||||
|
} |
||||||
|
|
||||||
|
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||||
|
__kernel void ethash_hash_simple( |
||||||
|
__global hash32_t* g_hashes, |
||||||
|
__constant hash32_t const* g_header, |
||||||
|
__global hash128_t const* g_dag, |
||||||
|
ulong start_nonce, |
||||||
|
uint isolate |
||||||
|
) |
||||||
|
{ |
||||||
|
uint const gid = get_global_id(0); |
||||||
|
g_hashes[gid] = compute_hash_simple(g_header, g_dag, start_nonce + gid, isolate); |
||||||
|
} |
||||||
|
|
||||||
|
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||||
|
__kernel void ethash_search_simple( |
||||||
|
__global volatile uint* restrict g_output, |
||||||
|
__constant hash32_t const* g_header, |
||||||
|
__global hash128_t const* g_dag, |
||||||
|
ulong start_nonce, |
||||||
|
ulong target, |
||||||
|
uint isolate |
||||||
|
) |
||||||
|
{ |
||||||
|
uint const gid = get_global_id(0); |
||||||
|
hash32_t hash = compute_hash_simple(g_header, g_dag, start_nonce + gid, isolate); |
||||||
|
|
||||||
|
if (hash.ulongs[countof(hash.ulongs)-1] < target) |
||||||
|
{ |
||||||
|
uint slot = min(MAX_OUTPUTS, atomic_inc(&g_output[0]) + 1); |
||||||
|
g_output[slot] = gid; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||||
|
__kernel void ethash_hash( |
||||||
|
__global hash32_t* g_hashes, |
||||||
|
__constant hash32_t const* g_header, |
||||||
|
__global hash128_t const* g_dag, |
||||||
|
ulong start_nonce, |
||||||
|
uint isolate |
||||||
|
) |
||||||
|
{ |
||||||
|
__local compute_hash_share share[HASHES_PER_LOOP]; |
||||||
|
|
||||||
|
uint const gid = get_global_id(0); |
||||||
|
g_hashes[gid] = compute_hash(share, g_header, g_dag, start_nonce + gid, isolate); |
||||||
|
} |
||||||
|
|
||||||
|
__attribute__((reqd_work_group_size(GROUP_SIZE, 1, 1))) |
||||||
|
__kernel void ethash_search( |
||||||
|
__global volatile uint* restrict g_output, |
||||||
|
__constant hash32_t const* g_header, |
||||||
|
__global hash128_t const* g_dag, |
||||||
|
ulong start_nonce, |
||||||
|
ulong target, |
||||||
|
uint isolate |
||||||
|
) |
||||||
|
{ |
||||||
|
__local compute_hash_share share[HASHES_PER_LOOP]; |
||||||
|
|
||||||
|
uint const gid = get_global_id(0); |
||||||
|
hash32_t hash = compute_hash(share, g_header, g_dag, start_nonce + gid, isolate); |
||||||
|
|
||||||
|
if (hash.ulongs[countof(hash.ulongs)-1] < target) |
||||||
|
{ |
||||||
|
uint slot = min(MAX_OUTPUTS, atomic_inc(&g_output[0]) + 1); |
||||||
|
g_output[slot] = gid; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
set(LIBRARY ethash) |
||||||
|
|
||||||
|
if (CPPETHEREUM) |
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") |
||||||
|
#else () |
||||||
|
endif () |
||||||
|
|
||||||
|
set(CMAKE_BUILD_TYPE Release) |
||||||
|
|
||||||
|
if (NOT MSVC) |
||||||
|
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") |
||||||
|
endif() |
||||||
|
|
||||||
|
set(FILES util.c |
||||||
|
util.h |
||||||
|
internal.c |
||||||
|
ethash.h |
||||||
|
endian.h |
||||||
|
compiler.h |
||||||
|
fnv.h |
||||||
|
data_sizes.h) |
||||||
|
|
||||||
|
if (NOT CRYPTOPP_FOUND) |
||||||
|
find_package(CryptoPP 5.6.2) |
||||||
|
endif() |
||||||
|
|
||||||
|
if (CRYPTOPP_FOUND) |
||||||
|
add_definitions(-DWITH_CRYPTOPP) |
||||||
|
include_directories( ${CRYPTOPP_INCLUDE_DIRS} ) |
||||||
|
list(APPEND FILES sha3_cryptopp.cpp sha3_cryptopp.h) |
||||||
|
else() |
||||||
|
list(APPEND FILES sha3.c sha3.h) |
||||||
|
endif() |
||||||
|
|
||||||
|
add_library(${LIBRARY} ${FILES}) |
||||||
|
|
||||||
|
if (CRYPTOPP_FOUND) |
||||||
|
TARGET_LINK_LIBRARIES(${LIBRARY} ${CRYPTOPP_LIBRARIES}) |
||||||
|
endif() |
@ -0,0 +1,33 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
/** @file compiler.h
|
||||||
|
* @date 2014 |
||||||
|
*/ |
||||||
|
#pragma once |
||||||
|
|
||||||
|
// Visual Studio doesn't support the inline keyword in C mode
|
||||||
|
#if defined(_MSC_VER) && !defined(__cplusplus) |
||||||
|
#define inline __inline |
||||||
|
#endif |
||||||
|
|
||||||
|
// pretend restrict is a standard keyword
|
||||||
|
#if defined(_MSC_VER) |
||||||
|
#define restrict __restrict |
||||||
|
#else |
||||||
|
#define restrict __restrict__ |
||||||
|
#endif |
||||||
|
|
@ -0,0 +1,779 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software FoundationUUU,either version 3 of the LicenseUUU,or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum is distributed in the hope that it will be usefulU, |
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
GNU General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If notUUU,see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
/** @file data_sizes.h
|
||||||
|
* @author Matthew Wampler-Doty <negacthulhu@gmail.com> |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
|
||||||
|
// TODO: Update this after ~3.5 years
|
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include "compiler.h" |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
// 2048 Epochs worth of tabulated DAG sizes
|
||||||
|
|
||||||
|
// Generated with the following Mathematica Code:
|
||||||
|
|
||||||
|
// GetDataSizes[n_] := Module[{
|
||||||
|
// DataSetSizeBytesInit = 2^30,
|
||||||
|
// MixBytes = 128,
|
||||||
|
// DataSetGrowth = 2^23,
|
||||||
|
// j = 0},
|
||||||
|
// Reap[
|
||||||
|
// While[j < n,
|
||||||
|
// Module[{i =
|
||||||
|
// Floor[(DataSetSizeBytesInit + DataSetGrowth * j) / MixBytes]},
|
||||||
|
// While[! PrimeQ[i], i--];
|
||||||
|
// Sow[i*MixBytes]; j++]]]][[2]][[1]]
|
||||||
|
|
||||||
|
static const size_t dag_sizes[] = { |
||||||
|
1073739904U, 1082130304U, 1090514816U, 1098906752U, 1107293056U, |
||||||
|
1115684224U, 1124070016U, 1132461952U, 1140849536U, 1149232768U, |
||||||
|
1157627776U, 1166013824U, 1174404736U, 1182786944U, 1191180416U, |
||||||
|
1199568512U, 1207958912U, 1216345216U, 1224732032U, 1233124736U, |
||||||
|
1241513344U, 1249902464U, 1258290304U, 1266673792U, 1275067264U, |
||||||
|
1283453312U, 1291844992U, 1300234112U, 1308619904U, 1317010048U, |
||||||
|
1325397376U, 1333787776U, 1342176128U, 1350561664U, 1358954368U, |
||||||
|
1367339392U, 1375731584U, 1384118144U, 1392507008U, 1400897408U, |
||||||
|
1409284736U, 1417673344U, 1426062464U, 1434451072U, 1442839168U, |
||||||
|
1451229056U, 1459615616U, 1468006016U, 1476394112U, 1484782976U, |
||||||
|
1493171584U, 1501559168U, 1509948032U, 1518337664U, 1526726528U, |
||||||
|
1535114624U, 1543503488U, 1551892096U, 1560278656U, 1568669056U, |
||||||
|
1577056384U, 1585446272U, 1593831296U, 1602219392U, 1610610304U, |
||||||
|
1619000192U, 1627386752U, 1635773824U, 1644164224U, 1652555648U, |
||||||
|
1660943488U, 1669332608U, 1677721216U, 1686109312U, 1694497664U, |
||||||
|
1702886272U, 1711274624U, 1719661184U, 1728047744U, 1736434816U, |
||||||
|
1744829056U, 1753218944U, 1761606272U, 1769995904U, 1778382464U, |
||||||
|
1786772864U, 1795157888U, 1803550592U, 1811937664U, 1820327552U, |
||||||
|
1828711552U, 1837102976U, 1845488768U, 1853879936U, 1862269312U, |
||||||
|
1870656896U, 1879048064U, 1887431552U, 1895825024U, 1904212096U, |
||||||
|
1912601216U, 1920988544U, 1929379456U, 1937765504U, 1946156672U, |
||||||
|
1954543232U, 1962932096U, 1971321728U, 1979707264U, 1988093056U, |
||||||
|
1996487552U, 2004874624U, 2013262208U, 2021653888U, 2030039936U, |
||||||
|
2038430848U, 2046819968U, 2055208576U, 2063596672U, 2071981952U, |
||||||
|
2080373632U, 2088762752U, 2097149056U, 2105539712U, 2113928576U, |
||||||
|
2122315136U, 2130700672U, 2139092608U, 2147483264U, 2155872128U, |
||||||
|
2164257664U, 2172642176U, 2181035392U, 2189426048U, 2197814912U, |
||||||
|
2206203008U, 2214587264U, 2222979712U, 2231367808U, 2239758208U, |
||||||
|
2248145024U, 2256527744U, 2264922752U, 2273312128U, 2281701248U, |
||||||
|
2290086272U, 2298476672U, 2306867072U, 2315251072U, 2323639168U, |
||||||
|
2332032128U, 2340420224U, 2348808064U, 2357196416U, 2365580416U, |
||||||
|
2373966976U, 2382363008U, 2390748544U, 2399139968U, 2407530368U, |
||||||
|
2415918976U, 2424307328U, 2432695424U, 2441084288U, 2449472384U, |
||||||
|
2457861248U, 2466247808U, 2474637184U, 2483026816U, 2491414144U, |
||||||
|
2499803776U, 2508191872U, 2516582272U, 2524970368U, 2533359232U, |
||||||
|
2541743488U, 2550134144U, 2558525056U, 2566913408U, 2575301504U, |
||||||
|
2583686528U, 2592073856U, 2600467328U, 2608856192U, 2617240448U, |
||||||
|
2625631616U, 2634022016U, 2642407552U, 2650796416U, 2659188352U, |
||||||
|
2667574912U, 2675965312U, 2684352896U, 2692738688U, 2701130624U, |
||||||
|
2709518464U, 2717907328U, 2726293376U, 2734685056U, 2743073152U, |
||||||
|
2751462016U, 2759851648U, 2768232832U, 2776625536U, 2785017728U, |
||||||
|
2793401984U, 2801794432U, 2810182016U, 2818571648U, 2826959488U, |
||||||
|
2835349376U, 2843734144U, 2852121472U, 2860514432U, 2868900992U, |
||||||
|
2877286784U, 2885676928U, 2894069632U, 2902451584U, 2910843008U, |
||||||
|
2919234688U, 2927622784U, 2936011648U, 2944400768U, 2952789376U, |
||||||
|
2961177728U, 2969565568U, 2977951616U, 2986338944U, 2994731392U, |
||||||
|
3003120256U, 3011508352U, 3019895936U, 3028287104U, 3036675968U, |
||||||
|
3045063808U, 3053452928U, 3061837696U, 3070228352U, 3078615424U, |
||||||
|
3087003776U, 3095394944U, 3103782272U, 3112173184U, 3120562048U, |
||||||
|
3128944768U, 3137339264U, 3145725056U, 3154109312U, 3162505088U, |
||||||
|
3170893184U, 3179280256U, 3187669376U, 3196056704U, 3204445568U, |
||||||
|
3212836736U, 3221224064U, 3229612928U, 3238002304U, 3246391168U, |
||||||
|
3254778496U, 3263165824U, 3271556224U, 3279944576U, 3288332416U, |
||||||
|
3296719232U, 3305110912U, 3313500032U, 3321887104U, 3330273152U, |
||||||
|
3338658944U, 3347053184U, 3355440512U, 3363827072U, 3372220288U, |
||||||
|
3380608384U, 3388997504U, 3397384576U, 3405774208U, 3414163072U, |
||||||
|
3422551936U, 3430937984U, 3439328384U, 3447714176U, 3456104576U, |
||||||
|
3464493952U, 3472883584U, 3481268864U, 3489655168U, 3498048896U, |
||||||
|
3506434432U, 3514826368U, 3523213952U, 3531603584U, 3539987072U, |
||||||
|
3548380288U, 3556763264U, 3565157248U, 3573545344U, 3581934464U, |
||||||
|
3590324096U, 3598712704U, 3607098752U, 3615488384U, 3623877248U, |
||||||
|
3632265856U, 3640646528U, 3649043584U, 3657430144U, 3665821568U, |
||||||
|
3674207872U, 3682597504U, 3690984832U, 3699367808U, 3707764352U, |
||||||
|
3716152448U, 3724541056U, 3732925568U, 3741318016U, 3749706368U, |
||||||
|
3758091136U, 3766481536U, 3774872704U, 3783260032U, 3791650432U, |
||||||
|
3800036224U, 3808427648U, 3816815488U, 3825204608U, 3833592704U, |
||||||
|
3841981568U, 3850370432U, 3858755968U, 3867147904U, 3875536256U, |
||||||
|
3883920512U, 3892313728U, 3900702592U, 3909087872U, 3917478784U, |
||||||
|
3925868416U, 3934256512U, 3942645376U, 3951032192U, 3959422336U, |
||||||
|
3967809152U, 3976200064U, 3984588416U, 3992974976U, 4001363584U, |
||||||
|
4009751168U, 4018141312U, 4026530432U, 4034911616U, 4043308928U, |
||||||
|
4051695488U, 4060084352U, 4068472448U, 4076862848U, 4085249408U, |
||||||
|
4093640576U, 4102028416U, 4110413696U, 4118805632U, 4127194496U, |
||||||
|
4135583104U, 4143971968U, 4152360832U, 4160746112U, 4169135744U, |
||||||
|
4177525888U, 4185912704U, 4194303616U, 4202691968U, 4211076736U, |
||||||
|
4219463552U, 4227855488U, 4236246656U, 4244633728U, 4253022848U, |
||||||
|
4261412224U, 4269799808U, 4278184832U, 4286578048U, 4294962304U, |
||||||
|
4303349632U, 4311743104U, 4320130432U, 4328521088U, 4336909184U, |
||||||
|
4345295488U, 4353687424U, 4362073472U, 4370458496U, 4378852736U, |
||||||
|
4387238528U, 4395630208U, 4404019072U, 4412407424U, 4420790656U, |
||||||
|
4429182848U, 4437571456U, 4445962112U, 4454344064U, 4462738048U, |
||||||
|
4471119232U, 4479516544U, 4487904128U, 4496289664U, 4504682368U, |
||||||
|
4513068416U, 4521459584U, 4529846144U, 4538232704U, 4546619776U, |
||||||
|
4555010176U, 4563402112U, 4571790208U, 4580174464U, 4588567936U, |
||||||
|
4596957056U, 4605344896U, 4613734016U, 4622119808U, 4630511488U, |
||||||
|
4638898816U, 4647287936U, 4655675264U, 4664065664U, 4672451968U, |
||||||
|
4680842624U, 4689231488U, 4697620352U, 4706007424U, 4714397056U, |
||||||
|
4722786176U, 4731173248U, 4739562368U, 4747951744U, 4756340608U, |
||||||
|
4764727936U, 4773114496U, 4781504384U, 4789894784U, 4798283648U, |
||||||
|
4806667648U, 4815059584U, 4823449472U, 4831835776U, 4840226176U, |
||||||
|
4848612224U, 4857003392U, 4865391488U, 4873780096U, 4882169728U, |
||||||
|
4890557312U, 4898946944U, 4907333248U, 4915722368U, 4924110976U, |
||||||
|
4932499328U, 4940889728U, 4949276032U, 4957666432U, 4966054784U, |
||||||
|
4974438016U, 4982831488U, 4991221376U, 4999607168U, 5007998848U, |
||||||
|
5016386432U, 5024763776U, 5033164672U, 5041544576U, 5049941888U, |
||||||
|
5058329728U, 5066717056U, 5075107456U, 5083494272U, 5091883904U, |
||||||
|
5100273536U, 5108662144U, 5117048192U, 5125436032U, 5133827456U, |
||||||
|
5142215296U, 5150605184U, 5158993024U, 5167382144U, 5175769472U, |
||||||
|
5184157568U, 5192543872U, 5200936064U, 5209324928U, 5217711232U, |
||||||
|
5226102656U, 5234490496U, 5242877312U, 5251263872U, 5259654016U, |
||||||
|
5268040832U, 5276434304U, 5284819328U, 5293209728U, 5301598592U, |
||||||
|
5309986688U, 5318374784U, 5326764416U, 5335151488U, 5343542144U, |
||||||
|
5351929472U, 5360319872U, 5368706944U, 5377096576U, 5385484928U, |
||||||
|
5393871232U, 5402263424U, 5410650496U, 5419040384U, 5427426944U, |
||||||
|
5435816576U, 5444205952U, 5452594816U, 5460981376U, 5469367936U, |
||||||
|
5477760896U, 5486148736U, 5494536832U, 5502925952U, 5511315328U, |
||||||
|
5519703424U, 5528089984U, 5536481152U, 5544869504U, 5553256064U, |
||||||
|
5561645696U, 5570032768U, 5578423936U, 5586811264U, 5595193216U, |
||||||
|
5603585408U, 5611972736U, 5620366208U, 5628750464U, 5637143936U, |
||||||
|
5645528192U, 5653921408U, 5662310272U, 5670694784U, 5679082624U, |
||||||
|
5687474048U, 5695864448U, 5704251008U, 5712641408U, 5721030272U, |
||||||
|
5729416832U, 5737806208U, 5746194304U, 5754583936U, 5762969984U, |
||||||
|
5771358592U, 5779748224U, 5788137856U, 5796527488U, 5804911232U, |
||||||
|
5813300608U, 5821692544U, 5830082176U, 5838468992U, 5846855552U, |
||||||
|
5855247488U, 5863636096U, 5872024448U, 5880411008U, 5888799872U, |
||||||
|
5897186432U, 5905576832U, 5913966976U, 5922352768U, 5930744704U, |
||||||
|
5939132288U, 5947522432U, 5955911296U, 5964299392U, 5972688256U, |
||||||
|
5981074304U, 5989465472U, 5997851008U, 6006241408U, 6014627968U, |
||||||
|
6023015552U, 6031408256U, 6039796096U, 6048185216U, 6056574848U, |
||||||
|
6064963456U, 6073351808U, 6081736064U, 6090128768U, 6098517632U, |
||||||
|
6106906496U, 6115289216U, 6123680896U, 6132070016U, 6140459648U, |
||||||
|
6148849024U, 6157237376U, 6165624704U, 6174009728U, 6182403712U, |
||||||
|
6190792064U, 6199176064U, 6207569792U, 6215952256U, 6224345216U, |
||||||
|
6232732544U, 6241124224U, 6249510272U, 6257899136U, 6266287744U, |
||||||
|
6274676864U, 6283065728U, 6291454336U, 6299843456U, 6308232064U, |
||||||
|
6316620928U, 6325006208U, 6333395584U, 6341784704U, 6350174848U, |
||||||
|
6358562176U, 6366951296U, 6375337856U, 6383729536U, 6392119168U, |
||||||
|
6400504192U, 6408895616U, 6417283456U, 6425673344U, 6434059136U, |
||||||
|
6442444672U, 6450837376U, 6459223424U, 6467613056U, 6476004224U, |
||||||
|
6484393088U, 6492781952U, 6501170048U, 6509555072U, 6517947008U, |
||||||
|
6526336384U, 6534725504U, 6543112832U, 6551500672U, 6559888768U, |
||||||
|
6568278656U, 6576662912U, 6585055616U, 6593443456U, 6601834112U, |
||||||
|
6610219648U, 6618610304U, 6626999168U, 6635385472U, 6643777408U, |
||||||
|
6652164224U, 6660552832U, 6668941952U, 6677330048U, 6685719424U, |
||||||
|
6694107776U, 6702493568U, 6710882176U, 6719274112U, 6727662976U, |
||||||
|
6736052096U, 6744437632U, 6752825984U, 6761213824U, 6769604224U, |
||||||
|
6777993856U, 6786383488U, 6794770816U, 6803158144U, 6811549312U, |
||||||
|
6819937664U, 6828326528U, 6836706176U, 6845101696U, 6853491328U, |
||||||
|
6861880448U, 6870269312U, 6878655104U, 6887046272U, 6895433344U, |
||||||
|
6903822208U, 6912212864U, 6920596864U, 6928988288U, 6937377152U, |
||||||
|
6945764992U, 6954149248U, 6962544256U, 6970928768U, 6979317376U, |
||||||
|
6987709312U, 6996093824U, 7004487296U, 7012875392U, 7021258624U, |
||||||
|
7029652352U, 7038038912U, 7046427776U, 7054818944U, 7063207808U, |
||||||
|
7071595136U, 7079980928U, 7088372608U, 7096759424U, 7105149824U, |
||||||
|
7113536896U, 7121928064U, 7130315392U, 7138699648U, 7147092352U, |
||||||
|
7155479168U, 7163865728U, 7172249984U, 7180648064U, 7189036672U, |
||||||
|
7197424768U, 7205810816U, 7214196608U, 7222589824U, 7230975104U, |
||||||
|
7239367552U, 7247755904U, 7256145536U, 7264533376U, 7272921472U, |
||||||
|
7281308032U, 7289694848U, 7298088832U, 7306471808U, 7314864512U, |
||||||
|
7323253888U, 7331643008U, 7340029568U, 7348419712U, 7356808832U, |
||||||
|
7365196672U, 7373585792U, 7381973888U, 7390362752U, 7398750592U, |
||||||
|
7407138944U, 7415528576U, 7423915648U, 7432302208U, 7440690304U, |
||||||
|
7449080192U, 7457472128U, 7465860992U, 7474249088U, 7482635648U, |
||||||
|
7491023744U, 7499412608U, 7507803008U, 7516192384U, 7524579968U, |
||||||
|
7532967296U, 7541358464U, 7549745792U, 7558134656U, 7566524032U, |
||||||
|
7574912896U, 7583300992U, 7591690112U, 7600075136U, 7608466816U, |
||||||
|
7616854912U, 7625244544U, 7633629824U, 7642020992U, 7650410368U, |
||||||
|
7658794112U, 7667187328U, 7675574912U, 7683961984U, 7692349568U, |
||||||
|
7700739712U, 7709130368U, 7717519232U, 7725905536U, 7734295424U, |
||||||
|
7742683264U, 7751069056U, 7759457408U, 7767849088U, 7776238208U, |
||||||
|
7784626816U, 7793014912U, 7801405312U, 7809792128U, 7818179968U, |
||||||
|
7826571136U, 7834957184U, 7843347328U, 7851732352U, 7860124544U, |
||||||
|
7868512384U, 7876902016U, 7885287808U, 7893679744U, 7902067072U, |
||||||
|
7910455936U, 7918844288U, 7927230848U, 7935622784U, 7944009344U, |
||||||
|
7952400256U, 7960786048U, 7969176704U, 7977565312U, 7985953408U, |
||||||
|
7994339968U, 8002730368U, 8011119488U, 8019508096U, 8027896192U, |
||||||
|
8036285056U, 8044674688U, 8053062272U, 8061448832U, 8069838464U, |
||||||
|
8078227328U, 8086616704U, 8095006592U, 8103393664U, 8111783552U, |
||||||
|
8120171392U, 8128560256U, 8136949376U, 8145336704U, 8153726848U, |
||||||
|
8162114944U, 8170503296U, 8178891904U, 8187280768U, 8195669632U, |
||||||
|
8204058496U, 8212444544U, 8220834176U, 8229222272U, 8237612672U, |
||||||
|
8246000768U, 8254389376U, 8262775168U, 8271167104U, 8279553664U, |
||||||
|
8287944064U, 8296333184U, 8304715136U, 8313108352U, 8321497984U, |
||||||
|
8329885568U, 8338274432U, 8346663296U, 8355052928U, 8363441536U, |
||||||
|
8371828352U, 8380217984U, 8388606592U, 8396996224U, 8405384576U, |
||||||
|
8413772672U, 8422161536U, 8430549376U, 8438939008U, 8447326592U, |
||||||
|
8455715456U, 8464104832U, 8472492928U, 8480882048U, 8489270656U, |
||||||
|
8497659776U, 8506045312U, 8514434944U, 8522823808U, 8531208832U, |
||||||
|
8539602304U, 8547990656U, 8556378752U, 8564768384U, 8573154176U, |
||||||
|
8581542784U, 8589933952U, 8598322816U, 8606705024U, 8615099264U, |
||||||
|
8623487872U, 8631876992U, 8640264064U, 8648653952U, 8657040256U, |
||||||
|
8665430656U, 8673820544U, 8682209152U, 8690592128U, 8698977152U, |
||||||
|
8707374464U, 8715763328U, 8724151424U, 8732540032U, 8740928384U, |
||||||
|
8749315712U, 8757704576U, 8766089344U, 8774480768U, 8782871936U, |
||||||
|
8791260032U, 8799645824U, 8808034432U, 8816426368U, 8824812928U, |
||||||
|
8833199488U, 8841591424U, 8849976448U, 8858366336U, 8866757248U, |
||||||
|
8875147136U, 8883532928U, 8891923328U, 8900306816U, 8908700288U, |
||||||
|
8917088384U, 8925478784U, 8933867392U, 8942250368U, 8950644608U, |
||||||
|
8959032704U, 8967420544U, 8975809664U, 8984197504U, 8992584064U, |
||||||
|
9000976256U, 9009362048U, 9017752448U, 9026141312U, 9034530688U, |
||||||
|
9042917504U, 9051307904U, 9059694208U, 9068084864U, 9076471424U, |
||||||
|
9084861824U, 9093250688U, 9101638528U, 9110027648U, 9118416512U, |
||||||
|
9126803584U, 9135188096U, 9143581312U, 9151969664U, 9160356224U, |
||||||
|
9168747136U, 9177134464U, 9185525632U, 9193910144U, 9202302848U, |
||||||
|
9210690688U, 9219079552U, 9227465344U, 9235854464U, 9244244864U, |
||||||
|
9252633472U, 9261021824U, 9269411456U, 9277799296U, 9286188928U, |
||||||
|
9294574208U, 9302965888U, 9311351936U, 9319740032U, 9328131968U, |
||||||
|
9336516736U, 9344907392U, 9353296768U, 9361685888U, 9370074752U, |
||||||
|
9378463616U, 9386849408U, 9395239808U, 9403629184U, 9412016512U, |
||||||
|
9420405376U, 9428795008U, 9437181568U, 9445570688U, 9453960832U, |
||||||
|
9462346624U, 9470738048U, 9479121536U, 9487515008U, 9495903616U, |
||||||
|
9504289664U, 9512678528U, 9521067904U, 9529456256U, 9537843584U, |
||||||
|
9546233728U, 9554621312U, 9563011456U, 9571398784U, 9579788672U, |
||||||
|
9588178304U, 9596567168U, 9604954496U, 9613343104U, 9621732992U, |
||||||
|
9630121856U, 9638508416U, 9646898816U, 9655283584U, 9663675776U, |
||||||
|
9672061312U, 9680449664U, 9688840064U, 9697230464U, 9705617536U, |
||||||
|
9714003584U, 9722393984U, 9730772608U, 9739172224U, 9747561088U, |
||||||
|
9755945344U, 9764338816U, 9772726144U, 9781116544U, 9789503872U, |
||||||
|
9797892992U, 9806282624U, 9814670464U, 9823056512U, 9831439232U, |
||||||
|
9839833984U, 9848224384U, 9856613504U, 9865000576U, 9873391232U, |
||||||
|
9881772416U, 9890162816U, 9898556288U, 9906940544U, 9915333248U, |
||||||
|
9923721088U, 9932108672U, 9940496512U, 9948888448U, 9957276544U, |
||||||
|
9965666176U, 9974048384U, 9982441088U, 9990830464U, 9999219584U, |
||||||
|
10007602816U, 10015996544U, 10024385152U, 10032774016U, 10041163648U, |
||||||
|
10049548928U, 10057940096U, 10066329472U, 10074717824U, 10083105152U, |
||||||
|
10091495296U, 10099878784U, 10108272256U, 10116660608U, 10125049216U, |
||||||
|
10133437312U, 10141825664U, 10150213504U, 10158601088U, 10166991232U, |
||||||
|
10175378816U, 10183766144U, 10192157312U, 10200545408U, 10208935552U, |
||||||
|
10217322112U, 10225712768U, 10234099328U, 10242489472U, 10250876032U, |
||||||
|
10259264896U, 10267656064U, 10276042624U, 10284429184U, 10292820352U, |
||||||
|
10301209472U, 10309598848U, 10317987712U, 10326375296U, 10334763392U, |
||||||
|
10343153536U, 10351541632U, 10359930752U, 10368318592U, 10376707456U, |
||||||
|
10385096576U, 10393484672U, 10401867136U, 10410262144U, 10418647424U, |
||||||
|
10427039104U, 10435425664U, 10443810176U, 10452203648U, 10460589952U, |
||||||
|
10468982144U, 10477369472U, 10485759104U, 10494147712U, 10502533504U, |
||||||
|
10510923392U, 10519313536U, 10527702656U, 10536091264U, 10544478592U, |
||||||
|
10552867712U, 10561255808U, 10569642368U, 10578032768U, 10586423168U, |
||||||
|
10594805632U, 10603200128U, 10611588992U, 10619976064U, 10628361344U, |
||||||
|
10636754048U, 10645143424U, 10653531776U, 10661920384U, 10670307968U, |
||||||
|
10678696832U, 10687086464U, 10695475072U, 10703863168U, 10712246144U, |
||||||
|
10720639616U, 10729026688U, 10737414784U, 10745806208U, 10754190976U, |
||||||
|
10762581376U, 10770971264U, 10779356288U, 10787747456U, 10796135552U, |
||||||
|
10804525184U, 10812915584U, 10821301888U, 10829692288U, 10838078336U, |
||||||
|
10846469248U, 10854858368U, 10863247232U, 10871631488U, 10880023424U, |
||||||
|
10888412032U, 10896799616U, 10905188992U, 10913574016U, 10921964672U, |
||||||
|
10930352768U, 10938742912U, 10947132544U, 10955518592U, 10963909504U, |
||||||
|
10972298368U, 10980687488U, 10989074816U, 10997462912U, 11005851776U, |
||||||
|
11014241152U, 11022627712U, 11031017344U, 11039403904U, 11047793024U, |
||||||
|
11056184704U, 11064570752U, 11072960896U, 11081343872U, 11089737856U, |
||||||
|
11098128256U, 11106514816U, 11114904448U, 11123293568U, 11131680128U, |
||||||
|
11140065152U, 11148458368U, 11156845696U, 11165236864U, 11173624192U, |
||||||
|
11182013824U, 11190402688U, 11198790784U, 11207179136U, 11215568768U, |
||||||
|
11223957376U, 11232345728U, 11240734592U, 11249122688U, 11257511296U, |
||||||
|
11265899648U, 11274285952U, 11282675584U, 11291065472U, 11299452544U, |
||||||
|
11307842432U, 11316231296U, 11324616832U, 11333009024U, 11341395584U, |
||||||
|
11349782656U, 11358172288U, 11366560384U, 11374950016U, 11383339648U, |
||||||
|
11391721856U, 11400117376U, 11408504192U, 11416893568U, 11425283456U, |
||||||
|
11433671552U, 11442061184U, 11450444672U, 11458837888U, 11467226752U, |
||||||
|
11475611776U, 11484003968U, 11492392064U, 11500780672U, 11509169024U, |
||||||
|
11517550976U, 11525944448U, 11534335616U, 11542724224U, 11551111808U, |
||||||
|
11559500672U, 11567890304U, 11576277376U, 11584667008U, 11593056128U, |
||||||
|
11601443456U, 11609830016U, 11618221952U, 11626607488U, 11634995072U, |
||||||
|
11643387776U, 11651775104U, 11660161664U, 11668552576U, 11676940928U, |
||||||
|
11685330304U, 11693718656U, 11702106496U, 11710496128U, 11718882688U, |
||||||
|
11727273088U, 11735660416U, 11744050048U, 11752437376U, 11760824704U, |
||||||
|
11769216128U, 11777604736U, 11785991296U, 11794381952U, 11802770048U, |
||||||
|
11811157888U, 11819548544U, 11827932544U, 11836324736U, 11844713344U, |
||||||
|
11853100928U, 11861486464U, 11869879936U, 11878268032U, 11886656896U, |
||||||
|
11895044992U, 11903433088U, 11911822976U, 11920210816U, 11928600448U, |
||||||
|
11936987264U, 11945375872U, 11953761152U, 11962151296U, 11970543488U, |
||||||
|
11978928512U, 11987320448U, 11995708288U, 12004095104U, 12012486272U, |
||||||
|
12020875136U, 12029255552U, 12037652096U, 12046039168U, 12054429568U, |
||||||
|
12062813824U, 12071206528U, 12079594624U, 12087983744U, 12096371072U, |
||||||
|
12104759936U, 12113147264U, 12121534592U, 12129924992U, 12138314624U, |
||||||
|
12146703232U, 12155091584U, 12163481216U, 12171864704U, 12180255872U, |
||||||
|
12188643968U, 12197034112U, 12205424512U, 12213811328U, 12222199424U, |
||||||
|
12230590336U, 12238977664U, 12247365248U, 12255755392U, 12264143488U, |
||||||
|
12272531584U, 12280920448U, 12289309568U, 12297694592U, 12306086528U, |
||||||
|
12314475392U, 12322865024U, 12331253632U, 12339640448U, 12348029312U, |
||||||
|
12356418944U, 12364805248U, 12373196672U, 12381580928U, 12389969024U, |
||||||
|
12398357632U, 12406750592U, 12415138432U, 12423527552U, 12431916416U, |
||||||
|
12440304512U, 12448692352U, 12457081216U, 12465467776U, 12473859968U, |
||||||
|
12482245504U, 12490636672U, 12499025536U, 12507411584U, 12515801728U, |
||||||
|
12524190592U, 12532577152U, 12540966272U, 12549354368U, 12557743232U, |
||||||
|
12566129536U, 12574523264U, 12582911872U, 12591299456U, 12599688064U, |
||||||
|
12608074624U, 12616463488U, 12624845696U, 12633239936U, 12641631616U, |
||||||
|
12650019968U, 12658407296U, 12666795136U, 12675183232U, 12683574656U, |
||||||
|
12691960192U, 12700350592U, 12708740224U, 12717128576U, 12725515904U, |
||||||
|
12733906816U, 12742295168U, 12750680192U, 12759071872U, 12767460736U, |
||||||
|
12775848832U, 12784236928U, 12792626816U, 12801014656U, 12809404288U, |
||||||
|
12817789312U, 12826181504U, 12834568832U, 12842954624U, 12851345792U, |
||||||
|
12859732352U, 12868122496U, 12876512128U, 12884901248U, 12893289088U, |
||||||
|
12901672832U, 12910067584U, 12918455168U, 12926842496U, 12935232896U, |
||||||
|
12943620736U, 12952009856U, 12960396928U, 12968786816U, 12977176192U, |
||||||
|
12985563776U, 12993951104U, 13002341504U, 13010730368U, 13019115392U, |
||||||
|
13027506304U, 13035895168U, 13044272512U, 13052673152U, 13061062528U, |
||||||
|
13069446272U, 13077838976U, 13086227072U, 13094613632U, 13103000192U, |
||||||
|
13111393664U, 13119782528U, 13128157568U, 13136559232U, 13144945024U, |
||||||
|
13153329536U, 13161724288U, 13170111872U, 13178502784U, 13186884736U, |
||||||
|
13195279744U, 13203667072U, 13212057472U, 13220445824U, 13228832128U, |
||||||
|
13237221248U, 13245610624U, 13254000512U, 13262388352U, 13270777472U, |
||||||
|
13279166336U, 13287553408U, 13295943296U, 13304331904U, 13312719488U, |
||||||
|
13321108096U, 13329494656U, 13337885824U, 13346274944U, 13354663808U, |
||||||
|
13363051136U, 13371439232U, 13379825024U, 13388210816U, 13396605056U, |
||||||
|
13404995456U, 13413380224U, 13421771392U, 13430159744U, 13438546048U, |
||||||
|
13446937216U, 13455326848U, 13463708288U, 13472103808U, 13480492672U, |
||||||
|
13488875648U, 13497269888U, 13505657728U, 13514045312U, 13522435712U, |
||||||
|
13530824576U, 13539210112U, 13547599232U, 13555989376U, 13564379008U, |
||||||
|
13572766336U, 13581154432U, 13589544832U, 13597932928U, 13606320512U, |
||||||
|
13614710656U, 13623097472U, 13631477632U, 13639874944U, 13648264064U, |
||||||
|
13656652928U, 13665041792U, 13673430656U, 13681818496U, 13690207616U, |
||||||
|
13698595712U, 13706982272U, 13715373184U, 13723762048U, 13732150144U, |
||||||
|
13740536704U, 13748926592U, 13757316224U, 13765700992U, 13774090112U, |
||||||
|
13782477952U, 13790869376U, 13799259008U, 13807647872U, 13816036736U, |
||||||
|
13824425344U, 13832814208U, 13841202304U, 13849591424U, 13857978752U, |
||||||
|
13866368896U, 13874754688U, 13883145344U, 13891533184U, 13899919232U, |
||||||
|
13908311168U, 13916692096U, 13925085056U, 13933473152U, 13941866368U, |
||||||
|
13950253696U, 13958643584U, 13967032192U, 13975417216U, 13983807616U, |
||||||
|
13992197504U, 14000582272U, 14008973696U, 14017363072U, 14025752192U, |
||||||
|
14034137984U, 14042528384U, 14050918016U, 14059301504U, 14067691648U, |
||||||
|
14076083584U, 14084470144U, 14092852352U, 14101249664U, 14109635968U, |
||||||
|
14118024832U, 14126407552U, 14134804352U, 14143188608U, 14151577984U, |
||||||
|
14159968384U, 14168357248U, 14176741504U, 14185127296U, 14193521024U, |
||||||
|
14201911424U, 14210301824U, 14218685056U, 14227067264U, 14235467392U, |
||||||
|
14243855488U, 14252243072U, 14260630144U, 14269021568U, 14277409408U, |
||||||
|
14285799296U, 14294187904U, 14302571392U, 14310961792U, 14319353728U, |
||||||
|
14327738752U, 14336130944U, 14344518784U, 14352906368U, 14361296512U, |
||||||
|
14369685376U, 14378071424U, 14386462592U, 14394848128U, 14403230848U, |
||||||
|
14411627392U, 14420013952U, 14428402304U, 14436793472U, 14445181568U, |
||||||
|
14453569664U, 14461959808U, 14470347904U, 14478737024U, 14487122816U, |
||||||
|
14495511424U, 14503901824U, 14512291712U, 14520677504U, 14529064832U, |
||||||
|
14537456768U, 14545845632U, 14554234496U, 14562618496U, 14571011456U, |
||||||
|
14579398784U, 14587789184U, 14596172672U, 14604564608U, 14612953984U, |
||||||
|
14621341312U, 14629724288U, 14638120832U, 14646503296U, 14654897536U, |
||||||
|
14663284864U, 14671675264U, 14680061056U, 14688447616U, 14696835968U, |
||||||
|
14705228416U, 14713616768U, 14722003328U, 14730392192U, 14738784128U, |
||||||
|
14747172736U, 14755561088U, 14763947648U, 14772336512U, 14780725376U, |
||||||
|
14789110144U, 14797499776U, 14805892736U, 14814276992U, 14822670208U, |
||||||
|
14831056256U, 14839444352U, 14847836032U, 14856222848U, 14864612992U, |
||||||
|
14872997504U, 14881388672U, 14889775744U, 14898165376U, 14906553472U, |
||||||
|
14914944896U, 14923329664U, 14931721856U, 14940109696U, 14948497024U, |
||||||
|
14956887424U, 14965276544U, 14973663616U, 14982053248U, 14990439808U, |
||||||
|
14998830976U, 15007216768U, 15015605888U, 15023995264U, 15032385152U, |
||||||
|
15040768384U, 15049154944U, 15057549184U, 15065939072U, 15074328448U, |
||||||
|
15082715008U, 15091104128U, 15099493504U, 15107879296U, 15116269184U, |
||||||
|
15124659584U, 15133042304U, 15141431936U, 15149824384U, 15158214272U, |
||||||
|
15166602368U, 15174991232U, 15183378304U, 15191760512U, 15200154496U, |
||||||
|
15208542592U, 15216931712U, 15225323392U, 15233708416U, 15242098048U, |
||||||
|
15250489216U, 15258875264U, 15267265408U, 15275654528U, 15284043136U, |
||||||
|
15292431488U, 15300819584U, 15309208192U, 15317596544U, 15325986176U, |
||||||
|
15334374784U, 15342763648U, 15351151744U, 15359540608U, 15367929728U, |
||||||
|
15376318336U, 15384706432U, 15393092992U, 15401481856U, 15409869952U, |
||||||
|
15418258816U, 15426649984U, 15435037568U, 15443425664U, 15451815296U, |
||||||
|
15460203392U, 15468589184U, 15476979328U, 15485369216U, 15493755776U, |
||||||
|
15502146944U, 15510534272U, 15518924416U, 15527311232U, 15535699072U, |
||||||
|
15544089472U, 15552478336U, 15560866688U, 15569254528U, 15577642624U, |
||||||
|
15586031488U, 15594419072U, 15602809472U, 15611199104U, 15619586432U, |
||||||
|
15627975296U, 15636364928U, 15644753792U, 15653141888U, 15661529216U, |
||||||
|
15669918848U, 15678305152U, 15686696576U, 15695083136U, 15703474048U, |
||||||
|
15711861632U, 15720251264U, 15728636288U, 15737027456U, 15745417088U, |
||||||
|
15753804928U, 15762194048U, 15770582656U, 15778971008U, 15787358336U, |
||||||
|
15795747712U, 15804132224U, 15812523392U, 15820909696U, 15829300096U, |
||||||
|
15837691264U, 15846071936U, 15854466944U, 15862855808U, 15871244672U, |
||||||
|
15879634816U, 15888020608U, 15896409728U, 15904799104U, 15913185152U, |
||||||
|
15921577088U, 15929966464U, 15938354816U, 15946743424U, 15955129472U, |
||||||
|
15963519872U, 15971907968U, 15980296064U, 15988684928U, 15997073024U, |
||||||
|
16005460864U, 16013851264U, 16022241152U, 16030629248U, 16039012736U, |
||||||
|
16047406976U, 16055794816U, 16064181376U, 16072571264U, 16080957824U, |
||||||
|
16089346688U, 16097737856U, 16106125184U, 16114514816U, 16122904192U, |
||||||
|
16131292544U, 16139678848U, 16148066944U, 16156453504U, 16164839552U, |
||||||
|
16173236096U, 16181623424U, 16190012032U, 16198401152U, 16206790528U, |
||||||
|
16215177344U, 16223567744U, 16231956352U, 16240344704U, 16248731008U, |
||||||
|
16257117824U, 16265504384U, 16273898624U, 16282281856U, 16290668672U, |
||||||
|
16299064192U, 16307449216U, 16315842176U, 16324230016U, 16332613504U, |
||||||
|
16341006464U, 16349394304U, 16357783168U, 16366172288U, 16374561664U, |
||||||
|
16382951296U, 16391337856U, 16399726208U, 16408116352U, 16416505472U, |
||||||
|
16424892032U, 16433282176U, 16441668224U, 16450058624U, 16458448768U, |
||||||
|
16466836864U, 16475224448U, 16483613056U, 16492001408U, 16500391808U, |
||||||
|
16508779648U, 16517166976U, 16525555328U, 16533944192U, 16542330752U, |
||||||
|
16550719616U, 16559110528U, 16567497088U, 16575888512U, 16584274816U, |
||||||
|
16592665472U, 16601051008U, 16609442944U, 16617832064U, 16626218624U, |
||||||
|
16634607488U, 16642996096U, 16651385728U, 16659773824U, 16668163712U, |
||||||
|
16676552576U, 16684938112U, 16693328768U, 16701718144U, 16710095488U, |
||||||
|
16718492288U, 16726883968U, 16735272832U, 16743661184U, 16752049792U, |
||||||
|
16760436608U, 16768827008U, 16777214336U, 16785599104U, 16793992832U, |
||||||
|
16802381696U, 16810768768U, 16819151744U, 16827542656U, 16835934848U, |
||||||
|
16844323712U, 16852711552U, 16861101952U, 16869489536U, 16877876864U, |
||||||
|
16886265728U, 16894653056U, 16903044736U, 16911431296U, 16919821696U, |
||||||
|
16928207488U, 16936592768U, 16944987776U, 16953375616U, 16961763968U, |
||||||
|
16970152832U, 16978540928U, 16986929536U, 16995319168U, 17003704448U, |
||||||
|
17012096896U, 17020481152U, 17028870784U, 17037262208U, 17045649536U, |
||||||
|
17054039936U, 17062426496U, 17070814336U, 17079205504U, 17087592064U, |
||||||
|
17095978112U, 17104369024U, 17112759424U, 17121147776U, 17129536384U, |
||||||
|
17137926016U, 17146314368U, 17154700928U, 17163089792U, 17171480192U, |
||||||
|
17179864192U, 17188256896U, 17196644992U, 17205033856U, 17213423488U, |
||||||
|
17221811072U, 17230198912U, 17238588032U, 17246976896U, 17255360384U, |
||||||
|
17263754624U, 17272143232U, 17280530048U, 17288918912U, 17297309312U, |
||||||
|
17305696384U, 17314085504U, 17322475136U, 17330863744U, 17339252096U, |
||||||
|
17347640192U, 17356026496U, 17364413824U, 17372796544U, 17381190016U, |
||||||
|
17389583488U, 17397972608U, 17406360704U, 17414748544U, 17423135872U, |
||||||
|
17431527296U, 17439915904U, 17448303232U, 17456691584U, 17465081728U, |
||||||
|
17473468288U, 17481857408U, 17490247552U, 17498635904U, 17507022464U, |
||||||
|
17515409024U, 17523801728U, 17532189824U, 17540577664U, 17548966016U, |
||||||
|
17557353344U, 17565741184U, 17574131584U, 17582519168U, 17590907008U, |
||||||
|
17599296128U, 17607687808U, 17616076672U, 17624455808U, 17632852352U, |
||||||
|
17641238656U, 17649630848U, 17658018944U, 17666403968U, 17674794112U, |
||||||
|
17683178368U, 17691573376U, 17699962496U, 17708350592U, 17716739968U, |
||||||
|
17725126528U, 17733517184U, 17741898112U, 17750293888U, 17758673024U, |
||||||
|
17767070336U, 17775458432U, 17783848832U, 17792236928U, 17800625536U, |
||||||
|
17809012352U, 17817402752U, 17825785984U, 17834178944U, 17842563968U, |
||||||
|
17850955648U, 17859344512U, 17867732864U, 17876119424U, 17884511872U, |
||||||
|
17892900224U, 17901287296U, 17909677696U, 17918058112U, 17926451072U, |
||||||
|
17934843776U, 17943230848U, 17951609216U, 17960008576U, 17968397696U, |
||||||
|
17976784256U, 17985175424U, 17993564032U, 18001952128U, 18010339712U, |
||||||
|
18018728576U, 18027116672U, 18035503232U, 18043894144U, 18052283264U, |
||||||
|
18060672128U, 18069056384U, 18077449856U, 18085837184U, 18094225792U, |
||||||
|
18102613376U, 18111004544U, 18119388544U, 18127781248U, 18136170368U, |
||||||
|
18144558976U, 18152947328U, 18161336192U, 18169724288U, 18178108544U, |
||||||
|
18186498944U, 18194886784U, 18203275648U, 18211666048U, 18220048768U, |
||||||
|
18228444544U, 18236833408U, 18245220736U |
||||||
|
}; |
||||||
|
|
||||||
|
|
||||||
|
// Generated with the following Mathematica Code:
|
||||||
|
|
||||||
|
// GetCacheSizes[n_] := Module[{
|
||||||
|
// DataSetSizeBytesInit = 2^30,
|
||||||
|
// MixBytes = 128,
|
||||||
|
// DataSetGrowth = 2^23,
|
||||||
|
// HashBytes = 64,
|
||||||
|
// CacheMultiplier = 1024,
|
||||||
|
// j = 0},
|
||||||
|
// Reap[
|
||||||
|
// While[j < n,
|
||||||
|
// Module[{i = Floor[(DataSetSizeBytesInit + DataSetGrowth * j) / (CacheMultiplier * HashBytes)]},
|
||||||
|
// While[! PrimeQ[i], i--];
|
||||||
|
// Sow[i*HashBytes]; j++]]]][[2]][[1]]
|
||||||
|
|
||||||
|
const size_t cache_sizes[] = { |
||||||
|
1048384U, 1055552U, 1064512U, 1072832U, 1080896U, 1089344U, 1096768U, |
||||||
|
1104448U, 1113664U, 1121216U, 1130176U, 1138624U, 1146304U, 1155008U, |
||||||
|
1162816U, 1171264U, 1179328U, 1187392U, 1195456U, 1203392U, 1210816U, |
||||||
|
1220416U, 1227712U, 1236416U, 1244608U, 1253312U, 1261376U, 1268416U, |
||||||
|
1277632U, 1285696U, 1294016U, 1302208U, 1310656U, 1318336U, 1326784U, |
||||||
|
1334848U, 1342912U, 1350848U, 1359808U, 1366208U, 1376192U, 1383488U, |
||||||
|
1392448U, 1400384U, 1408832U, 1416512U, 1425344U, 1433408U, 1440704U, |
||||||
|
1449664U, 1458112U, 1466048U, 1474496U, 1482688U, 1490752U, 1498688U, |
||||||
|
1507136U, 1515328U, 1523264U, 1531456U, 1539904U, 1547584U, 1556288U, |
||||||
|
1564352U, 1572544U, 1580608U, 1588544U, 1596992U, 1605568U, 1612096U, |
||||||
|
1621952U, 1630144U, 1637696U, 1645888U, 1654336U, 1662784U, 1671104U, |
||||||
|
1679168U, 1686848U, 1695296U, 1702208U, 1711168U, 1720256U, 1727552U, |
||||||
|
1736128U, 1744576U, 1751488U, 1760576U, 1769408U, 1777472U, 1785664U, |
||||||
|
1793984U, 1801664U, 1810112U, 1818304U, 1826624U, 1834816U, 1842752U, |
||||||
|
1851328U, 1858112U, 1867456U, 1875904U, 1883968U, 1892288U, 1899712U, |
||||||
|
1908416U, 1916608U, 1924544U, 1932992U, 1940672U, 1948736U, 1956928U, |
||||||
|
1965632U, 1973824U, 1982144U, 1989824U, 1998784U, 2006848U, 2014784U, |
||||||
|
2022848U, 2031424U, 2038976U, 2047424U, 2055616U, 2064064U, 2072384U, |
||||||
|
2080448U, 2088512U, 2095936U, 2104768U, 2113472U, 2121664U, 2127808U, |
||||||
|
2137792U, 2146112U, 2153408U, 2162624U, 2170304U, 2178496U, 2186944U, |
||||||
|
2195392U, 2203456U, 2211136U, 2219968U, 2227648U, 2236096U, 2244416U, |
||||||
|
2250944U, 2260928U, 2268736U, 2276672U, 2283328U, 2293696U, 2301632U, |
||||||
|
2309312U, 2317888U, 2325952U, 2334656U, 2342848U, 2350144U, 2358848U, |
||||||
|
2366656U, 2375488U, 2383552U, 2391616U, 2400064U, 2407616U, 2415808U, |
||||||
|
2424256U, 2432704U, 2439616U, 2448704U, 2457152U, 2464064U, 2473792U, |
||||||
|
2482112U, 2489792U, 2497472U, 2506432U, 2514752U, 2522816U, 2531264U, |
||||||
|
2539456U, 2547136U, 2555456U, 2564032U, 2572096U, 2578496U, 2587712U, |
||||||
|
2595776U, 2604736U, 2613056U, 2620736U, 2629184U, 2637632U, 2645824U, |
||||||
|
2653888U, 2662208U, 2670016U, 2678464U, 2686912U, 2694464U, 2703296U, |
||||||
|
2710976U, 2719424U, 2727104U, 2736064U, 2743232U, 2752192U, 2760512U, |
||||||
|
2768704U, 2777024U, 2785088U, 2792512U, 2800576U, 2809024U, 2817856U, |
||||||
|
2826176U, 2833984U, 2840896U, 2850752U, 2858048U, 2867008U, 2875328U, |
||||||
|
2883392U, 2891584U, 2899648U, 2908096U, 2915648U, 2924224U, 2932672U, |
||||||
|
2940736U, 2948672U, 2956736U, 2964928U, 2973248U, 2981824U, 2988992U, |
||||||
|
2997184U, 3005248U, 3013952U, 3022144U, 3030592U, 3037376U, 3046976U, |
||||||
|
3055552U, 3063616U, 3070784U, 3079744U, 3087808U, 3096512U, 3103808U, |
||||||
|
3111872U, 3121088U, 3128896U, 3137216U, 3144896U, 3153856U, 3161152U, |
||||||
|
3169984U, 3178432U, 3186496U, 3194816U, 3203008U, 3210176U, 3218624U, |
||||||
|
3227072U, 3235264U, 3243712U, 3250496U, 3259456U, 3268544U, 3276736U, |
||||||
|
3283648U, 3292736U, 3301184U, 3308224U, 3317696U, 3324736U, 3333184U, |
||||||
|
3342272U, 3348544U, 3357248U, 3365312U, 3374912U, 3383104U, 3390784U, |
||||||
|
3399488U, 3407296U, 3414976U, 3424192U, 3432256U, 3440576U, 3448768U, |
||||||
|
3456832U, 3464896U, 3473216U, 3480128U, 3489344U, 3497408U, 3505856U, |
||||||
|
3514048U, 3521344U, 3530432U, 3538624U, 3546304U, 3555008U, 3563072U, |
||||||
|
3571648U, 3579712U, 3587392U, 3595456U, 3603904U, 3612352U, 3620416U, |
||||||
|
3628864U, 3636928U, 3645248U, 3652928U, 3660992U, 3669184U, 3677888U, |
||||||
|
3685952U, 3694528U, 3702592U, 3710528U, 3719104U, 3727168U, 3735488U, |
||||||
|
3742784U, 3751232U, 3759424U, 3765184U, 3775808U, 3783872U, 3792832U, |
||||||
|
3800768U, 3808832U, 3816256U, 3825344U, 3832768U, 3841856U, 3849536U, |
||||||
|
3857344U, 3866432U, 3874496U, 3882304U, 3890752U, 3899072U, 3907264U, |
||||||
|
3914816U, 3923008U, 3930688U, 3939904U, 3947968U, 3956416U, 3964736U, |
||||||
|
3972544U, 3981248U, 3988928U, 3997376U, 4005824U, 4012864U, 4020928U, |
||||||
|
4030144U, 4038592U, 4045504U, 4054592U, 4063168U, 4071104U, 4079552U, |
||||||
|
4087232U, 4095808U, 4103872U, 4111168U, 4120384U, 4127936U, 4136512U, |
||||||
|
4144832U, 4153024U, 4160704U, 4169408U, 4177216U, 4186048U, 4193344U, |
||||||
|
4202048U, 4210496U, 4217536U, 4227008U, 4235072U, 4243264U, 4251584U, |
||||||
|
4259392U, 4267712U, 4275776U, 4284352U, 4291904U, 4300096U, 4307648U, |
||||||
|
4316992U, 4325056U, 4333376U, 4341056U, 4349888U, 4357568U, 4366016U, |
||||||
|
4374464U, 4382528U, 4390208U, 4398656U, 4407232U, 4413632U, 4423616U, |
||||||
|
4431808U, 4439744U, 4447936U, 4455872U, 4463296U, 4472128U, 4480576U, |
||||||
|
4489024U, 4497344U, 4505152U, 4512448U, 4520896U, 4530112U, 4537664U, |
||||||
|
4546496U, 4554688U, 4562752U, 4570816U, 4579264U, 4586944U, 4595648U, |
||||||
|
4603712U, 4611392U, 4619072U, 4628032U, 4635584U, 4643776U, 4652864U, |
||||||
|
4660672U, 4669376U, 4677056U, 4684096U, 4693184U, 4702144U, 4710208U, |
||||||
|
4718528U, 4726336U, 4734272U, 4742464U, 4750784U, 4759232U, 4767296U, |
||||||
|
4775872U, 4783808U, 4791872U, 4797376U, 4808512U, 4816192U, 4825024U, |
||||||
|
4832704U, 4841024U, 4849472U, 4856512U, 4865984U, 4874176U, 4882112U, |
||||||
|
4889792U, 4898752U, 4906688U, 4913984U, 4922816U, 4931008U, 4938944U, |
||||||
|
4946624U, 4955584U, 4964032U, 4972096U, 4980032U, 4988864U, 4997056U, |
||||||
|
5004992U, 5012288U, 5020096U, 5029312U, 5037632U, 5045696U, 5052224U, |
||||||
|
5062592U, 5070784U, 5078848U, 5086784U, 5095232U, 5100736U, 5111488U, |
||||||
|
5119936U, 5127104U, 5136064U, 5143616U, 5151424U, 5160256U, 5168704U, |
||||||
|
5175232U, 5185472U, 5192384U, 5199296U, 5209664U, 5218112U, 5225536U, |
||||||
|
5233472U, 5242816U, 5250496U, 5258944U, 5267264U, 5274944U, 5283776U, |
||||||
|
5290048U, 5300032U, 5308096U, 5316544U, 5323328U, 5331904U, 5340736U, |
||||||
|
5349056U, 5356864U, 5365312U, 5372096U, 5381696U, 5390272U, 5398336U, |
||||||
|
5405888U, 5413696U, 5422784U, 5430976U, 5439424U, 5446976U, 5455808U, |
||||||
|
5463616U, 5471168U, 5480128U, 5488064U, 5494592U, 5504704U, 5513152U, |
||||||
|
5521216U, 5529536U, 5536576U, 5544256U, 5554112U, 5559616U, 5570368U, |
||||||
|
5577664U, 5586752U, 5594944U, 5603008U, 5611456U, 5619392U, 5627584U, |
||||||
|
5634368U, 5643328U, 5651264U, 5659328U, 5667008U, 5675584U, 5684416U, |
||||||
|
5692864U, 5701568U, 5709632U, 5717056U, 5725376U, 5734336U, 5740096U, |
||||||
|
5750336U, 5758912U, 5766848U, 5775296U, 5782976U, 5790784U, 5799616U, |
||||||
|
5807936U, 5815232U, 5823808U, 5832256U, 5840192U, 5848768U, 5856832U, |
||||||
|
5864896U, 5873344U, 5879872U, 5888576U, 5897792U, 5905216U, 5914432U, |
||||||
|
5920448U, 5930944U, 5938624U, 5947328U, 5955392U, 5963456U, 5971648U, |
||||||
|
5979328U, 5988032U, 5995712U, 6003904U, 6012736U, 6021056U, 6029248U, |
||||||
|
6037184U, 6045632U, 6053312U, 6061376U, 6070208U, 6077504U, 6086464U, |
||||||
|
6094784U, 6101696U, 6110912U, 6118592U, 6127168U, 6135616U, 6143296U, |
||||||
|
6150208U, 6158912U, 6168128U, 6175808U, 6182464U, 6192832U, 6201152U, |
||||||
|
6209344U, 6217664U, 6224576U, 6233408U, 6241472U, 6249664U, 6258496U, |
||||||
|
6266816U, 6275008U, 6281152U, 6291136U, 6299456U, 6306752U, 6314816U, |
||||||
|
6323776U, 6332096U, 6339392U, 6348224U, 6356288U, 6364096U, 6373184U, |
||||||
|
6381376U, 6389696U, 6397504U, 6404416U, 6413632U, 6421952U, 6430016U, |
||||||
|
6437824U, 6446912U, 6454592U, 6463168U, 6471616U, 6478144U, 6487232U, |
||||||
|
6496192U, 6504128U, 6511936U, 6520256U, 6528832U, 6536896U, 6544576U, |
||||||
|
6553408U, 6561472U, 6569792U, 6577216U, 6586304U, 6592448U, 6601024U, |
||||||
|
6610624U, 6619072U, 6627136U, 6634816U, 6643264U, 6650816U, 6659776U, |
||||||
|
6667712U, 6675904U, 6682688U, 6691904U, 6700864U, 6709184U, 6717376U, |
||||||
|
6724544U, 6733504U, 6741824U, 6749888U, 6756032U, 6766528U, 6773056U, |
||||||
|
6782912U, 6790976U, 6798016U, 6807488U, 6815168U, 6823744U, 6832064U, |
||||||
|
6840128U, 6847552U, 6855872U, 6864064U, 6872128U, 6880576U, 6889408U, |
||||||
|
6897472U, 6905792U, 6913472U, 6920896U, 6930368U, 6938432U, 6946624U, |
||||||
|
6953536U, 6963136U, 6971072U, 6979136U, 6986944U, 6995392U, 7003712U, |
||||||
|
7012288U, 7019072U, 7028416U, 7036352U, 7044416U, 7051712U, 7060672U, |
||||||
|
7069376U, 7077568U, 7085504U, 7092544U, 7102016U, 7110592U, 7118656U, |
||||||
|
7126208U, 7135168U, 7143104U, 7150912U, 7159744U, 7167808U, 7175744U, |
||||||
|
7184192U, 7191232U, 7200448U, 7207744U, 7216576U, 7224128U, 7233472U, |
||||||
|
7241536U, 7249856U, 7256512U, 7264832U, 7274048U, 7282112U, 7290176U, |
||||||
|
7298752U, 7306688U, 7315136U, 7322816U, 7331392U, 7339456U, 7347776U, |
||||||
|
7356224U, 7364288U, 7371712U, 7380928U, 7387456U, 7396544U, 7404352U, |
||||||
|
7413568U, 7421632U, 7429696U, 7436864U, 7446464U, 7454144U, 7461952U, |
||||||
|
7470784U, 7478336U, 7487296U, 7495616U, 7503424U, 7511872U, 7520192U, |
||||||
|
7527616U, 7536448U, 7544512U, 7551424U, 7560128U, 7568576U, 7577536U, |
||||||
|
7583552U, 7592512U, 7600448U, 7610048U, 7618496U, 7626176U, 7634752U, |
||||||
|
7642816U, 7651264U, 7659328U, 7667008U, 7675456U, 7683136U, 7691584U, |
||||||
|
7700416U, 7707584U, 7716416U, 7724224U, 7733056U, 7740608U, 7749184U, |
||||||
|
7756096U, 7765952U, 7774016U, 7781824U, 7790528U, 7798592U, 7805888U, |
||||||
|
7814336U, 7822784U, 7831232U, 7839296U, 7847104U, 7855552U, 7863616U, |
||||||
|
7872448U, 7880128U, 7888576U, 7896256U, 7905088U, 7912768U, 7920448U, |
||||||
|
7928768U, 7937344U, 7945792U, 7953728U, 7959488U, 7970752U, 7978816U, |
||||||
|
7987136U, 7994816U, 8003392U, 8011712U, 8019904U, 8027456U, 8035264U, |
||||||
|
8044352U, 8052544U, 8060224U, 8069056U, 8076736U, 8084672U, 8093504U, |
||||||
|
8101312U, 8110016U, 8117696U, 8125888U, 8134592U, 8142016U, 8149952U, |
||||||
|
8159168U, 8166976U, 8175296U, 8183488U, 8191808U, 8199616U, 8207296U, |
||||||
|
8216128U, 8224576U, 8232256U, 8241088U, 8248256U, 8257472U, 8264128U, |
||||||
|
8273728U, 8281792U, 8290112U, 8297152U, 8305216U, 8314816U, 8322752U, |
||||||
|
8330944U, 8339392U, 8347072U, 8355392U, 8363968U, 8371904U, 8379328U, |
||||||
|
8388544U, 8394944U, 8404544U, 8412736U, 8421184U, 8429504U, 8437696U, |
||||||
|
8445376U, 8452544U, 8460736U, 8470208U, 8478016U, 8486848U, 8494144U, |
||||||
|
8503232U, 8511296U, 8519488U, 8527424U, 8534464U, 8543936U, 8552384U, |
||||||
|
8558912U, 8568128U, 8575936U, 8584256U, 8593216U, 8601536U, 8608832U, |
||||||
|
8616896U, 8625728U, 8634176U, 8641856U, 8649664U, 8658112U, 8666176U, |
||||||
|
8674112U, 8682944U, 8691136U, 8699456U, 8707648U, 8716096U, 8724416U, |
||||||
|
8732608U, 8740672U, 8748352U, 8756032U, 8764864U, 8773568U, 8781376U, |
||||||
|
8789824U, 8796992U, 8806208U, 8814272U, 8822336U, 8830912U, 8838848U, |
||||||
|
8847296U, 8854336U, 8863552U, 8871488U, 8879296U, 8887616U, 8894528U, |
||||||
|
8904512U, 8911424U, 8920768U, 8928704U, 8936128U, 8944576U, 8953664U, |
||||||
|
8960576U, 8970176U, 8977984U, 8986304U, 8994112U, 9002432U, 9011008U, |
||||||
|
9018176U, 9026624U, 9035584U, 9043904U, 9052096U, 9059264U, 9068096U, |
||||||
|
9075904U, 9084224U, 9092288U, 9100352U, 9108928U, 9116992U, 9125824U, |
||||||
|
9133504U, 9141824U, 9150272U, 9157952U, 9164608U, 9174848U, 9182912U, |
||||||
|
9190976U, 9199552U, 9205312U, 9215936U, 9222592U, 9232192U, 9240512U, |
||||||
|
9248704U, 9256256U, 9264832U, 9272896U, 9281344U, 9288896U, 9297088U, |
||||||
|
9305536U, 9313984U, 9322304U, 9329728U, 9337792U, 9346112U, 9355072U, |
||||||
|
9363136U, 9371072U, 9378752U, 9387712U, 9395648U, 9404224U, 9411008U, |
||||||
|
9420608U, 9428416U, 9436864U, 9445312U, 9453376U, 9460928U, 9468736U, |
||||||
|
9477824U, 9485248U, 9493696U, 9502144U, 9509056U, 9518528U, 9527104U, |
||||||
|
9535424U, 9543616U, 9551296U, 9559744U, 9568192U, 9576256U, 9584576U, |
||||||
|
9591872U, 9600704U, 9608384U, 9615808U, 9624512U, 9633472U, 9641536U, |
||||||
|
9649856U, 9658048U, 9665728U, 9674432U, 9682496U, 9691072U, 9699136U, |
||||||
|
9707072U, 9715136U, 9722176U, 9732032U, 9740096U, 9747904U, 9756352U, |
||||||
|
9764288U, 9771584U, 9780544U, 9789376U, 9796928U, 9804224U, 9813952U, |
||||||
|
9822016U, 9829696U, 9838016U, 9845824U, 9852992U, 9863104U, 9870656U, |
||||||
|
9878464U, 9887552U, 9895744U, 9903808U, 9912128U, 9920192U, 9927616U, |
||||||
|
9936064U, 9944768U, 9952576U, 9960128U, 9969472U, 9977152U, 9985216U, |
||||||
|
9994048U, 10001216U, 10007744U, 10018496U, 10026944U, 10035136U, 10042432U, |
||||||
|
10051264U, 10059584U, 10067648U, 10075712U, 10083904U, 10091456U, 10100672U, |
||||||
|
10108864U, 10116928U, 10124864U, 10133056U, 10140736U, 10149824U, 10156736U, |
||||||
|
10165952U, 10173376U, 10182208U, 10190528U, 10198336U, 10206272U, 10213696U, |
||||||
|
10223296U, 10231744U, 10238656U, 10247488U, 10256192U, 10263872U, 10272448U, |
||||||
|
10280896U, 10288448U, 10296512U, 10305088U, 10313536U, 10321088U, 10330048U, |
||||||
|
10337984U, 10346176U, 10354112U, 10362304U, 10369088U, 10377152U, 10386752U, |
||||||
|
10394816U, 10403648U, 10411712U, 10418624U, 10427968U, 10436032U, 10444736U, |
||||||
|
10452928U, 10459712U, 10468672U, 10476608U, 10484416U, 10491328U, 10501952U, |
||||||
|
10509376U, 10517824U, 10526528U, 10534336U, 10542656U, 10549696U, 10559168U, |
||||||
|
10566592U, 10575808U, 10583488U, 10590656U, 10599488U, 10607936U, 10616768U, |
||||||
|
10624832U, 10630336U, 10640576U, 10649536U, 10655168U, 10665152U, 10674112U, |
||||||
|
10682176U, 10690496U, 10698176U, 10705216U, 10715072U, 10722752U, 10731328U, |
||||||
|
10739264U, 10746688U, 10754752U, 10761664U, 10770752U, 10779712U, 10787776U, |
||||||
|
10796608U, 10803392U, 10812352U, 10821056U, 10828736U, 10837952U, 10846144U, |
||||||
|
10853824U, 10861376U, 10869952U, 10877248U, 10887104U, 10895296U, 10903232U, |
||||||
|
10910912U, 10918976U, 10927936U, 10935872U, 10944448U, 10952384U, 10960832U, |
||||||
|
10968512U, 10977088U, 10985024U, 10992832U, 11000896U, 11009984U, 11018048U, |
||||||
|
11026112U, 11034304U, 11042624U, 11050432U, 11058368U, 11064512U, 11075392U, |
||||||
|
11083712U, 11091776U, 11099584U, 11107904U, 11115968U, 11124416U, 11131712U, |
||||||
|
11141056U, 11148608U, 11157184U, 11165248U, 11173312U, 11180992U, 11189056U, |
||||||
|
11197376U, 11206592U, 11214656U, 11222336U, 11230784U, 11238464U, 11246528U, |
||||||
|
11254976U, 11263552U, 11271872U, 11279552U, 11288512U, 11296576U, 11304256U, |
||||||
|
11312192U, 11320768U, 11329216U, 11336384U, 11345216U, 11352512U, 11362112U, |
||||||
|
11369408U, 11378624U, 11386688U, 11394496U, 11402816U, 11411264U, 11418688U, |
||||||
|
11427776U, 11435584U, 11444032U, 11452096U, 11459648U, 11467072U, 11476928U, |
||||||
|
11484992U, 11493184U, 11500352U, 11509312U, 11517248U, 11524928U, 11534144U, |
||||||
|
11542208U, 11550272U, 11556416U, 11566784U, 11574208U, 11581376U, 11589568U, |
||||||
|
11599552U, 11607104U, 11616064U, 11623616U, 11632576U, 11639872U, 11648704U, |
||||||
|
11657024U, 11664704U, 11672896U, 11681216U, 11689792U, 11697856U, 11705536U, |
||||||
|
11714368U, 11722688U, 11730496U, 11737408U, 11745728U, 11754304U, 11763008U, |
||||||
|
11770816U, 11779648U, 11788096U, 11795776U, 11804608U, 11812544U, 11820992U, |
||||||
|
11829184U, 11837248U, 11844928U, 11852096U, 11860928U, 11869888U, 11878336U, |
||||||
|
11886272U, 11894336U, 11902144U, 11910848U, 11919296U, 11925952U, 11934784U, |
||||||
|
11943616U, 11951552U, 11960128U, 11968192U, 11976512U, 11983168U, 11992768U, |
||||||
|
12000832U, 12008896U, 12016832U, 12025408U, 12033856U, 12042176U, 12049984U, |
||||||
|
12058048U, 12066112U, 12073792U, 12082624U, 12091328U, 12098752U, 12106816U, |
||||||
|
12115904U, 12124096U, 12131776U, 12140224U, 12148672U, 12156736U, 12164032U, |
||||||
|
12173248U, 12181184U, 12186176U, 12197824U, 12205888U, 12213952U, 12218944U, |
||||||
|
12230336U, 12238784U, 12246592U, 12254272U, 12262336U, 12269888U, 12279104U, |
||||||
|
12287936U, 12295744U, 12304064U, 12312512U, 12319936U, 12328768U, 12337088U, |
||||||
|
12344896U, 12352832U, 12361408U, 12368704U, 12377152U, 12384832U, 12394432U, |
||||||
|
12402496U, 12409024U, 12417728U, 12426688U, 12433216U, 12443584U, 12450752U, |
||||||
|
12459968U, 12468032U, 12475712U, 12484544U, 12492608U, 12500416U, 12508352U, |
||||||
|
12517184U, 12525376U, 12532288U, 12541888U, 12549568U, 12556864U, 12565568U, |
||||||
|
12574528U, 12582208U, 12590528U, 12598592U, 12607424U, 12615488U, 12623552U, |
||||||
|
12631744U, 12638656U, 12647744U, 12656576U, 12664768U, 12672832U, 12680896U, |
||||||
|
12688576U, 12697408U, 12704192U, 12713408U, 12721216U, 12729664U, 12738496U, |
||||||
|
12745792U, 12754496U, 12762688U, 12769472U, 12779456U, 12787648U, 12795712U, |
||||||
|
12804032U, 12812224U, 12819008U, 12828352U, 12836672U, 12844736U, 12851648U, |
||||||
|
12859456U, 12868672U, 12877504U, 12885568U, 12892864U, 12902336U, 12909376U, |
||||||
|
12918208U, 12926656U, 12934976U, 12942784U, 12951104U, 12959552U, 12967744U, |
||||||
|
12976064U, 12984256U, 12991936U, 12999488U, 13007936U, 13016768U, 13021504U, |
||||||
|
13033024U, 13041472U, 13049408U, 13057472U, 13065664U, 13072064U, 13081408U, |
||||||
|
13089344U, 13098688U, 13107008U, 13115072U, 13122752U, 13130944U, 13139648U, |
||||||
|
13147712U, 13155776U, 13162432U, 13172672U, 13180864U, 13188928U, 13196992U, |
||||||
|
13203392U, 13213504U, 13219264U, 13228736U, 13236928U, 13244992U, 13253056U, |
||||||
|
13262528U, 13269952U, 13278784U, 13285952U, 13295552U, 13303616U, 13311808U, |
||||||
|
13319744U, 13328192U, 13336256U, 13344704U, 13352384U, 13360576U, 13369024U, |
||||||
|
13377344U, 13385408U, 13393216U, 13401664U, 13410112U, 13418176U, 13426496U, |
||||||
|
13434688U, 13442368U, 13451072U, 13459136U, 13466944U, 13475648U, 13482944U, |
||||||
|
13491904U, 13500352U, 13508288U, 13516736U, 13524416U, 13532224U, 13541312U, |
||||||
|
13549504U, 13556288U, 13564736U, 13573184U, 13581376U, 13587008U, 13598656U, |
||||||
|
13605952U, 13612864U, 13622464U, 13631168U, 13639616U, 13647808U, 13655104U, |
||||||
|
13663424U, 13671872U, 13680064U, 13688768U, 13696576U, 13705024U, 13712576U, |
||||||
|
13721536U, 13729216U, 13737664U, 13746112U, 13753024U, 13759552U, 13770304U, |
||||||
|
13777856U, 13786688U, 13793984U, 13802176U, 13811264U, 13819328U, 13827904U, |
||||||
|
13835456U, 13844416U, 13851584U, 13860544U, 13868992U, 13877056U, 13884608U, |
||||||
|
13893184U, 13901248U, 13909696U, 13917632U, 13925056U, 13934528U, 13942336U, |
||||||
|
13950784U, 13959104U, 13966912U, 13975232U, 13982656U, 13991872U, 13999936U, |
||||||
|
14007872U, 14016064U, 14024512U, 14032064U, 14040896U, 14049088U, 14057408U, |
||||||
|
14065088U, 14072896U, 14081344U, 14089664U, 14097856U, 14106304U, 14114752U, |
||||||
|
14122688U, 14130752U, 14138816U, 14147008U, 14155072U, 14163904U, 14170432U, |
||||||
|
14180288U, 14187328U, 14196032U, 14204864U, 14212672U, 14220736U, 14229056U, |
||||||
|
14237504U, 14245568U, 14253632U, 14261824U, 14269888U, 14278592U, 14286656U, |
||||||
|
14293696U, 14302784U, 14309696U, 14317504U, 14326336U, 14335936U, 14343232U, |
||||||
|
14352064U, 14359232U, 14368064U, 14376512U, 14384576U, 14393024U, 14401472U, |
||||||
|
14409536U, 14416832U, 14424512U, 14433856U, 14440768U, 14449984U, 14458816U, |
||||||
|
14465728U, 14474816U, 14482112U, 14491328U, 14499392U, 14506816U, 14516032U, |
||||||
|
14524352U, 14531392U, 14540224U, 14547392U, 14556992U, 14565184U, 14573248U, |
||||||
|
14580928U, 14588864U, 14596928U, 14606272U, 14613824U, 14622656U, 14630464U, |
||||||
|
14638912U, 14646976U, 14655296U, 14661952U, 14671808U, 14679872U, 14687936U, |
||||||
|
14696384U, 14704576U, 14710336U, 14720192U, 14729152U, 14736448U, 14745152U, |
||||||
|
14752448U, 14761792U, 14769856U, 14777024U, 14785984U, 14792384U, 14802752U, |
||||||
|
14810816U, 14819264U, 14827328U, 14835136U, 14843072U, 14851264U, 14860096U, |
||||||
|
14867648U, 14876096U, 14884544U, 14892736U, 14900672U, 14907968U, 14917312U, |
||||||
|
14924864U, 14933824U, 14939968U, 14950336U, 14957632U, 14966464U, 14974912U, |
||||||
|
14982592U, 14991296U, 14999104U, 15006272U, 15015232U, 15023936U, 15031616U, |
||||||
|
15040448U, 15047488U, 15055552U, 15063616U, 15073216U, 15079744U, 15088064U, |
||||||
|
15097664U, 15105344U, 15113792U, 15122368U, 15130048U, 15137728U, 15146176U, |
||||||
|
15154112U, 15162688U, 15171392U, 15179456U, 15187264U, 15194176U, 15204032U, |
||||||
|
15212224U, 15220544U, 15227456U, 15237056U, 15245248U, 15253184U, 15261632U, |
||||||
|
15269824U, 15277376U, 15285824U, 15293888U, 15301568U, 15310784U, 15318848U, |
||||||
|
15325504U, 15335104U, 15343168U, 15350848U, 15359936U, 15367232U, 15373376U, |
||||||
|
15384256U, 15392576U, 15400384U, 15408832U, 15417152U, 15424832U, 15433024U, |
||||||
|
15441344U, 15449152U, 15457088U, 15466432U, 15474112U, 15482816U, 15488576U, |
||||||
|
15499072U, 15505856U, 15514816U, 15523264U, 15531584U, 15540032U, 15547328U, |
||||||
|
15553984U, 15564608U, 15571904U, 15579968U, 15589312U, 15597376U, 15605696U, |
||||||
|
15612992U, 15621824U, 15630016U, 15638464U, 15646144U, 15654592U, 15662912U, |
||||||
|
15671104U, 15677248U, 15686848U, 15693376U, 15701696U, 15712064U, 15720256U, |
||||||
|
15728576U, 15736384U, 15744704U, 15752512U, 15761344U, 15769024U, 15777728U, |
||||||
|
15785152U, 15793984U, 15802048U, 15809984U, 15817024U, 15825856U, 15834944U, |
||||||
|
15843008U, 15849664U, 15859136U, 15866432U, 15876032U, 15884096U, 15892288U, |
||||||
|
15900608U, 15908416U, 15916864U, 15924928U, 15930176U, 15941056U, 15949504U, |
||||||
|
15957824U, 15965632U, 15973952U, 15982528U, 15990592U, 15998272U, 16006976U, |
||||||
|
16012736U, 16023104U, 16031296U, 16039616U, 16048064U, 16055744U, 16064192U, |
||||||
|
16071488U, 16080832U, 16088768U, 16097216U, 16104896U, 16112704U, 16121792U, |
||||||
|
16129856U, 16138048U, 16146112U, 16154176U, 16162624U, 16170688U, 16177856U, |
||||||
|
16186816U, 16195136U, 16202176U, 16211648U, 16220096U, 16228288U, 16235584U, |
||||||
|
16244672U, 16252864U, 16260544U, 16269248U, 16277056U, 16285504U, 16291648U, |
||||||
|
16301632U, 16309312U, 16318144U, 16326208U, 16333888U, 16342336U, 16351168U, |
||||||
|
16359232U, 16367552U, 16375616U, 16383296U, 16391744U, 16398016U, 16407616U, |
||||||
|
16415936U, 16424896U, 16432448U, 16440896U, 16449088U, 16457024U, 16465472U, |
||||||
|
16474048U, 16481216U, 16490048U, 16498624U, 16505792U, 16513984U, 16523072U, |
||||||
|
16531136U, 16538944U, 16547264U, 16555328U, 16563776U, 16570816U, 16578112U, |
||||||
|
16587712U, 16596544U, 16604992U, 16613312U, 16620608U, 16629568U, 16637888U, |
||||||
|
16645696U, 16653632U, 16661696U, 16669888U, 16677568U, 16686272U, 16695232U, |
||||||
|
16703168U, 16710464U, 16719424U, 16726592U, 16733888U, 16744384U, 16752448U, |
||||||
|
16760768U, 16768448U, 16776896U, 16785344U, 16793536U, 16801216U, 16809664U, |
||||||
|
16818112U, 16826176U, 16833472U, 16842688U, 16850752U, 16859072U, 16866368U, |
||||||
|
16875328U, 16883392U, 16891712U, 16899776U, 16907456U, 16915264U, 16924352U, |
||||||
|
16931776U, 16940608U, 16949056U, 16957376U, 16965056U, 16973248U, 16981696U, |
||||||
|
16990144U, 16997056U, 17005888U, 17014208U, 17021504U, 17031104U, 17039296U, |
||||||
|
17046976U, 17055424U, 17062592U, 17070016U, 17079488U, 17087936U, 17096512U, |
||||||
|
17104576U, 17113024U, 17121088U, 17129408U, 17136832U, 17145664U, 17152832U, |
||||||
|
17161792U, 17170112U, 17177792U, 17186368U, 17194304U, 17202496U, 17211328U, |
||||||
|
17218624U, 17227712U, 17233984U, 17243584U, 17251904U, 17259712U, 17266624U, |
||||||
|
17276608U, 17284672U, 17292224U, 17301056U, 17309632U, 17317568U, 17326016U, |
||||||
|
17333824U, 17342272U, 17350208U, 17358784U, 17366848U, 17374912U, 17382592U, |
||||||
|
17390656U, 17399488U, 17406784U, 17413952U, 17423936U, 17432512U, 17440448U, |
||||||
|
17447744U, 17456704U, 17464768U, 17472064U, 17481536U, 17489344U, 17495488U, |
||||||
|
17505728U, 17513792U, 17522368U, 17530816U, 17538112U, 17546944U, 17555264U, |
||||||
|
17563072U, 17569856U, 17579456U, 17587904U, 17596352U, 17603776U, 17611712U, |
||||||
|
17620672U, 17628992U, 17637184U, 17645504U, 17653568U, 17661632U, 17669824U, |
||||||
|
17677376U, 17686208U, 17693888U, 17702336U, 17710144U, 17718208U, 17726528U, |
||||||
|
17734336U, 17743808U, 17751872U, 17759936U, 17766592U, 17776448U, 17784512U, |
||||||
|
17791936U, 17801152U, 17809216U, 17817152U |
||||||
|
}; |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,74 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include "compiler.h" |
||||||
|
|
||||||
|
static const uint8_t BitReverseTable256[] = |
||||||
|
{ |
||||||
|
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0, |
||||||
|
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8, |
||||||
|
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4, |
||||||
|
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC, |
||||||
|
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2, |
||||||
|
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA, |
||||||
|
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6, |
||||||
|
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE, |
||||||
|
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1, |
||||||
|
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9, |
||||||
|
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5, |
||||||
|
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD, |
||||||
|
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3, |
||||||
|
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB, |
||||||
|
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7, |
||||||
|
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF |
||||||
|
}; |
||||||
|
|
||||||
|
static inline uint32_t bitfn_swap32(uint32_t a) { |
||||||
|
return (BitReverseTable256[a & 0xff] << 24) | |
||||||
|
(BitReverseTable256[(a >> 8) & 0xff] << 16) | |
||||||
|
(BitReverseTable256[(a >> 16) & 0xff] << 8) | |
||||||
|
(BitReverseTable256[(a >> 24) & 0xff]); |
||||||
|
} |
||||||
|
|
||||||
|
static inline uint64_t bitfn_swap64(uint64_t a) { |
||||||
|
return ((uint64_t) bitfn_swap32((uint32_t) (a >> 32))) | |
||||||
|
(((uint64_t) bitfn_swap32((uint32_t) a)) << 32); |
||||||
|
} |
||||||
|
|
||||||
|
#if defined(__MINGW32__) || defined(_WIN32) |
||||||
|
# define LITTLE_ENDIAN 1234 |
||||||
|
# define BYTE_ORDER LITTLE_ENDIAN |
||||||
|
#elif defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) |
||||||
|
# include <sys/endian.h> |
||||||
|
#elif defined(__OpenBSD__) || defined(__SVR4) |
||||||
|
# include <sys/types.h> |
||||||
|
#elif defined(__APPLE__) |
||||||
|
# include <machine/endian.h> |
||||||
|
#elif defined( BSD ) && (BSD >= 199103) |
||||||
|
# include <machine/endian.h> |
||||||
|
#elif defined( __QNXNTO__ ) && defined( __LITTLEENDIAN__ ) |
||||||
|
# define LITTLE_ENDIAN 1234 |
||||||
|
# define BYTE_ORDER LITTLE_ENDIAN |
||||||
|
#elif defined( __QNXNTO__ ) && defined( __BIGENDIAN__ ) |
||||||
|
# define BIG_ENDIAN 1234 |
||||||
|
# define BYTE_ORDER BIG_ENDIAN |
||||||
|
#else |
||||||
|
|
||||||
|
# include <endian.h> |
||||||
|
|
||||||
|
#endif |
||||||
|
|
||||||
|
|
||||||
|
#if LITTLE_ENDIAN == BYTE_ORDER |
||||||
|
|
||||||
|
#define fix_endian32(x) (x) |
||||||
|
#define fix_endian64(x) (x) |
||||||
|
|
||||||
|
#elif BIG_ENDIAN == BYTE_ORDER |
||||||
|
|
||||||
|
#define fix_endian32(x) bitfn_swap32(x) |
||||||
|
#define fix_endian64(x) bitfn_swap64(x) |
||||||
|
|
||||||
|
#else |
||||||
|
# error "endian not supported" |
||||||
|
#endif // BYTE_ORDER
|
@ -0,0 +1,95 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
/** @file ethash.h
|
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <stdbool.h> |
||||||
|
#include <string.h> |
||||||
|
#include <stddef.h> |
||||||
|
#include "compiler.h" |
||||||
|
|
||||||
|
#define REVISION 20 |
||||||
|
#define DAGSIZE_BYTES_INIT 1073741824U // 2**30
|
||||||
|
#define DAG_GROWTH 8388608U // 2**23
|
||||||
|
#define CACHE_MULTIPLIER 1024 |
||||||
|
#define EPOCH_LENGTH 30000U |
||||||
|
#define MIX_BYTES 128 |
||||||
|
#define DAG_PARENTS 256 |
||||||
|
#define CACHE_ROUNDS 3 |
||||||
|
#define ACCESSES 64 |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
typedef struct ethash_params { |
||||||
|
size_t full_size; // Size of full data set (in bytes, multiple of mix size (128)).
|
||||||
|
size_t cache_size; // Size of compute cache (in bytes, multiple of node size (64)).
|
||||||
|
} ethash_params; |
||||||
|
|
||||||
|
typedef struct ethash_return_value { |
||||||
|
uint8_t result[32]; |
||||||
|
uint8_t mix_hash[32]; |
||||||
|
} ethash_return_value; |
||||||
|
|
||||||
|
size_t ethash_get_datasize(const uint32_t block_number); |
||||||
|
size_t ethash_get_cachesize(const uint32_t block_number); |
||||||
|
|
||||||
|
// initialize the parameters
|
||||||
|
static inline void ethash_params_init(ethash_params *params, const uint32_t block_number) { |
||||||
|
params->full_size = ethash_get_datasize(block_number); |
||||||
|
params->cache_size = ethash_get_cachesize(block_number); |
||||||
|
} |
||||||
|
|
||||||
|
typedef struct ethash_cache { |
||||||
|
void *mem; |
||||||
|
} ethash_cache; |
||||||
|
|
||||||
|
void ethash_mkcache(ethash_cache *cache, ethash_params const *params, const uint8_t seed[32]); |
||||||
|
void ethash_compute_full_data(void *mem, ethash_params const *params, ethash_cache const *cache); |
||||||
|
void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); |
||||||
|
void ethash_light(ethash_return_value *ret, ethash_cache const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); |
||||||
|
|
||||||
|
static inline void ethash_prep_light(void *cache, ethash_params const *params, const uint8_t seed[32]) { ethash_cache c; c.mem = cache; ethash_mkcache(&c, params, seed); } |
||||||
|
static inline void ethash_compute_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { ethash_cache c; c.mem = (void*)cache; ethash_light(ret, &c, params, header_hash, nonce); } |
||||||
|
static inline void ethash_prep_full(void *full, ethash_params const *params, void const *cache) { ethash_cache c; c.mem = (void*)cache; ethash_compute_full_data(full, params, &c); } |
||||||
|
static inline void ethash_compute_full(ethash_return_value *ret, void const *full, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { ethash_full(ret, full, params, header_hash, nonce); } |
||||||
|
|
||||||
|
static inline int ethash_check_difficulty( |
||||||
|
const uint8_t hash[32], |
||||||
|
const uint8_t difficulty[32]) { |
||||||
|
// Difficulty is big endian
|
||||||
|
for (int i = 0; i < 32; i++) { |
||||||
|
if (hash[i] == difficulty[i]) continue; |
||||||
|
return hash[i] < difficulty[i]; |
||||||
|
} |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
int ethash_quick_check_difficulty( |
||||||
|
const uint8_t header_hash[32], |
||||||
|
const uint64_t nonce, |
||||||
|
const uint8_t mix_hash[32], |
||||||
|
const uint8_t difficulty[32]); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,38 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
/** @file fnv.h
|
||||||
|
* @author Matthew Wampler-Doty <negacthulhu@gmail.com> |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
#include <stdint.h> |
||||||
|
#include "compiler.h" |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
#define FNV_PRIME 0x01000193 |
||||||
|
|
||||||
|
static inline uint32_t fnv_hash(const uint32_t x, const uint32_t y) { |
||||||
|
return x*FNV_PRIME ^ y; |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,298 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
/** @file dash.cpp
|
||||||
|
* @author Tim Hughes <tim@twistedfury.com> |
||||||
|
* @author Matthew Wampler-Doty |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <assert.h> |
||||||
|
#include <inttypes.h> |
||||||
|
#include <stddef.h> |
||||||
|
#include "ethash.h" |
||||||
|
#include "fnv.h" |
||||||
|
#include "endian.h" |
||||||
|
#include "internal.h" |
||||||
|
#include "data_sizes.h" |
||||||
|
|
||||||
|
#ifdef WITH_CRYPTOPP |
||||||
|
|
||||||
|
#include "sha3_cryptopp.h" |
||||||
|
|
||||||
|
#else |
||||||
|
#include "sha3.h" |
||||||
|
#endif // WITH_CRYPTOPP
|
||||||
|
|
||||||
|
size_t ethash_get_datasize(const uint32_t block_number) { |
||||||
|
assert(block_number / EPOCH_LENGTH < 500); |
||||||
|
return dag_sizes[block_number / EPOCH_LENGTH]; |
||||||
|
} |
||||||
|
|
||||||
|
size_t ethash_get_cachesize(const uint32_t block_number) { |
||||||
|
assert(block_number / EPOCH_LENGTH < 500); |
||||||
|
return cache_sizes[block_number / EPOCH_LENGTH]; |
||||||
|
} |
||||||
|
|
||||||
|
// Follows Sergio's "STRICT MEMORY HARD HASHING FUNCTIONS" (2014)
|
||||||
|
// https://bitslog.files.wordpress.com/2013/12/memohash-v0-3.pdf
|
||||||
|
// SeqMemoHash(s, R, N)
|
||||||
|
void static ethash_compute_cache_nodes( |
||||||
|
node *const nodes, |
||||||
|
ethash_params const *params, |
||||||
|
const uint8_t seed[32]) { |
||||||
|
assert((params->cache_size % sizeof(node)) == 0); |
||||||
|
uint32_t const num_nodes = (uint32_t)(params->cache_size / sizeof(node)); |
||||||
|
|
||||||
|
SHA3_512(nodes[0].bytes, seed, 32); |
||||||
|
|
||||||
|
for (unsigned i = 1; i != num_nodes; ++i) { |
||||||
|
SHA3_512(nodes[i].bytes, nodes[i - 1].bytes, 64); |
||||||
|
} |
||||||
|
|
||||||
|
for (unsigned j = 0; j != CACHE_ROUNDS; j++) { |
||||||
|
for (unsigned i = 0; i != num_nodes; i++) { |
||||||
|
uint32_t const idx = nodes[i].words[0] % num_nodes; |
||||||
|
node data; |
||||||
|
data = nodes[(num_nodes - 1 + i) % num_nodes]; |
||||||
|
for (unsigned w = 0; w != NODE_WORDS; ++w) |
||||||
|
{ |
||||||
|
data.words[w] ^= nodes[idx].words[w]; |
||||||
|
} |
||||||
|
SHA3_512(nodes[i].bytes, data.bytes, sizeof(data)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// now perform endian conversion
|
||||||
|
#if BYTE_ORDER != LITTLE_ENDIAN |
||||||
|
for (unsigned w = 0; w != (num_nodes*NODE_WORDS); ++w) |
||||||
|
{ |
||||||
|
nodes->words[w] = fix_endian32(nodes->words[w]); |
||||||
|
} |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
void ethash_mkcache( |
||||||
|
ethash_cache *cache, |
||||||
|
ethash_params const *params, |
||||||
|
const uint8_t seed[32]) { |
||||||
|
node *nodes = (node *) cache->mem; |
||||||
|
ethash_compute_cache_nodes(nodes, params, seed); |
||||||
|
} |
||||||
|
|
||||||
|
void ethash_calculate_dag_item( |
||||||
|
node *const ret, |
||||||
|
const unsigned node_index, |
||||||
|
const struct ethash_params *params, |
||||||
|
const struct ethash_cache *cache) { |
||||||
|
|
||||||
|
uint32_t num_parent_nodes = (uint32_t)(params->cache_size / sizeof(node)); |
||||||
|
node const *cache_nodes = (node const *) cache->mem; |
||||||
|
node const *init = &cache_nodes[node_index % num_parent_nodes]; |
||||||
|
|
||||||
|
memcpy(ret, init, sizeof(node)); |
||||||
|
ret->words[0] ^= node_index; |
||||||
|
SHA3_512(ret->bytes, ret->bytes, sizeof(node)); |
||||||
|
|
||||||
|
#if defined(_M_X64) && ENABLE_SSE |
||||||
|
__m128i const fnv_prime = _mm_set1_epi32(FNV_PRIME); |
||||||
|
__m128i xmm0 = ret->xmm[0]; |
||||||
|
__m128i xmm1 = ret->xmm[1]; |
||||||
|
__m128i xmm2 = ret->xmm[2]; |
||||||
|
__m128i xmm3 = ret->xmm[3]; |
||||||
|
#endif |
||||||
|
|
||||||
|
for (unsigned i = 0; i != DAG_PARENTS; ++i) |
||||||
|
{ |
||||||
|
uint32_t parent_index = ((node_index ^ i)*FNV_PRIME ^ ret->words[i % NODE_WORDS]) % num_parent_nodes; |
||||||
|
node const *parent = &cache_nodes[parent_index]; |
||||||
|
|
||||||
|
#if defined(_M_X64) && ENABLE_SSE |
||||||
|
{ |
||||||
|
xmm0 = _mm_mullo_epi32(xmm0, fnv_prime); |
||||||
|
xmm1 = _mm_mullo_epi32(xmm1, fnv_prime); |
||||||
|
xmm2 = _mm_mullo_epi32(xmm2, fnv_prime); |
||||||
|
xmm3 = _mm_mullo_epi32(xmm3, fnv_prime); |
||||||
|
xmm0 = _mm_xor_si128(xmm0, parent->xmm[0]); |
||||||
|
xmm1 = _mm_xor_si128(xmm1, parent->xmm[1]); |
||||||
|
xmm2 = _mm_xor_si128(xmm2, parent->xmm[2]); |
||||||
|
xmm3 = _mm_xor_si128(xmm3, parent->xmm[3]); |
||||||
|
|
||||||
|
// have to write to ret as values are used to compute index
|
||||||
|
ret->xmm[0] = xmm0; |
||||||
|
ret->xmm[1] = xmm1; |
||||||
|
ret->xmm[2] = xmm2; |
||||||
|
ret->xmm[3] = xmm3; |
||||||
|
} |
||||||
|
#else |
||||||
|
{ |
||||||
|
for (unsigned w = 0; w != NODE_WORDS; ++w) { |
||||||
|
ret->words[w] = fnv_hash(ret->words[w], parent->words[w]); |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
SHA3_512(ret->bytes, ret->bytes, sizeof(node)); |
||||||
|
} |
||||||
|
|
||||||
|
void ethash_compute_full_data( |
||||||
|
void *mem, |
||||||
|
ethash_params const *params, |
||||||
|
ethash_cache const *cache) { |
||||||
|
assert((params->full_size % (sizeof(uint32_t) * MIX_WORDS)) == 0); |
||||||
|
assert((params->full_size % sizeof(node)) == 0); |
||||||
|
node *full_nodes = mem; |
||||||
|
|
||||||
|
// now compute full nodes
|
||||||
|
for (unsigned n = 0; n != (params->full_size / sizeof(node)); ++n) { |
||||||
|
ethash_calculate_dag_item(&(full_nodes[n]), n, params, cache); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static void ethash_hash( |
||||||
|
ethash_return_value * ret, |
||||||
|
node const *full_nodes, |
||||||
|
ethash_cache const *cache, |
||||||
|
ethash_params const *params, |
||||||
|
const uint8_t header_hash[32], |
||||||
|
const uint64_t nonce) { |
||||||
|
|
||||||
|
assert((params->full_size % MIX_WORDS) == 0); |
||||||
|
|
||||||
|
// pack hash and nonce together into first 40 bytes of s_mix
|
||||||
|
assert(sizeof(node)*8 == 512); |
||||||
|
node s_mix[MIX_NODES + 1]; |
||||||
|
memcpy(s_mix[0].bytes, header_hash, 32); |
||||||
|
|
||||||
|
#if BYTE_ORDER != LITTLE_ENDIAN |
||||||
|
s_mix[0].double_words[4] = fix_endian64(nonce); |
||||||
|
#else |
||||||
|
s_mix[0].double_words[4] = nonce; |
||||||
|
#endif |
||||||
|
|
||||||
|
// compute sha3-512 hash and replicate across mix
|
||||||
|
SHA3_512(s_mix->bytes, s_mix->bytes, 40); |
||||||
|
|
||||||
|
#if BYTE_ORDER != LITTLE_ENDIAN |
||||||
|
for (unsigned w = 0; w != 16; ++w) { |
||||||
|
s_mix[0].words[w] = fix_endian32(s_mix[0].words[w]); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
node* const mix = s_mix + 1; |
||||||
|
for (unsigned w = 0; w != MIX_WORDS; ++w) { |
||||||
|
mix->words[w] = s_mix[0].words[w % NODE_WORDS]; |
||||||
|
} |
||||||
|
|
||||||
|
unsigned const |
||||||
|
page_size = sizeof(uint32_t) * MIX_WORDS, |
||||||
|
num_full_pages = (unsigned)(params->full_size / page_size); |
||||||
|
|
||||||
|
|
||||||
|
for (unsigned i = 0; i != ACCESSES; ++i) |
||||||
|
{ |
||||||
|
uint32_t const index = ((s_mix->words[0] ^ i)*FNV_PRIME ^ mix->words[i % MIX_WORDS]) % num_full_pages; |
||||||
|
|
||||||
|
for (unsigned n = 0; n != MIX_NODES; ++n) |
||||||
|
{ |
||||||
|
const node * dag_node = &full_nodes[MIX_NODES * index + n]; |
||||||
|
|
||||||
|
if (!full_nodes) { |
||||||
|
node tmp_node; |
||||||
|
ethash_calculate_dag_item(&tmp_node, index * MIX_NODES + n, params, cache); |
||||||
|
dag_node = &tmp_node; |
||||||
|
} |
||||||
|
|
||||||
|
#if defined(_M_X64) && ENABLE_SSE |
||||||
|
{ |
||||||
|
__m128i fnv_prime = _mm_set1_epi32(FNV_PRIME); |
||||||
|
__m128i xmm0 = _mm_mullo_epi32(fnv_prime, mix[n].xmm[0]); |
||||||
|
__m128i xmm1 = _mm_mullo_epi32(fnv_prime, mix[n].xmm[1]); |
||||||
|
__m128i xmm2 = _mm_mullo_epi32(fnv_prime, mix[n].xmm[2]); |
||||||
|
__m128i xmm3 = _mm_mullo_epi32(fnv_prime, mix[n].xmm[3]); |
||||||
|
mix[n].xmm[0] = _mm_xor_si128(xmm0, dag_node->xmm[0]); |
||||||
|
mix[n].xmm[1] = _mm_xor_si128(xmm1, dag_node->xmm[1]); |
||||||
|
mix[n].xmm[2] = _mm_xor_si128(xmm2, dag_node->xmm[2]); |
||||||
|
mix[n].xmm[3] = _mm_xor_si128(xmm3, dag_node->xmm[3]); |
||||||
|
} |
||||||
|
#else |
||||||
|
{ |
||||||
|
for (unsigned w = 0; w != NODE_WORDS; ++w) { |
||||||
|
mix[n].words[w] = fnv_hash(mix[n].words[w], dag_node->words[w]); |
||||||
|
} |
||||||
|
} |
||||||
|
#endif |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
// compress mix
|
||||||
|
for (unsigned w = 0; w != MIX_WORDS; w += 4) |
||||||
|
{ |
||||||
|
uint32_t reduction = mix->words[w+0]; |
||||||
|
reduction = reduction*FNV_PRIME ^ mix->words[w+1]; |
||||||
|
reduction = reduction*FNV_PRIME ^ mix->words[w+2]; |
||||||
|
reduction = reduction*FNV_PRIME ^ mix->words[w+3]; |
||||||
|
mix->words[w/4] = reduction; |
||||||
|
} |
||||||
|
|
||||||
|
#if BYTE_ORDER != LITTLE_ENDIAN |
||||||
|
for (unsigned w = 0; w != MIX_WORDS/4; ++w) { |
||||||
|
mix->words[w] = fix_endian32(mix->words[w]); |
||||||
|
} |
||||||
|
#endif |
||||||
|
|
||||||
|
memcpy(ret->mix_hash, mix->bytes, 32); |
||||||
|
// final Keccak hash
|
||||||
|
SHA3_256(ret->result, s_mix->bytes, 64+32); // Keccak-256(s + compressed_mix)
|
||||||
|
} |
||||||
|
|
||||||
|
void ethash_quick_hash( |
||||||
|
uint8_t return_hash[32], |
||||||
|
const uint8_t header_hash[32], |
||||||
|
const uint64_t nonce, |
||||||
|
const uint8_t mix_hash[32]) { |
||||||
|
|
||||||
|
uint8_t buf[64+32]; |
||||||
|
memcpy(buf, header_hash, 32); |
||||||
|
#if BYTE_ORDER != LITTLE_ENDIAN |
||||||
|
nonce = fix_endian64(nonce); |
||||||
|
#endif |
||||||
|
memcpy(&(buf[32]), &nonce, 8); |
||||||
|
SHA3_512(buf, buf, 40); |
||||||
|
memcpy(&(buf[64]), mix_hash, 32); |
||||||
|
SHA3_256(return_hash, buf, 64+32); |
||||||
|
} |
||||||
|
|
||||||
|
int ethash_quick_check_difficulty( |
||||||
|
const uint8_t header_hash[32], |
||||||
|
const uint64_t nonce, |
||||||
|
const uint8_t mix_hash[32], |
||||||
|
const uint8_t difficulty[32]) { |
||||||
|
|
||||||
|
uint8_t return_hash[32]; |
||||||
|
ethash_quick_hash(return_hash, header_hash, nonce, mix_hash); |
||||||
|
return ethash_check_difficulty(return_hash, difficulty); |
||||||
|
} |
||||||
|
|
||||||
|
void ethash_full(ethash_return_value * ret, void const *full_mem, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { |
||||||
|
ethash_hash(ret, (node const *) full_mem, NULL, params, previous_hash, nonce); |
||||||
|
} |
||||||
|
|
||||||
|
void ethash_light(ethash_return_value * ret, ethash_cache const *cache, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { |
||||||
|
ethash_hash(ret, NULL, cache, params, previous_hash, nonce); |
||||||
|
} |
@ -0,0 +1,48 @@ |
|||||||
|
#pragma once |
||||||
|
#include "compiler.h" |
||||||
|
#include "endian.h" |
||||||
|
#include "ethash.h" |
||||||
|
|
||||||
|
#define ENABLE_SSE 1 |
||||||
|
|
||||||
|
#if defined(_M_X64) && ENABLE_SSE |
||||||
|
#include <smmintrin.h> |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
// compile time settings
|
||||||
|
#define NODE_WORDS (64/4) |
||||||
|
#define MIX_WORDS (MIX_BYTES/4) |
||||||
|
#define MIX_NODES (MIX_WORDS / NODE_WORDS) |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
typedef union node { |
||||||
|
uint8_t bytes[NODE_WORDS * 4]; |
||||||
|
uint32_t words[NODE_WORDS]; |
||||||
|
uint64_t double_words[NODE_WORDS / 2]; |
||||||
|
|
||||||
|
#if defined(_M_X64) && ENABLE_SSE |
||||||
|
__m128i xmm[NODE_WORDS/4]; |
||||||
|
#endif |
||||||
|
|
||||||
|
} node; |
||||||
|
|
||||||
|
void ethash_calculate_dag_item( |
||||||
|
node *const ret, |
||||||
|
const unsigned node_index, |
||||||
|
ethash_params const *params, |
||||||
|
ethash_cache const *cache |
||||||
|
); |
||||||
|
|
||||||
|
void ethash_quick_hash( |
||||||
|
uint8_t return_hash[32], |
||||||
|
const uint8_t header_hash[32], |
||||||
|
const uint64_t nonce, |
||||||
|
const uint8_t mix_hash[32]); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,151 @@ |
|||||||
|
/** libkeccak-tiny
|
||||||
|
* |
||||||
|
* A single-file implementation of SHA-3 and SHAKE. |
||||||
|
* |
||||||
|
* Implementor: David Leon Gil |
||||||
|
* License: CC0, attribution kindly requested. Blame taken too, |
||||||
|
* but not liability. |
||||||
|
*/ |
||||||
|
#include "sha3.h" |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include <stdlib.h> |
||||||
|
#include <string.h> |
||||||
|
|
||||||
|
/******** The Keccak-f[1600] permutation ********/ |
||||||
|
|
||||||
|
/*** Constants. ***/ |
||||||
|
static const uint8_t rho[24] = \
|
||||||
|
{ 1, 3, 6, 10, 15, 21, |
||||||
|
28, 36, 45, 55, 2, 14, |
||||||
|
27, 41, 56, 8, 25, 43, |
||||||
|
62, 18, 39, 61, 20, 44}; |
||||||
|
static const uint8_t pi[24] = \
|
||||||
|
{10, 7, 11, 17, 18, 3, |
||||||
|
5, 16, 8, 21, 24, 4, |
||||||
|
15, 23, 19, 13, 12, 2, |
||||||
|
20, 14, 22, 9, 6, 1}; |
||||||
|
static const uint64_t RC[24] = \
|
||||||
|
{1ULL, 0x8082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, |
||||||
|
0x808bULL, 0x80000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, |
||||||
|
0x8aULL, 0x88ULL, 0x80008009ULL, 0x8000000aULL, |
||||||
|
0x8000808bULL, 0x800000000000008bULL, 0x8000000000008089ULL, 0x8000000000008003ULL, |
||||||
|
0x8000000000008002ULL, 0x8000000000000080ULL, 0x800aULL, 0x800000008000000aULL, |
||||||
|
0x8000000080008081ULL, 0x8000000000008080ULL, 0x80000001ULL, 0x8000000080008008ULL}; |
||||||
|
|
||||||
|
/*** Helper macros to unroll the permutation. ***/ |
||||||
|
#define rol(x, s) (((x) << s) | ((x) >> (64 - s))) |
||||||
|
#define REPEAT6(e) e e e e e e |
||||||
|
#define REPEAT24(e) REPEAT6(e e e e) |
||||||
|
#define REPEAT5(e) e e e e e |
||||||
|
#define FOR5(v, s, e) \ |
||||||
|
v = 0; \
|
||||||
|
REPEAT5(e; v += s;) |
||||||
|
|
||||||
|
/*** Keccak-f[1600] ***/ |
||||||
|
static inline void keccakf(void* state) { |
||||||
|
uint64_t* a = (uint64_t*)state; |
||||||
|
uint64_t b[5] = {0}; |
||||||
|
uint64_t t = 0; |
||||||
|
uint8_t x, y; |
||||||
|
|
||||||
|
for (int i = 0; i < 24; i++) { |
||||||
|
// Theta
|
||||||
|
FOR5(x, 1, |
||||||
|
b[x] = 0; |
||||||
|
FOR5(y, 5, |
||||||
|
b[x] ^= a[x + y]; )) |
||||||
|
FOR5(x, 1, |
||||||
|
FOR5(y, 5, |
||||||
|
a[y + x] ^= b[(x + 4) % 5] ^ rol(b[(x + 1) % 5], 1); )) |
||||||
|
// Rho and pi
|
||||||
|
t = a[1]; |
||||||
|
x = 0; |
||||||
|
REPEAT24(b[0] = a[pi[x]]; |
||||||
|
a[pi[x]] = rol(t, rho[x]); |
||||||
|
t = b[0]; |
||||||
|
x++; ) |
||||||
|
// Chi
|
||||||
|
FOR5(y, |
||||||
|
5, |
||||||
|
FOR5(x, 1, |
||||||
|
b[x] = a[y + x];) |
||||||
|
FOR5(x, 1, |
||||||
|
a[y + x] = b[x] ^ ((~b[(x + 1) % 5]) & b[(x + 2) % 5]); )) |
||||||
|
// Iota
|
||||||
|
a[0] ^= RC[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/******** The FIPS202-defined functions. ********/ |
||||||
|
|
||||||
|
/*** Some helper macros. ***/ |
||||||
|
|
||||||
|
#define _(S) do { S } while (0) |
||||||
|
#define FOR(i, ST, L, S) \ |
||||||
|
_(for (size_t i = 0; i < L; i += ST) { S; }) |
||||||
|
#define mkapply_ds(NAME, S) \ |
||||||
|
static inline void NAME(uint8_t* dst, \
|
||||||
|
const uint8_t* src, \
|
||||||
|
size_t len) { \
|
||||||
|
FOR(i, 1, len, S); \
|
||||||
|
} |
||||||
|
#define mkapply_sd(NAME, S) \ |
||||||
|
static inline void NAME(const uint8_t* src, \
|
||||||
|
uint8_t* dst, \
|
||||||
|
size_t len) { \
|
||||||
|
FOR(i, 1, len, S); \
|
||||||
|
} |
||||||
|
|
||||||
|
mkapply_ds(xorin, dst[i] ^= src[i]) // xorin
|
||||||
|
mkapply_sd(setout, dst[i] = src[i]) // setout
|
||||||
|
|
||||||
|
#define P keccakf |
||||||
|
#define Plen 200 |
||||||
|
|
||||||
|
// Fold P*F over the full blocks of an input.
|
||||||
|
#define foldP(I, L, F) \ |
||||||
|
while (L >= rate) { \
|
||||||
|
F(a, I, rate); \
|
||||||
|
P(a); \
|
||||||
|
I += rate; \
|
||||||
|
L -= rate; \
|
||||||
|
} |
||||||
|
|
||||||
|
/** The sponge-based hash construction. **/ |
||||||
|
static inline int hash(uint8_t* out, size_t outlen, |
||||||
|
const uint8_t* in, size_t inlen, |
||||||
|
size_t rate, uint8_t delim) { |
||||||
|
if ((out == NULL) || ((in == NULL) && inlen != 0) || (rate >= Plen)) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
uint8_t a[Plen] = {0}; |
||||||
|
// Absorb input.
|
||||||
|
foldP(in, inlen, xorin); |
||||||
|
// Xor in the DS and pad frame.
|
||||||
|
a[inlen] ^= delim; |
||||||
|
a[rate - 1] ^= 0x80; |
||||||
|
// Xor in the last block.
|
||||||
|
xorin(a, in, inlen); |
||||||
|
// Apply P
|
||||||
|
P(a); |
||||||
|
// Squeeze output.
|
||||||
|
foldP(out, outlen, setout); |
||||||
|
setout(a, out, outlen); |
||||||
|
memset(a, 0, 200); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
#define defsha3(bits) \ |
||||||
|
int sha3_##bits(uint8_t* out, size_t outlen, \
|
||||||
|
const uint8_t* in, size_t inlen) { \
|
||||||
|
if (outlen > (bits/8)) { \
|
||||||
|
return -1; \
|
||||||
|
} \
|
||||||
|
return hash(out, outlen, in, inlen, 200 - (bits / 4), 0x01); \
|
||||||
|
} |
||||||
|
|
||||||
|
/*** FIPS202 SHA3 FOFs ***/ |
||||||
|
defsha3(256) |
||||||
|
defsha3(512) |
@ -0,0 +1,27 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
#include "compiler.h" |
||||||
|
#include <stdint.h> |
||||||
|
#include <stdlib.h> |
||||||
|
|
||||||
|
#define decsha3(bits) \ |
||||||
|
int sha3_##bits(uint8_t*, size_t, const uint8_t*, size_t); |
||||||
|
|
||||||
|
decsha3(256) |
||||||
|
decsha3(512) |
||||||
|
|
||||||
|
static inline void SHA3_256(uint8_t * const ret, uint8_t const *data, const size_t size) { |
||||||
|
sha3_256(ret, 32, data, size); |
||||||
|
} |
||||||
|
|
||||||
|
static inline void SHA3_512(uint8_t * const ret, uint8_t const *data, const size_t size) { |
||||||
|
sha3_512(ret, 64, data, size); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
34
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/sha3_cryptopp.cpp
generated
vendored
34
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/sha3_cryptopp.cpp
generated
vendored
@ -0,0 +1,34 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
|
||||||
|
/** @file sha3.cpp
|
||||||
|
* @author Tim Hughes <tim@twistedfury.com> |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
|
||||||
|
#include <stdint.h> |
||||||
|
#include <cryptopp/sha3.h> |
||||||
|
|
||||||
|
extern "C" { |
||||||
|
void SHA3_256(uint8_t *const ret, const uint8_t *data, size_t size) { |
||||||
|
CryptoPP::SHA3_256().CalculateDigest(ret, data, size); |
||||||
|
} |
||||||
|
|
||||||
|
void SHA3_512(uint8_t *const ret, const uint8_t *data, size_t size) { |
||||||
|
CryptoPP::SHA3_512().CalculateDigest(ret, data, size); |
||||||
|
} |
||||||
|
} |
15
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/sha3_cryptopp.h
generated
vendored
15
Godeps/_workspace/src/github.com/ethereum/ethash/src/libethash/sha3_cryptopp.h
generated
vendored
@ -0,0 +1,15 @@ |
|||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "compiler.h" |
||||||
|
#include <stdint.h> |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
void SHA3_256(uint8_t *const ret, const uint8_t *data, size_t size); |
||||||
|
void SHA3_512(uint8_t *const ret, const uint8_t *data, size_t size); |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,41 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
/** @file util.c
|
||||||
|
* @author Tim Hughes <tim@twistedfury.com> |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
#include <stdarg.h> |
||||||
|
#include <stdio.h> |
||||||
|
#include "util.h" |
||||||
|
|
||||||
|
#ifdef _MSC_VER |
||||||
|
|
||||||
|
// foward declare without all of Windows.h
|
||||||
|
__declspec(dllimport) void __stdcall OutputDebugStringA(const char* lpOutputString); |
||||||
|
|
||||||
|
void debugf(const char *str, ...) |
||||||
|
{ |
||||||
|
va_list args; |
||||||
|
va_start(args, str); |
||||||
|
|
||||||
|
char buf[1<<16]; |
||||||
|
_vsnprintf_s(buf, sizeof(buf), sizeof(buf), str, args); |
||||||
|
buf[sizeof(buf)-1] = '\0'; |
||||||
|
OutputDebugStringA(buf); |
||||||
|
} |
||||||
|
|
||||||
|
#endif |
@ -0,0 +1,47 @@ |
|||||||
|
/*
|
||||||
|
This file is part of cpp-ethereum. |
||||||
|
|
||||||
|
cpp-ethereum is free software: you can redistribute it and/or modify |
||||||
|
it under the terms of the GNU General Public License as published by |
||||||
|
the Free Software Foundation, either version 3 of the License, or |
||||||
|
(at your option) any later version. |
||||||
|
|
||||||
|
cpp-ethereum 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 General Public License for more details. |
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License |
||||||
|
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/ |
||||||
|
/** @file util.h
|
||||||
|
* @author Tim Hughes <tim@twistedfury.com> |
||||||
|
* @date 2015 |
||||||
|
*/ |
||||||
|
#pragma once |
||||||
|
#include <stdint.h> |
||||||
|
#include "compiler.h" |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
extern "C" { |
||||||
|
#endif |
||||||
|
|
||||||
|
#ifdef _MSC_VER |
||||||
|
void debugf(const char *str, ...); |
||||||
|
#else |
||||||
|
#define debugf printf |
||||||
|
#endif |
||||||
|
|
||||||
|
static inline uint32_t min_u32(uint32_t a, uint32_t b) |
||||||
|
{ |
||||||
|
return a < b ? a : b; |
||||||
|
} |
||||||
|
|
||||||
|
static inline uint32_t clamp_u32(uint32_t x, uint32_t min_, uint32_t max_) |
||||||
|
{ |
||||||
|
return x < min_ ? min_ : (x > max_ ? max_ : x); |
||||||
|
} |
||||||
|
|
||||||
|
#ifdef __cplusplus |
||||||
|
} |
||||||
|
#endif |
@ -0,0 +1,64 @@ |
|||||||
|
#include <Python.h> |
||||||
|
#include <alloca.h> |
||||||
|
#include <stdint.h> |
||||||
|
#include "../libethash/ethash.h" |
||||||
|
|
||||||
|
static PyObject* |
||||||
|
get_cache_size(PyObject* self, PyObject* args) |
||||||
|
{ |
||||||
|
unsigned long block_number; |
||||||
|
if (!PyArg_ParseTuple(args, "k", &block_number)) |
||||||
|
return 0; |
||||||
|
return Py_BuildValue("i", ethash_get_cachesize(block_number)); |
||||||
|
} |
||||||
|
|
||||||
|
static PyObject* |
||||||
|
get_full_size(PyObject* self, PyObject* args) |
||||||
|
{ |
||||||
|
unsigned long block_number; |
||||||
|
if (!PyArg_ParseTuple(args, "k", &block_number)) |
||||||
|
return 0; |
||||||
|
return Py_BuildValue("i", ethash_get_datasize(block_number)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static PyObject* |
||||||
|
mkcache(PyObject* self, PyObject* args) |
||||||
|
{ |
||||||
|
char * seed; |
||||||
|
unsigned long cache_size; |
||||||
|
int seed_len; |
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "ks#", &cache_size, &seed, &seed_len)) |
||||||
|
return 0; |
||||||
|
|
||||||
|
if (seed_len != 32) |
||||||
|
{ |
||||||
|
PyErr_SetString(PyExc_ValueError, |
||||||
|
"Seed must be 32 bytes long"); |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
printf("cache size: %lu\n", cache_size); |
||||||
|
ethash_params params; |
||||||
|
params.cache_size = (size_t) cache_size; |
||||||
|
ethash_cache cache; |
||||||
|
cache.mem = alloca(cache_size); |
||||||
|
ethash_mkcache(&cache, ¶ms, (uint8_t *) seed); |
||||||
|
return PyString_FromStringAndSize(cache.mem, cache_size); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
static PyMethodDef CoreMethods[] = |
||||||
|
{ |
||||||
|
{"get_cache_size", get_cache_size, METH_VARARGS, "Get the cache size for a given block number"}, |
||||||
|
{"get_full_size", get_full_size, METH_VARARGS, "Get the full size for a given block number"}, |
||||||
|
{"mkcache", mkcache, METH_VARARGS, "Makes the cache for given parameters and seed hash"}, |
||||||
|
{NULL, NULL, 0, NULL} |
||||||
|
}; |
||||||
|
|
||||||
|
PyMODINIT_FUNC |
||||||
|
initcore(void) |
||||||
|
{ |
||||||
|
(void) Py_InitModule("core", CoreMethods); |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
IF( NOT Boost_FOUND ) |
||||||
|
find_package(Boost COMPONENTS unit_test_framework) |
||||||
|
ENDIF() |
||||||
|
|
||||||
|
IF( Boost_FOUND ) |
||||||
|
include_directories( ${Boost_INCLUDE_DIR} ) |
||||||
|
include_directories(../../src) |
||||||
|
|
||||||
|
link_directories ( ${Boost_LIBRARY_DIRS} ) |
||||||
|
file(GLOB HEADERS "*.h") |
||||||
|
ADD_DEFINITIONS(-DBOOST_TEST_DYN_LINK) |
||||||
|
|
||||||
|
if (NOT CRYPTOPP_FOUND) |
||||||
|
find_package (CryptoPP) |
||||||
|
endif() |
||||||
|
|
||||||
|
if (CRYPTOPP_FOUND) |
||||||
|
add_definitions(-DWITH_CRYPTOPP) |
||||||
|
endif() |
||||||
|
|
||||||
|
add_executable (Test test.cpp ${HEADERS}) |
||||||
|
target_link_libraries (Test ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${ETHHASH_LIBS}) |
||||||
|
|
||||||
|
if (CRYPTOPP_FOUND) |
||||||
|
TARGET_LINK_LIBRARIES(Test ${CRYPTOPP_LIBRARIES}) |
||||||
|
endif() |
||||||
|
|
||||||
|
enable_testing () |
||||||
|
add_test(NAME ethash COMMAND Test) |
||||||
|
ENDIF() |
@ -0,0 +1,214 @@ |
|||||||
|
#include <iomanip> |
||||||
|
#include <libethash/fnv.h> |
||||||
|
#include <libethash/ethash.h> |
||||||
|
#include <libethash/internal.h> |
||||||
|
|
||||||
|
#ifdef WITH_CRYPTOPP |
||||||
|
#include <libethash/sha3_cryptopp.h> |
||||||
|
#else |
||||||
|
#include <libethash/sha3.h> |
||||||
|
#endif // WITH_CRYPTOPP
|
||||||
|
|
||||||
|
#define BOOST_TEST_MODULE Daggerhashimoto |
||||||
|
#define BOOST_TEST_MAIN |
||||||
|
|
||||||
|
#include <boost/test/unit_test.hpp> |
||||||
|
#include <iostream> |
||||||
|
|
||||||
|
std::string bytesToHexString(const uint8_t *str, const size_t s) { |
||||||
|
std::ostringstream ret; |
||||||
|
|
||||||
|
for (int i = 0; i < s; ++i) |
||||||
|
ret << std::hex << std::setfill('0') << std::setw(2) << std::nouppercase << (int) str[i]; |
||||||
|
|
||||||
|
return ret.str(); |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(fnv_hash_check) { |
||||||
|
uint32_t x = 1235U; |
||||||
|
const uint32_t |
||||||
|
y = 9999999U, |
||||||
|
expected = (FNV_PRIME * x) ^ y; |
||||||
|
|
||||||
|
x = fnv_hash(x, y); |
||||||
|
|
||||||
|
BOOST_REQUIRE_MESSAGE(x == expected, |
||||||
|
"\nexpected: " << expected << "\n" |
||||||
|
<< "actual: " << x << "\n"); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(SHA256_check) { |
||||||
|
uint8_t input[32], out[32]; |
||||||
|
memcpy(input, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", 32); |
||||||
|
SHA3_256(out, input, 32); |
||||||
|
const std::string |
||||||
|
expected = "2b5ddf6f4d21c23de216f44d5e4bdc68e044b71897837ea74c83908be7037cd7", |
||||||
|
actual = bytesToHexString(out, 32); |
||||||
|
BOOST_REQUIRE_MESSAGE(expected == actual, |
||||||
|
"\nexpected: " << expected.c_str() << "\n" |
||||||
|
<< "actual: " << actual.c_str() << "\n"); |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(SHA512_check) { |
||||||
|
uint8_t input[64], out[64]; |
||||||
|
memcpy(input, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", 64); |
||||||
|
SHA3_512(out, input, 64); |
||||||
|
const std::string |
||||||
|
expected = "0be8a1d334b4655fe58c6b38789f984bb13225684e86b20517a55ab2386c7b61c306f25e0627c60064cecd6d80cd67a82b3890bd1289b7ceb473aad56a359405", |
||||||
|
actual = bytesToHexString(out, 64); |
||||||
|
BOOST_REQUIRE_MESSAGE(expected == actual, |
||||||
|
"\nexpected: " << expected.c_str() << "\n" |
||||||
|
<< "actual: " << actual.c_str() << "\n"); |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(ethash_params_init_genesis_check) { |
||||||
|
ethash_params params; |
||||||
|
ethash_params_init(¶ms, 0); |
||||||
|
BOOST_REQUIRE_MESSAGE(params.full_size < DAGSIZE_BYTES_INIT, |
||||||
|
"\nfull size: " << params.full_size << "\n" |
||||||
|
<< "should be less than or equal to: " << DAGSIZE_BYTES_INIT << "\n"); |
||||||
|
BOOST_REQUIRE_MESSAGE(params.full_size + 20*MIX_BYTES >= DAGSIZE_BYTES_INIT, |
||||||
|
"\nfull size + 20*MIX_BYTES: " << params.full_size + 20*MIX_BYTES << "\n" |
||||||
|
<< "should be greater than or equal to: " << DAGSIZE_BYTES_INIT << "\n"); |
||||||
|
BOOST_REQUIRE_MESSAGE(params.cache_size < DAGSIZE_BYTES_INIT / 32, |
||||||
|
"\ncache size: " << params.cache_size << "\n" |
||||||
|
<< "should be less than or equal to: " << DAGSIZE_BYTES_INIT / 32 << "\n"); |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(ethash_params_init_genesis_calcifide_check) { |
||||||
|
ethash_params params; |
||||||
|
ethash_params_init(¶ms, 0); |
||||||
|
const uint32_t expected_full_size = 1073739904; |
||||||
|
const uint32_t expected_cache_size = 1048384; |
||||||
|
BOOST_REQUIRE_MESSAGE(params.full_size == expected_full_size, |
||||||
|
"\nexpected: " << expected_cache_size << "\n" |
||||||
|
<< "actual: " << params.full_size << "\n"); |
||||||
|
BOOST_REQUIRE_MESSAGE(params.cache_size == expected_cache_size, |
||||||
|
"\nexpected: " << expected_cache_size << "\n" |
||||||
|
<< "actual: " << params.cache_size << "\n"); |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(light_and_full_client_checks) { |
||||||
|
ethash_params params; |
||||||
|
uint8_t seed[32], hash[32]; |
||||||
|
ethash_return_value light_out, full_out; |
||||||
|
memcpy(seed, "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~", 32); |
||||||
|
memcpy(hash, "~~~X~~~~~~~~~~~~~~~~~~~~~~~~~~~~", 32); |
||||||
|
ethash_params_init(¶ms, 0); |
||||||
|
params.cache_size = 1024; |
||||||
|
params.full_size = 1024 * 32; |
||||||
|
ethash_cache cache; |
||||||
|
cache.mem = alloca(params.cache_size); |
||||||
|
ethash_mkcache(&cache, ¶ms, seed); |
||||||
|
node * full_mem = (node *) alloca(params.full_size); |
||||||
|
ethash_compute_full_data(full_mem, ¶ms, &cache); |
||||||
|
|
||||||
|
{ |
||||||
|
const std::string |
||||||
|
expected = "2da2b506f21070e1143d908e867962486d6b0a02e31d468fd5e3a7143aafa76a14201f63374314e2a6aaf84ad2eb57105dea3378378965a1b3873453bb2b78f9a8620b2ebeca41fbc773bb837b5e724d6eb2de570d99858df0d7d97067fb8103b21757873b735097b35d3bea8fd1c359a9e8a63c1540c76c9784cf8d975e995ca8620b2ebeca41fbc773bb837b5e724d6eb2de570d99858df0d7d97067fb8103b21757873b735097b35d3bea8fd1c359a9e8a63c1540c76c9784cf8d975e995ca8620b2ebeca41fbc773bb837b5e724d6eb2de570d99858df0d7d97067fb8103b21757873b735097b35d3bea8fd1c359a9e8a63c1540c76c9784cf8d975e995c259440b89fa3481c2c33171477c305c8e1e421f8d8f6d59585449d0034f3e421808d8da6bbd0b6378f567647cc6c4ba6c434592b198ad444e7284905b7c6adaf70bf43ec2daa7bd5e8951aa609ab472c124cf9eba3d38cff5091dc3f58409edcc386c743c3bd66f92408796ee1e82dd149eaefbf52b00ce33014a6eb3e50625413b072a58bc01da28262f42cbe4f87d4abc2bf287d15618405a1fe4e386fcdafbb171064bd99901d8f81dd6789396ce5e364ac944bbbd75a7827291c70b42d26385910cd53ca535ab29433dd5c5714d26e0dce95514c5ef866329c12e958097e84462197c2b32087849dab33e88b11da61d52f9dbc0b92cc61f742c07dbbf751c49d7678624ee60dfbe62e5e8c47a03d8247643f3d16ad8c8e663953bcda1f59d7e2d4a9bf0768e789432212621967a8f41121ad1df6ae1fa78782530695414c6213942865b2730375019105cae91a4c17a558d4b63059661d9f108362143107babe0b848de412e4da59168cce82bfbff3c99e022dd6ac1e559db991f2e3f7bb910cefd173e65ed00a8d5d416534e2c8416ff23977dbf3eb7180b75c71580d08ce95efeb9b0afe904ea12285a392aff0c8561ff79fca67f694a62b9e52377485c57cc3598d84cac0a9d27960de0cc31ff9bbfe455acaa62c8aa5d2cce96f345da9afe843d258a99c4eaf3650fc62efd81c7b81cd0d534d2d71eeda7a6e315d540b4473c80f8730037dc2ae3e47b986240cfc65ccc565f0d8cde0bc68a57e39a271dda57440b3598bee19f799611d25731a96b5dbbbefdff6f4f656161462633030d62560ea4e9c161cf78fc96a2ca5aaa32453a6c5dea206f766244e8c9d9a8dc61185ce37f1fc804459c5f07434f8ecb34141b8dcae7eae704c950b55556c5f40140c3714b45eddb02637513268778cbf937a33e4e33183685f9deb31ef54e90161e76d969587dd782eaa94e289420e7c2ee908517f5893a26fdb5873d68f92d118d4bcf98d7a4916794d6ab290045e30f9ea00ca547c584b8482b0331ba1539a0f2714fddc3a0b06b0cfbb6a607b8339c39bcfd6640b1f653e9d70ef6c985b", |
||||||
|
actual = bytesToHexString((uint8_t const *) cache.mem, params.cache_size); |
||||||
|
|
||||||
|
BOOST_REQUIRE_MESSAGE(expected == actual, |
||||||
|
"\nexpected: " << expected.c_str() << "\n" |
||||||
|
<< "actual: " << actual.c_str() << "\n"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
{ |
||||||
|
node node; |
||||||
|
ethash_calculate_dag_item(&node, 0, ¶ms, &cache); |
||||||
|
const std::string |
||||||
|
actual = bytesToHexString((uint8_t const *) &node, sizeof(node)), |
||||||
|
expected = "b1698f829f90b35455804e5185d78f549fcb1bdce2bee006d4d7e68eb154b596be1427769eb1c3c3e93180c760af75f81d1023da6a0ffbe321c153a7c0103597"; |
||||||
|
BOOST_REQUIRE_MESSAGE(actual == expected, |
||||||
|
"\n" << "expected: " << expected.c_str() << "\n" |
||||||
|
<< "actual: " << actual.c_str() << "\n"); |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
for (int i = 0 ; i < params.full_size / sizeof(node) ; ++i ) { |
||||||
|
for (uint32_t j = 0; j < 32; ++j) { |
||||||
|
node expected_node; |
||||||
|
ethash_calculate_dag_item(&expected_node, j, ¶ms, &cache); |
||||||
|
const std::string |
||||||
|
actual = bytesToHexString((uint8_t const *) &(full_mem[j]), sizeof(node)), |
||||||
|
expected = bytesToHexString((uint8_t const *) &expected_node, sizeof(node)); |
||||||
|
BOOST_REQUIRE_MESSAGE(actual == expected, |
||||||
|
"\ni: " << j << "\n" |
||||||
|
<< "expected: " << expected.c_str() << "\n" |
||||||
|
<< "actual: " << actual.c_str() << "\n"); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
{ |
||||||
|
uint64_t nonce = 0x7c7c597c; |
||||||
|
ethash_full(&full_out, full_mem, ¶ms, hash, nonce); |
||||||
|
ethash_light(&light_out, &cache, ¶ms, hash, nonce); |
||||||
|
const std::string |
||||||
|
light_result_string = bytesToHexString(light_out.result, 32), |
||||||
|
full_result_string = bytesToHexString(full_out.result, 32); |
||||||
|
BOOST_REQUIRE_MESSAGE(light_result_string == full_result_string, |
||||||
|
"\nlight result: " << light_result_string.c_str() << "\n" |
||||||
|
<< "full result: " << full_result_string.c_str() << "\n"); |
||||||
|
const std::string |
||||||
|
light_mix_hash_string = bytesToHexString(light_out.mix_hash, 32), |
||||||
|
full_mix_hash_string = bytesToHexString(full_out.mix_hash, 32); |
||||||
|
BOOST_REQUIRE_MESSAGE(full_mix_hash_string == light_mix_hash_string, |
||||||
|
"\nlight mix hash: " << light_mix_hash_string.c_str() << "\n" |
||||||
|
<< "full mix hash: " << full_mix_hash_string.c_str() << "\n"); |
||||||
|
uint8_t check_hash[32]; |
||||||
|
ethash_quick_hash(check_hash, hash, nonce, full_out.mix_hash); |
||||||
|
const std::string check_hash_string = bytesToHexString(check_hash, 32); |
||||||
|
BOOST_REQUIRE_MESSAGE(check_hash_string == full_result_string, |
||||||
|
"\ncheck hash string: " << check_hash_string.c_str() << "\n" |
||||||
|
<< "full result: " << full_result_string.c_str() << "\n"); |
||||||
|
} |
||||||
|
{ |
||||||
|
ethash_full(&full_out, full_mem, ¶ms, hash, 5); |
||||||
|
std::string |
||||||
|
light_result_string = bytesToHexString(light_out.result, 32), |
||||||
|
full_result_string = bytesToHexString(full_out.result, 32); |
||||||
|
|
||||||
|
BOOST_REQUIRE_MESSAGE(light_result_string != full_result_string, |
||||||
|
"\nlight result and full result should differ: " << light_result_string.c_str() << "\n"); |
||||||
|
|
||||||
|
ethash_light(&light_out, &cache, ¶ms, hash, 5); |
||||||
|
light_result_string = bytesToHexString(light_out.result, 32); |
||||||
|
BOOST_REQUIRE_MESSAGE(light_result_string == full_result_string, |
||||||
|
"\nlight result and full result should be the same\n" |
||||||
|
<< "light result: " << light_result_string.c_str() << "\n" |
||||||
|
<< "full result: " << full_result_string.c_str() << "\n"); |
||||||
|
std::string |
||||||
|
light_mix_hash_string = bytesToHexString(light_out.mix_hash, 32), |
||||||
|
full_mix_hash_string = bytesToHexString(full_out.mix_hash, 32); |
||||||
|
BOOST_REQUIRE_MESSAGE(full_mix_hash_string == light_mix_hash_string, |
||||||
|
"\nlight mix hash: " << light_mix_hash_string.c_str() << "\n" |
||||||
|
<< "full mix hash: " << full_mix_hash_string.c_str() << "\n"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(ethash_check_difficulty_check) { |
||||||
|
uint8_t hash[32], target[32]; |
||||||
|
memset(hash, 0, 32); |
||||||
|
memset(target, 0, 32); |
||||||
|
|
||||||
|
memcpy(hash, "11111111111111111111111111111111", 32); |
||||||
|
memcpy(target, "22222222222222222222222222222222", 32); |
||||||
|
BOOST_REQUIRE_MESSAGE( |
||||||
|
ethash_check_difficulty(hash, target), |
||||||
|
"\nexpected \"" << hash << "\" to have less difficulty than \"" << target << "\"\n"); |
||||||
|
BOOST_REQUIRE_MESSAGE( |
||||||
|
!ethash_check_difficulty(hash, hash), |
||||||
|
"\nexpected \"" << hash << "\" to have the same difficulty as \"" << hash << "\"\n"); |
||||||
|
memcpy(target, "11111111111111111111111111111112", 32); |
||||||
|
BOOST_REQUIRE_MESSAGE( |
||||||
|
ethash_check_difficulty(hash, target), |
||||||
|
"\nexpected \"" << hash << "\" to have less difficulty than \"" << target << "\"\n"); |
||||||
|
memcpy(target, "11111111111111111111111111111110", 32); |
||||||
|
BOOST_REQUIRE_MESSAGE( |
||||||
|
!ethash_check_difficulty(hash, target), |
||||||
|
"\nexpected \"" << hash << "\" to have more difficulty than \"" << target << "\"\n"); |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# Strict mode |
||||||
|
set -e |
||||||
|
|
||||||
|
SOURCE="${BASH_SOURCE[0]}" |
||||||
|
while [ -h "$SOURCE" ]; do |
||||||
|
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" |
||||||
|
SOURCE="$(readlink "$SOURCE")" |
||||||
|
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" |
||||||
|
done |
||||||
|
TEST_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" |
||||||
|
|
||||||
|
rm -rf $TEST_DIR/build |
||||||
|
mkdir -p $TEST_DIR/build |
||||||
|
cd $TEST_DIR/build ; |
||||||
|
cmake ../../.. > /dev/null |
||||||
|
make Test |
||||||
|
./test/c/Test |
@ -0,0 +1 @@ |
|||||||
|
python-virtual-env/ |
@ -0,0 +1,2 @@ |
|||||||
|
pyethereum==0.7.522 |
||||||
|
nose==1.3.4 |
@ -0,0 +1,19 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# Strict mode |
||||||
|
set -e |
||||||
|
|
||||||
|
SOURCE="${BASH_SOURCE[0]}" |
||||||
|
while [ -h "$SOURCE" ]; do |
||||||
|
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" |
||||||
|
SOURCE="$(readlink "$SOURCE")" |
||||||
|
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" |
||||||
|
done |
||||||
|
TEST_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" |
||||||
|
|
||||||
|
[ -d $TEST_DIR/python-virtual-env ] || virtualenv --system-site-packages $TEST_DIR/python-virtual-env |
||||||
|
source $TEST_DIR/python-virtual-env/bin/activate |
||||||
|
pip install -r $TEST_DIR/requirements.txt > /dev/null |
||||||
|
pip install -e $TEST_DIR/../.. > /dev/null |
||||||
|
cd $TEST_DIR |
||||||
|
nosetests --with-doctest -v |
@ -0,0 +1,45 @@ |
|||||||
|
import pyethash |
||||||
|
from random import randint |
||||||
|
|
||||||
|
def test_get_cache_size_not_None(): |
||||||
|
for _ in range(100): |
||||||
|
block_num = randint(0,12456789) |
||||||
|
out = pyethash.core.get_cache_size(block_num) |
||||||
|
assert out != None |
||||||
|
|
||||||
|
def test_get_full_size_not_None(): |
||||||
|
for _ in range(100): |
||||||
|
block_num = randint(0,12456789) |
||||||
|
out = pyethash.core.get_full_size(block_num) |
||||||
|
assert out != None |
||||||
|
|
||||||
|
def test_get_cache_size_based_on_EPOCH(): |
||||||
|
for _ in range(100): |
||||||
|
block_num = randint(0,12456789) |
||||||
|
out1 = pyethash.core.get_cache_size(block_num) |
||||||
|
out2 = pyethash.core.get_cache_size((block_num // pyethash.EPOCH_LENGTH) * pyethash.EPOCH_LENGTH) |
||||||
|
assert out1 == out2 |
||||||
|
|
||||||
|
def test_get_full_size_based_on_EPOCH(): |
||||||
|
for _ in range(100): |
||||||
|
block_num = randint(0,12456789) |
||||||
|
out1 = pyethash.core.get_full_size(block_num) |
||||||
|
out2 = pyethash.core.get_full_size((block_num // pyethash.EPOCH_LENGTH) * pyethash.EPOCH_LENGTH) |
||||||
|
assert out1 == out2 |
||||||
|
|
||||||
|
#def test_get_params_based_on_EPOCH(): |
||||||
|
# block_num = 123456 |
||||||
|
# out1 = pyethash.core.get_params(block_num) |
||||||
|
# out2 = pyethash.core.get_params((block_num // pyethash.EPOCH_LENGTH) * pyethash.EPOCH_LENGTH) |
||||||
|
# assert out1["DAG Size"] == out2["DAG Size"] |
||||||
|
# assert out1["Cache Size"] == out2["Cache Size"] |
||||||
|
# |
||||||
|
#def test_get_params_returns_different_values_based_on_different_block_input(): |
||||||
|
# out1 = pyethash.core.get_params(123456) |
||||||
|
# out2 = pyethash.core.get_params(12345) |
||||||
|
# assert out1["DAG Size"] != out2["DAG Size"] |
||||||
|
# assert out1["Cache Size"] != out2["Cache Size"] |
||||||
|
# |
||||||
|
#def test_get_cache_smoke_test(): |
||||||
|
# params = pyethash.core.get_params(123456) |
||||||
|
# assert pyethash.core.mkcache(params, "~~~~") != None |
@ -0,0 +1,26 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
# Strict mode |
||||||
|
set -e |
||||||
|
|
||||||
|
SOURCE="${BASH_SOURCE[0]}" |
||||||
|
while [ -h "$SOURCE" ]; do |
||||||
|
DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" |
||||||
|
SOURCE="$(readlink "$SOURCE")" |
||||||
|
[[ $SOURCE != /* ]] && SOURCE="$DIR/$SOURCE" |
||||||
|
done |
||||||
|
TEST_DIR="$( cd -P "$( dirname "$SOURCE" )" && pwd )" |
||||||
|
|
||||||
|
echo -e "\n################# Testing JS ##################" |
||||||
|
# TODO: Use mocha and real testing tools instead of rolling our own |
||||||
|
cd $TEST_DIR/../js |
||||||
|
node test.js |
||||||
|
|
||||||
|
echo -e "\n################# Testing C ##################" |
||||||
|
$TEST_DIR/c/test.sh |
||||||
|
|
||||||
|
echo -e "\n################# Testing Python ##################" |
||||||
|
$TEST_DIR/python/test.sh |
||||||
|
|
||||||
|
#echo "################# Testing Go ##################" |
||||||
|
#$TEST_DIR/go/test.sh |
@ -0,0 +1,21 @@ |
|||||||
|
Copyright © 2012 Peter Harris |
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a |
||||||
|
copy of this software and associated documentation files (the "Software"), |
||||||
|
to deal in the Software without restriction, including without limitation |
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, |
||||||
|
and/or sell copies of the Software, and to permit persons to whom the |
||||||
|
Software is furnished to do so, subject to the following conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next |
||||||
|
paragraph) shall be included in all copies or substantial portions of the |
||||||
|
Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
||||||
|
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||||
|
DEALINGS IN THE SOFTWARE. |
||||||
|
|
@ -0,0 +1,95 @@ |
|||||||
|
Liner |
||||||
|
===== |
||||||
|
|
||||||
|
Liner is a command line editor with history. It was inspired by linenoise; |
||||||
|
everything Unix-like is a VT100 (or is trying very hard to be). If your |
||||||
|
terminal is not pretending to be a VT100, change it. Liner also support |
||||||
|
Windows. |
||||||
|
|
||||||
|
Liner is released under the X11 license (which is similar to the new BSD |
||||||
|
license). |
||||||
|
|
||||||
|
Line Editing |
||||||
|
------------ |
||||||
|
|
||||||
|
The following line editing commands are supported on platforms and terminals |
||||||
|
that Liner supports: |
||||||
|
|
||||||
|
Keystroke | Action |
||||||
|
--------- | ------ |
||||||
|
Ctrl-A, Home | Move cursor to beginning of line |
||||||
|
Ctrl-E, End | Move cursor to end of line |
||||||
|
Ctrl-B, Left | Move cursor one character left |
||||||
|
Ctrl-F, Right| Move cursor one character right |
||||||
|
Ctrl-Left | Move cursor to previous word |
||||||
|
Ctrl-Right | Move cursor to next word |
||||||
|
Ctrl-D, Del | (if line is *not* empty) Delete character under cursor |
||||||
|
Ctrl-D | (if line *is* empty) End of File - usually quits application |
||||||
|
Ctrl-C | Reset input (create new empty prompt) |
||||||
|
Ctrl-L | Clear screen (line is unmodified) |
||||||
|
Ctrl-T | Transpose previous character with current character |
||||||
|
Ctrl-H, BackSpace | Delete character before cursor |
||||||
|
Ctrl-W | Delete word leading up to cursor |
||||||
|
Ctrl-K | Delete from cursor to end of line |
||||||
|
Ctrl-U | Delete from start of line to cursor |
||||||
|
Ctrl-P, Up | Previous match from history |
||||||
|
Ctrl-N, Down | Next match from history |
||||||
|
Ctrl-R | Reverse Search history (Ctrl-S forward, Ctrl-G cancel) |
||||||
|
Ctrl-Y | Paste from Yank buffer (Alt-Y to paste next yank instead) |
||||||
|
Tab | Next completion |
||||||
|
Shift-Tab | (after Tab) Previous completion |
||||||
|
|
||||||
|
Getting started |
||||||
|
----------------- |
||||||
|
|
||||||
|
```go |
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"log" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/peterh/liner" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
history_fn = "/tmp/.liner_history" |
||||||
|
names = []string{"john", "james", "mary", "nancy"} |
||||||
|
) |
||||||
|
|
||||||
|
func main() { |
||||||
|
line := liner.NewLiner() |
||||||
|
defer line.Close() |
||||||
|
|
||||||
|
line.SetCompleter(func(line string) (c []string) { |
||||||
|
for _, n := range names { |
||||||
|
if strings.HasPrefix(n, strings.ToLower(line)) { |
||||||
|
c = append(c, n) |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
}) |
||||||
|
|
||||||
|
if f, err := os.Open(history_fn); err == nil { |
||||||
|
line.ReadHistory(f) |
||||||
|
f.Close() |
||||||
|
} |
||||||
|
|
||||||
|
if name, err := line.Prompt("What is your name? "); err != nil { |
||||||
|
log.Print("Error reading line: ", err) |
||||||
|
} else { |
||||||
|
log.Print("Got: ", name) |
||||||
|
line.AppendHistory(name) |
||||||
|
} |
||||||
|
|
||||||
|
if f, err := os.Create(history_fn); err != nil { |
||||||
|
log.Print("Error writing history file: ", err) |
||||||
|
} else { |
||||||
|
line.WriteHistory(f) |
||||||
|
f.Close() |
||||||
|
} |
||||||
|
} |
||||||
|
``` |
||||||
|
|
||||||
|
For documentation, see http://godoc.org/github.com/peterh/liner |
@ -0,0 +1,39 @@ |
|||||||
|
// +build openbsd freebsd netbsd
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import "syscall" |
||||||
|
|
||||||
|
const ( |
||||||
|
getTermios = syscall.TIOCGETA |
||||||
|
setTermios = syscall.TIOCSETA |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// Input flags
|
||||||
|
inpck = 0x010 |
||||||
|
istrip = 0x020 |
||||||
|
icrnl = 0x100 |
||||||
|
ixon = 0x200 |
||||||
|
|
||||||
|
// Output flags
|
||||||
|
opost = 0x1 |
||||||
|
|
||||||
|
// Control flags
|
||||||
|
cs8 = 0x300 |
||||||
|
|
||||||
|
// Local flags
|
||||||
|
isig = 0x080 |
||||||
|
icanon = 0x100 |
||||||
|
iexten = 0x400 |
||||||
|
) |
||||||
|
|
||||||
|
type termios struct { |
||||||
|
Iflag uint32 |
||||||
|
Oflag uint32 |
||||||
|
Cflag uint32 |
||||||
|
Lflag uint32 |
||||||
|
Cc [20]byte |
||||||
|
Ispeed int32 |
||||||
|
Ospeed int32 |
||||||
|
} |
@ -0,0 +1,219 @@ |
|||||||
|
/* |
||||||
|
Package liner implements a simple command line editor, inspired by linenoise |
||||||
|
(https://github.com/antirez/linenoise/). This package supports WIN32 in
|
||||||
|
addition to the xterm codes supported by everything else. |
||||||
|
*/ |
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"bytes" |
||||||
|
"container/ring" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type commonState struct { |
||||||
|
terminalSupported bool |
||||||
|
outputRedirected bool |
||||||
|
inputRedirected bool |
||||||
|
history []string |
||||||
|
historyMutex sync.RWMutex |
||||||
|
completer WordCompleter |
||||||
|
columns int |
||||||
|
killRing *ring.Ring |
||||||
|
ctrlCAborts bool |
||||||
|
r *bufio.Reader |
||||||
|
tabStyle TabStyle |
||||||
|
} |
||||||
|
|
||||||
|
// TabStyle is used to select how tab completions are displayed.
|
||||||
|
type TabStyle int |
||||||
|
|
||||||
|
// Two tab styles are currently available:
|
||||||
|
//
|
||||||
|
// TabCircular cycles through each completion item and displays it directly on
|
||||||
|
// the prompt
|
||||||
|
//
|
||||||
|
// TabPrints prints the list of completion items to the screen after a second
|
||||||
|
// tab key is pressed. This behaves similar to GNU readline and BASH (which
|
||||||
|
// uses readline)
|
||||||
|
const ( |
||||||
|
TabCircular TabStyle = iota |
||||||
|
TabPrints |
||||||
|
) |
||||||
|
|
||||||
|
// ErrPromptAborted is returned from Prompt or PasswordPrompt when the user presses Ctrl-C
|
||||||
|
// if SetCtrlCAborts(true) has been called on the State
|
||||||
|
var ErrPromptAborted = errors.New("prompt aborted") |
||||||
|
|
||||||
|
// ErrNotTerminalOutput is returned from Prompt or PasswordPrompt if the
|
||||||
|
// platform is normally supported, but stdout has been redirected
|
||||||
|
var ErrNotTerminalOutput = errors.New("standard output is not a terminal") |
||||||
|
|
||||||
|
// Max elements to save on the killring
|
||||||
|
const KillRingMax = 60 |
||||||
|
|
||||||
|
// HistoryLimit is the maximum number of entries saved in the scrollback history.
|
||||||
|
const HistoryLimit = 1000 |
||||||
|
|
||||||
|
// ReadHistory reads scrollback history from r. Returns the number of lines
|
||||||
|
// read, and any read error (except io.EOF).
|
||||||
|
func (s *State) ReadHistory(r io.Reader) (num int, err error) { |
||||||
|
s.historyMutex.Lock() |
||||||
|
defer s.historyMutex.Unlock() |
||||||
|
|
||||||
|
in := bufio.NewReader(r) |
||||||
|
num = 0 |
||||||
|
for { |
||||||
|
line, part, err := in.ReadLine() |
||||||
|
if err == io.EOF { |
||||||
|
break |
||||||
|
} |
||||||
|
if err != nil { |
||||||
|
return num, err |
||||||
|
} |
||||||
|
if part { |
||||||
|
return num, fmt.Errorf("line %d is too long", num+1) |
||||||
|
} |
||||||
|
if !utf8.Valid(line) { |
||||||
|
return num, fmt.Errorf("invalid string at line %d", num+1) |
||||||
|
} |
||||||
|
num++ |
||||||
|
s.history = append(s.history, string(line)) |
||||||
|
if len(s.history) > HistoryLimit { |
||||||
|
s.history = s.history[1:] |
||||||
|
} |
||||||
|
} |
||||||
|
return num, nil |
||||||
|
} |
||||||
|
|
||||||
|
// WriteHistory writes scrollback history to w. Returns the number of lines
|
||||||
|
// successfully written, and any write error.
|
||||||
|
//
|
||||||
|
// Unlike the rest of liner's API, WriteHistory is safe to call
|
||||||
|
// from another goroutine while Prompt is in progress.
|
||||||
|
// This exception is to facilitate the saving of the history buffer
|
||||||
|
// during an unexpected exit (for example, due to Ctrl-C being invoked)
|
||||||
|
func (s *State) WriteHistory(w io.Writer) (num int, err error) { |
||||||
|
s.historyMutex.RLock() |
||||||
|
defer s.historyMutex.RUnlock() |
||||||
|
|
||||||
|
for _, item := range s.history { |
||||||
|
_, err := fmt.Fprintln(w, item) |
||||||
|
if err != nil { |
||||||
|
return num, err |
||||||
|
} |
||||||
|
num++ |
||||||
|
} |
||||||
|
return num, nil |
||||||
|
} |
||||||
|
|
||||||
|
// AppendHistory appends an entry to the scrollback history. AppendHistory
|
||||||
|
// should be called iff Prompt returns a valid command.
|
||||||
|
func (s *State) AppendHistory(item string) { |
||||||
|
s.historyMutex.Lock() |
||||||
|
defer s.historyMutex.Unlock() |
||||||
|
|
||||||
|
if len(s.history) > 0 { |
||||||
|
if item == s.history[len(s.history)-1] { |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
s.history = append(s.history, item) |
||||||
|
if len(s.history) > HistoryLimit { |
||||||
|
s.history = s.history[1:] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the history lines starting with prefix
|
||||||
|
func (s *State) getHistoryByPrefix(prefix string) (ph []string) { |
||||||
|
for _, h := range s.history { |
||||||
|
if strings.HasPrefix(h, prefix) { |
||||||
|
ph = append(ph, h) |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Returns the history lines matching the inteligent search
|
||||||
|
func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) { |
||||||
|
if pattern == "" { |
||||||
|
return |
||||||
|
} |
||||||
|
for _, h := range s.history { |
||||||
|
if i := strings.Index(h, pattern); i >= 0 { |
||||||
|
ph = append(ph, h) |
||||||
|
pos = append(pos, i) |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// Completer takes the currently edited line content at the left of the cursor
|
||||||
|
// and returns a list of completion candidates.
|
||||||
|
// If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed
|
||||||
|
// to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!".
|
||||||
|
type Completer func(line string) []string |
||||||
|
|
||||||
|
// WordCompleter takes the currently edited line with the cursor position and
|
||||||
|
// returns the completion candidates for the partial word to be completed.
|
||||||
|
// If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed
|
||||||
|
// to the completer which may returns ("Hello, ", {"world", "Word"}, "!!!") to have "Hello, world!!!".
|
||||||
|
type WordCompleter func(line string, pos int) (head string, completions []string, tail string) |
||||||
|
|
||||||
|
// SetCompleter sets the completion function that Liner will call to
|
||||||
|
// fetch completion candidates when the user presses tab.
|
||||||
|
func (s *State) SetCompleter(f Completer) { |
||||||
|
if f == nil { |
||||||
|
s.completer = nil |
||||||
|
return |
||||||
|
} |
||||||
|
s.completer = func(line string, pos int) (string, []string, string) { |
||||||
|
return "", f(line[:pos]), line[pos:] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// SetWordCompleter sets the completion function that Liner will call to
|
||||||
|
// fetch completion candidates when the user presses tab.
|
||||||
|
func (s *State) SetWordCompleter(f WordCompleter) { |
||||||
|
s.completer = f |
||||||
|
} |
||||||
|
|
||||||
|
// SetTabCompletionStyle sets the behvavior when the Tab key is pressed
|
||||||
|
// for auto-completion. TabCircular is the default behavior and cycles
|
||||||
|
// through the list of candidates at the prompt. TabPrints will print
|
||||||
|
// the available completion candidates to the screen similar to BASH
|
||||||
|
// and GNU Readline
|
||||||
|
func (s *State) SetTabCompletionStyle(tabStyle TabStyle) { |
||||||
|
s.tabStyle = tabStyle |
||||||
|
} |
||||||
|
|
||||||
|
// ModeApplier is the interface that wraps a representation of the terminal
|
||||||
|
// mode. ApplyMode sets the terminal to this mode.
|
||||||
|
type ModeApplier interface { |
||||||
|
ApplyMode() error |
||||||
|
} |
||||||
|
|
||||||
|
// SetCtrlCAborts sets whether Prompt on a supported terminal will return an
|
||||||
|
// ErrPromptAborted when Ctrl-C is pressed. The default is false (will not
|
||||||
|
// return when Ctrl-C is pressed). Unsupported terminals typically raise SIGINT
|
||||||
|
// (and Prompt does not return) regardless of the value passed to SetCtrlCAborts.
|
||||||
|
func (s *State) SetCtrlCAborts(aborts bool) { |
||||||
|
s.ctrlCAborts = aborts |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) promptUnsupported(p string) (string, error) { |
||||||
|
if !s.inputRedirected { |
||||||
|
fmt.Print(p) |
||||||
|
} |
||||||
|
linebuf, _, err := s.r.ReadLine() |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
return string(bytes.TrimSpace(linebuf)), nil |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
// +build !windows,!linux,!darwin,!openbsd,!freebsd,!netbsd
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"errors" |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
// State represents an open terminal
|
||||||
|
type State struct { |
||||||
|
commonState |
||||||
|
} |
||||||
|
|
||||||
|
// Prompt displays p, and then waits for user input. Prompt does not support
|
||||||
|
// line editing on this operating system.
|
||||||
|
func (s *State) Prompt(p string) (string, error) { |
||||||
|
return s.promptUnsupported(p) |
||||||
|
} |
||||||
|
|
||||||
|
// PasswordPrompt is not supported in this OS.
|
||||||
|
func (s *State) PasswordPrompt(p string) (string, error) { |
||||||
|
return "", errors.New("liner: function not supported in this terminal") |
||||||
|
} |
||||||
|
|
||||||
|
// NewLiner initializes a new *State
|
||||||
|
//
|
||||||
|
// Note that this operating system uses a fallback mode without line
|
||||||
|
// editing. Patches welcome.
|
||||||
|
func NewLiner() *State { |
||||||
|
var s State |
||||||
|
s.r = bufio.NewReader(os.Stdin) |
||||||
|
return &s |
||||||
|
} |
||||||
|
|
||||||
|
// Close returns the terminal to its previous mode
|
||||||
|
func (s *State) Close() error { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// TerminalSupported returns false because line editing is not
|
||||||
|
// supported on this platform.
|
||||||
|
func TerminalSupported() bool { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
type noopMode struct{} |
||||||
|
|
||||||
|
func (n noopMode) ApplyMode() error { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// TerminalMode returns a noop InputModeSetter on this platform.
|
||||||
|
func TerminalMode() (ModeApplier, error) { |
||||||
|
return noopMode{}, nil |
||||||
|
} |
@ -0,0 +1,359 @@ |
|||||||
|
// +build linux darwin openbsd freebsd netbsd
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"errors" |
||||||
|
"os" |
||||||
|
"os/signal" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"syscall" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type nexter struct { |
||||||
|
r rune |
||||||
|
err error |
||||||
|
} |
||||||
|
|
||||||
|
// State represents an open terminal
|
||||||
|
type State struct { |
||||||
|
commonState |
||||||
|
origMode termios |
||||||
|
defaultMode termios |
||||||
|
next <-chan nexter |
||||||
|
winch chan os.Signal |
||||||
|
pending []rune |
||||||
|
useCHA bool |
||||||
|
} |
||||||
|
|
||||||
|
// NewLiner initializes a new *State, and sets the terminal into raw mode. To
|
||||||
|
// restore the terminal to its previous state, call State.Close().
|
||||||
|
//
|
||||||
|
// Note if you are still using Go 1.0: NewLiner handles SIGWINCH, so it will
|
||||||
|
// leak a channel every time you call it. Therefore, it is recommened that you
|
||||||
|
// upgrade to a newer release of Go, or ensure that NewLiner is only called
|
||||||
|
// once.
|
||||||
|
func NewLiner() *State { |
||||||
|
var s State |
||||||
|
s.r = bufio.NewReader(os.Stdin) |
||||||
|
|
||||||
|
s.terminalSupported = TerminalSupported() |
||||||
|
if m, err := TerminalMode(); err == nil { |
||||||
|
s.origMode = *m.(*termios) |
||||||
|
} else { |
||||||
|
s.terminalSupported = false |
||||||
|
s.inputRedirected = true |
||||||
|
} |
||||||
|
if _, err := getMode(syscall.Stdout); err != 0 { |
||||||
|
s.terminalSupported = false |
||||||
|
s.outputRedirected = true |
||||||
|
} |
||||||
|
if s.terminalSupported { |
||||||
|
mode := s.origMode |
||||||
|
mode.Iflag &^= icrnl | inpck | istrip | ixon |
||||||
|
mode.Cflag |= cs8 |
||||||
|
mode.Lflag &^= syscall.ECHO | icanon | iexten |
||||||
|
mode.ApplyMode() |
||||||
|
|
||||||
|
winch := make(chan os.Signal, 1) |
||||||
|
signal.Notify(winch, syscall.SIGWINCH) |
||||||
|
s.winch = winch |
||||||
|
|
||||||
|
s.checkOutput() |
||||||
|
} |
||||||
|
|
||||||
|
if !s.outputRedirected { |
||||||
|
s.getColumns() |
||||||
|
s.outputRedirected = s.columns <= 0 |
||||||
|
} |
||||||
|
|
||||||
|
return &s |
||||||
|
} |
||||||
|
|
||||||
|
var errTimedOut = errors.New("timeout") |
||||||
|
|
||||||
|
func (s *State) startPrompt() { |
||||||
|
if s.terminalSupported { |
||||||
|
if m, err := TerminalMode(); err == nil { |
||||||
|
s.defaultMode = *m.(*termios) |
||||||
|
mode := s.defaultMode |
||||||
|
mode.Lflag &^= isig |
||||||
|
mode.ApplyMode() |
||||||
|
} |
||||||
|
} |
||||||
|
s.restartPrompt() |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) restartPrompt() { |
||||||
|
next := make(chan nexter) |
||||||
|
go func() { |
||||||
|
for { |
||||||
|
var n nexter |
||||||
|
n.r, _, n.err = s.r.ReadRune() |
||||||
|
next <- n |
||||||
|
// Shut down nexter loop when an end condition has been reached
|
||||||
|
if n.err != nil || n.r == '\n' || n.r == '\r' || n.r == ctrlC || n.r == ctrlD { |
||||||
|
close(next) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
}() |
||||||
|
s.next = next |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) stopPrompt() { |
||||||
|
if s.terminalSupported { |
||||||
|
s.defaultMode.ApplyMode() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) nextPending(timeout <-chan time.Time) (rune, error) { |
||||||
|
select { |
||||||
|
case thing, ok := <-s.next: |
||||||
|
if !ok { |
||||||
|
return 0, errors.New("liner: internal error") |
||||||
|
} |
||||||
|
if thing.err != nil { |
||||||
|
return 0, thing.err |
||||||
|
} |
||||||
|
s.pending = append(s.pending, thing.r) |
||||||
|
return thing.r, nil |
||||||
|
case <-timeout: |
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, errTimedOut |
||||||
|
} |
||||||
|
// not reached
|
||||||
|
return 0, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) readNext() (interface{}, error) { |
||||||
|
if len(s.pending) > 0 { |
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, nil |
||||||
|
} |
||||||
|
var r rune |
||||||
|
select { |
||||||
|
case thing, ok := <-s.next: |
||||||
|
if !ok { |
||||||
|
return 0, errors.New("liner: internal error") |
||||||
|
} |
||||||
|
if thing.err != nil { |
||||||
|
return nil, thing.err |
||||||
|
} |
||||||
|
r = thing.r |
||||||
|
case <-s.winch: |
||||||
|
s.getColumns() |
||||||
|
return winch, nil |
||||||
|
} |
||||||
|
if r != esc { |
||||||
|
return r, nil |
||||||
|
} |
||||||
|
s.pending = append(s.pending, r) |
||||||
|
|
||||||
|
// Wait at most 50 ms for the rest of the escape sequence
|
||||||
|
// If nothing else arrives, it was an actual press of the esc key
|
||||||
|
timeout := time.After(50 * time.Millisecond) |
||||||
|
flag, err := s.nextPending(timeout) |
||||||
|
if err != nil { |
||||||
|
if err == errTimedOut { |
||||||
|
return flag, nil |
||||||
|
} |
||||||
|
return unknown, err |
||||||
|
} |
||||||
|
|
||||||
|
switch flag { |
||||||
|
case '[': |
||||||
|
code, err := s.nextPending(timeout) |
||||||
|
if err != nil { |
||||||
|
if err == errTimedOut { |
||||||
|
return code, nil |
||||||
|
} |
||||||
|
return unknown, err |
||||||
|
} |
||||||
|
switch code { |
||||||
|
case 'A': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return up, nil |
||||||
|
case 'B': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return down, nil |
||||||
|
case 'C': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return right, nil |
||||||
|
case 'D': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return left, nil |
||||||
|
case 'F': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return end, nil |
||||||
|
case 'H': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return home, nil |
||||||
|
case 'Z': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return shiftTab, nil |
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': |
||||||
|
num := []rune{code} |
||||||
|
for { |
||||||
|
code, err := s.nextPending(timeout) |
||||||
|
if err != nil { |
||||||
|
if err == errTimedOut { |
||||||
|
return code, nil |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
switch code { |
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': |
||||||
|
num = append(num, code) |
||||||
|
case ';': |
||||||
|
// Modifier code to follow
|
||||||
|
// This only supports Ctrl-left and Ctrl-right for now
|
||||||
|
x, _ := strconv.ParseInt(string(num), 10, 32) |
||||||
|
if x != 1 { |
||||||
|
// Can't be left or right
|
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, nil |
||||||
|
} |
||||||
|
num = num[:0] |
||||||
|
for { |
||||||
|
code, err = s.nextPending(timeout) |
||||||
|
if err != nil { |
||||||
|
if err == errTimedOut { |
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, nil |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
switch code { |
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': |
||||||
|
num = append(num, code) |
||||||
|
case 'C', 'D': |
||||||
|
// right, left
|
||||||
|
mod, _ := strconv.ParseInt(string(num), 10, 32) |
||||||
|
if mod != 5 { |
||||||
|
// Not bare Ctrl
|
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, nil |
||||||
|
} |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
if code == 'C' { |
||||||
|
return wordRight, nil |
||||||
|
} |
||||||
|
return wordLeft, nil |
||||||
|
default: |
||||||
|
// Not left or right
|
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, nil |
||||||
|
} |
||||||
|
} |
||||||
|
case '~': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
x, _ := strconv.ParseInt(string(num), 10, 32) |
||||||
|
switch x { |
||||||
|
case 2: |
||||||
|
return insert, nil |
||||||
|
case 3: |
||||||
|
return del, nil |
||||||
|
case 5: |
||||||
|
return pageUp, nil |
||||||
|
case 6: |
||||||
|
return pageDown, nil |
||||||
|
case 7: |
||||||
|
return home, nil |
||||||
|
case 8: |
||||||
|
return end, nil |
||||||
|
case 15: |
||||||
|
return f5, nil |
||||||
|
case 17: |
||||||
|
return f6, nil |
||||||
|
case 18: |
||||||
|
return f7, nil |
||||||
|
case 19: |
||||||
|
return f8, nil |
||||||
|
case 20: |
||||||
|
return f9, nil |
||||||
|
case 21: |
||||||
|
return f10, nil |
||||||
|
case 23: |
||||||
|
return f11, nil |
||||||
|
case 24: |
||||||
|
return f12, nil |
||||||
|
default: |
||||||
|
return unknown, nil |
||||||
|
} |
||||||
|
default: |
||||||
|
// unrecognized escape code
|
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
case 'O': |
||||||
|
code, err := s.nextPending(timeout) |
||||||
|
if err != nil { |
||||||
|
if err == errTimedOut { |
||||||
|
return code, nil |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
switch code { |
||||||
|
case 'c': |
||||||
|
return wordRight, nil |
||||||
|
case 'd': |
||||||
|
return wordLeft, nil |
||||||
|
case 'H': |
||||||
|
return home, nil |
||||||
|
case 'F': |
||||||
|
return end, nil |
||||||
|
case 'P': |
||||||
|
return f1, nil |
||||||
|
case 'Q': |
||||||
|
return f2, nil |
||||||
|
case 'R': |
||||||
|
return f3, nil |
||||||
|
case 'S': |
||||||
|
return f4, nil |
||||||
|
default: |
||||||
|
return unknown, nil |
||||||
|
} |
||||||
|
case 'y': |
||||||
|
s.pending = s.pending[:0] // escape code complete
|
||||||
|
return altY, nil |
||||||
|
default: |
||||||
|
rv := s.pending[0] |
||||||
|
s.pending = s.pending[1:] |
||||||
|
return rv, nil |
||||||
|
} |
||||||
|
|
||||||
|
// not reached
|
||||||
|
return r, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Close returns the terminal to its previous mode
|
||||||
|
func (s *State) Close() error { |
||||||
|
stopSignal(s.winch) |
||||||
|
if s.terminalSupported { |
||||||
|
s.origMode.ApplyMode() |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// TerminalSupported returns true if the current terminal supports
|
||||||
|
// line editing features, and false if liner will use the 'dumb'
|
||||||
|
// fallback for input.
|
||||||
|
func TerminalSupported() bool { |
||||||
|
bad := map[string]bool{"": true, "dumb": true, "cons25": true} |
||||||
|
return !bad[strings.ToLower(os.Getenv("TERM"))] |
||||||
|
} |
@ -0,0 +1,39 @@ |
|||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import "syscall" |
||||||
|
|
||||||
|
const ( |
||||||
|
getTermios = syscall.TIOCGETA |
||||||
|
setTermios = syscall.TIOCSETA |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
// Input flags
|
||||||
|
inpck = 0x010 |
||||||
|
istrip = 0x020 |
||||||
|
icrnl = 0x100 |
||||||
|
ixon = 0x200 |
||||||
|
|
||||||
|
// Output flags
|
||||||
|
opost = 0x1 |
||||||
|
|
||||||
|
// Control flags
|
||||||
|
cs8 = 0x300 |
||||||
|
|
||||||
|
// Local flags
|
||||||
|
isig = 0x080 |
||||||
|
icanon = 0x100 |
||||||
|
iexten = 0x400 |
||||||
|
) |
||||||
|
|
||||||
|
type termios struct { |
||||||
|
Iflag uintptr |
||||||
|
Oflag uintptr |
||||||
|
Cflag uintptr |
||||||
|
Lflag uintptr |
||||||
|
Cc [20]byte |
||||||
|
Ispeed uintptr |
||||||
|
Ospeed uintptr |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import "syscall" |
||||||
|
|
||||||
|
const ( |
||||||
|
getTermios = syscall.TCGETS |
||||||
|
setTermios = syscall.TCSETS |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
icrnl = syscall.ICRNL |
||||||
|
inpck = syscall.INPCK |
||||||
|
istrip = syscall.ISTRIP |
||||||
|
ixon = syscall.IXON |
||||||
|
opost = syscall.OPOST |
||||||
|
cs8 = syscall.CS8 |
||||||
|
isig = syscall.ISIG |
||||||
|
icanon = syscall.ICANON |
||||||
|
iexten = syscall.IEXTEN |
||||||
|
) |
||||||
|
|
||||||
|
type termios struct { |
||||||
|
syscall.Termios |
||||||
|
} |
@ -0,0 +1,61 @@ |
|||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"bytes" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func (s *State) expectRune(t *testing.T, r rune) { |
||||||
|
item, err := s.readNext() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("Expected rune '%c', got error %s\n", r, err) |
||||||
|
} |
||||||
|
if v, ok := item.(rune); !ok { |
||||||
|
t.Fatalf("Expected rune '%c', got non-rune %v\n", r, v) |
||||||
|
} else { |
||||||
|
if v != r { |
||||||
|
t.Fatalf("Expected rune '%c', got rune '%c'\n", r, v) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) expectAction(t *testing.T, a action) { |
||||||
|
item, err := s.readNext() |
||||||
|
if err != nil { |
||||||
|
t.Fatalf("Expected Action %d, got error %s\n", a, err) |
||||||
|
} |
||||||
|
if v, ok := item.(action); !ok { |
||||||
|
t.Fatalf("Expected Action %d, got non-Action %v\n", a, v) |
||||||
|
} else { |
||||||
|
if v != a { |
||||||
|
t.Fatalf("Expected Action %d, got Action %d\n", a, v) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestTypes(t *testing.T) { |
||||||
|
input := []byte{'A', 27, 'B', 27, 91, 68, 27, '[', '1', ';', '5', 'D', 'e'} |
||||||
|
var s State |
||||||
|
s.r = bufio.NewReader(bytes.NewBuffer(input)) |
||||||
|
|
||||||
|
next := make(chan nexter) |
||||||
|
go func() { |
||||||
|
for { |
||||||
|
var n nexter |
||||||
|
n.r, _, n.err = s.r.ReadRune() |
||||||
|
next <- n |
||||||
|
} |
||||||
|
}() |
||||||
|
s.next = next |
||||||
|
|
||||||
|
s.expectRune(t, 'A') |
||||||
|
s.expectRune(t, 27) |
||||||
|
s.expectRune(t, 'B') |
||||||
|
s.expectAction(t, left) |
||||||
|
s.expectAction(t, wordLeft) |
||||||
|
|
||||||
|
s.expectRune(t, 'e') |
||||||
|
} |
@ -0,0 +1,313 @@ |
|||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"os" |
||||||
|
"syscall" |
||||||
|
"unsafe" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
kernel32 = syscall.NewLazyDLL("kernel32.dll") |
||||||
|
|
||||||
|
procGetStdHandle = kernel32.NewProc("GetStdHandle") |
||||||
|
procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW") |
||||||
|
procGetConsoleMode = kernel32.NewProc("GetConsoleMode") |
||||||
|
procSetConsoleMode = kernel32.NewProc("SetConsoleMode") |
||||||
|
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") |
||||||
|
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") |
||||||
|
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") |
||||||
|
) |
||||||
|
|
||||||
|
// These names are from the Win32 api, so they use underscores (contrary to
|
||||||
|
// what golint suggests)
|
||||||
|
const ( |
||||||
|
std_input_handle = uint32(-10 & 0xFFFFFFFF) |
||||||
|
std_output_handle = uint32(-11 & 0xFFFFFFFF) |
||||||
|
std_error_handle = uint32(-12 & 0xFFFFFFFF) |
||||||
|
invalid_handle_value = ^uintptr(0) |
||||||
|
) |
||||||
|
|
||||||
|
type inputMode uint32 |
||||||
|
|
||||||
|
// State represents an open terminal
|
||||||
|
type State struct { |
||||||
|
commonState |
||||||
|
handle syscall.Handle |
||||||
|
hOut syscall.Handle |
||||||
|
origMode inputMode |
||||||
|
defaultMode inputMode |
||||||
|
key interface{} |
||||||
|
repeat uint16 |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
enableEchoInput = 0x4 |
||||||
|
enableInsertMode = 0x20 |
||||||
|
enableLineInput = 0x2 |
||||||
|
enableMouseInput = 0x10 |
||||||
|
enableProcessedInput = 0x1 |
||||||
|
enableQuickEditMode = 0x40 |
||||||
|
enableWindowInput = 0x8 |
||||||
|
) |
||||||
|
|
||||||
|
// NewLiner initializes a new *State, and sets the terminal into raw mode. To
|
||||||
|
// restore the terminal to its previous state, call State.Close().
|
||||||
|
func NewLiner() *State { |
||||||
|
var s State |
||||||
|
hIn, _, _ := procGetStdHandle.Call(uintptr(std_input_handle)) |
||||||
|
s.handle = syscall.Handle(hIn) |
||||||
|
hOut, _, _ := procGetStdHandle.Call(uintptr(std_output_handle)) |
||||||
|
s.hOut = syscall.Handle(hOut) |
||||||
|
|
||||||
|
s.terminalSupported = true |
||||||
|
if m, err := TerminalMode(); err == nil { |
||||||
|
s.origMode = m.(inputMode) |
||||||
|
mode := s.origMode |
||||||
|
mode &^= enableEchoInput |
||||||
|
mode &^= enableInsertMode |
||||||
|
mode &^= enableLineInput |
||||||
|
mode &^= enableMouseInput |
||||||
|
mode |= enableWindowInput |
||||||
|
mode.ApplyMode() |
||||||
|
} else { |
||||||
|
s.inputRedirected = true |
||||||
|
s.r = bufio.NewReader(os.Stdin) |
||||||
|
} |
||||||
|
|
||||||
|
s.getColumns() |
||||||
|
s.outputRedirected = s.columns <= 0 |
||||||
|
|
||||||
|
return &s |
||||||
|
} |
||||||
|
|
||||||
|
// These names are from the Win32 api, so they use underscores (contrary to
|
||||||
|
// what golint suggests)
|
||||||
|
const ( |
||||||
|
focus_event = 0x0010 |
||||||
|
key_event = 0x0001 |
||||||
|
menu_event = 0x0008 |
||||||
|
mouse_event = 0x0002 |
||||||
|
window_buffer_size_event = 0x0004 |
||||||
|
) |
||||||
|
|
||||||
|
type input_record struct { |
||||||
|
eventType uint16 |
||||||
|
pad uint16 |
||||||
|
blob [16]byte |
||||||
|
} |
||||||
|
|
||||||
|
type key_event_record struct { |
||||||
|
KeyDown int32 |
||||||
|
RepeatCount uint16 |
||||||
|
VirtualKeyCode uint16 |
||||||
|
VirtualScanCode uint16 |
||||||
|
Char int16 |
||||||
|
ControlKeyState uint32 |
||||||
|
} |
||||||
|
|
||||||
|
// These names are from the Win32 api, so they use underscores (contrary to
|
||||||
|
// what golint suggests)
|
||||||
|
const ( |
||||||
|
vk_tab = 0x09 |
||||||
|
vk_prior = 0x21 |
||||||
|
vk_next = 0x22 |
||||||
|
vk_end = 0x23 |
||||||
|
vk_home = 0x24 |
||||||
|
vk_left = 0x25 |
||||||
|
vk_up = 0x26 |
||||||
|
vk_right = 0x27 |
||||||
|
vk_down = 0x28 |
||||||
|
vk_insert = 0x2d |
||||||
|
vk_delete = 0x2e |
||||||
|
vk_f1 = 0x70 |
||||||
|
vk_f2 = 0x71 |
||||||
|
vk_f3 = 0x72 |
||||||
|
vk_f4 = 0x73 |
||||||
|
vk_f5 = 0x74 |
||||||
|
vk_f6 = 0x75 |
||||||
|
vk_f7 = 0x76 |
||||||
|
vk_f8 = 0x77 |
||||||
|
vk_f9 = 0x78 |
||||||
|
vk_f10 = 0x79 |
||||||
|
vk_f11 = 0x7a |
||||||
|
vk_f12 = 0x7b |
||||||
|
yKey = 0x59 |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
shiftPressed = 0x0010 |
||||||
|
leftAltPressed = 0x0002 |
||||||
|
leftCtrlPressed = 0x0008 |
||||||
|
rightAltPressed = 0x0001 |
||||||
|
rightCtrlPressed = 0x0004 |
||||||
|
|
||||||
|
modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed |
||||||
|
) |
||||||
|
|
||||||
|
func (s *State) readNext() (interface{}, error) { |
||||||
|
if s.repeat > 0 { |
||||||
|
s.repeat-- |
||||||
|
return s.key, nil |
||||||
|
} |
||||||
|
|
||||||
|
var input input_record |
||||||
|
pbuf := uintptr(unsafe.Pointer(&input)) |
||||||
|
var rv uint32 |
||||||
|
prv := uintptr(unsafe.Pointer(&rv)) |
||||||
|
|
||||||
|
for { |
||||||
|
ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv) |
||||||
|
|
||||||
|
if ok == 0 { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if input.eventType == window_buffer_size_event { |
||||||
|
xy := (*coord)(unsafe.Pointer(&input.blob[0])) |
||||||
|
s.columns = int(xy.x) |
||||||
|
return winch, nil |
||||||
|
} |
||||||
|
if input.eventType != key_event { |
||||||
|
continue |
||||||
|
} |
||||||
|
ke := (*key_event_record)(unsafe.Pointer(&input.blob[0])) |
||||||
|
if ke.KeyDown == 0 { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
if ke.VirtualKeyCode == vk_tab && ke.ControlKeyState&modKeys == shiftPressed { |
||||||
|
s.key = shiftTab |
||||||
|
} else if ke.VirtualKeyCode == yKey && (ke.ControlKeyState&modKeys == leftAltPressed || |
||||||
|
ke.ControlKeyState&modKeys == rightAltPressed) { |
||||||
|
s.key = altY |
||||||
|
} else if ke.Char > 0 { |
||||||
|
s.key = rune(ke.Char) |
||||||
|
} else { |
||||||
|
switch ke.VirtualKeyCode { |
||||||
|
case vk_prior: |
||||||
|
s.key = pageUp |
||||||
|
case vk_next: |
||||||
|
s.key = pageDown |
||||||
|
case vk_end: |
||||||
|
s.key = end |
||||||
|
case vk_home: |
||||||
|
s.key = home |
||||||
|
case vk_left: |
||||||
|
s.key = left |
||||||
|
if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 { |
||||||
|
if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) { |
||||||
|
s.key = wordLeft |
||||||
|
} |
||||||
|
} |
||||||
|
case vk_right: |
||||||
|
s.key = right |
||||||
|
if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 { |
||||||
|
if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) { |
||||||
|
s.key = wordRight |
||||||
|
} |
||||||
|
} |
||||||
|
case vk_up: |
||||||
|
s.key = up |
||||||
|
case vk_down: |
||||||
|
s.key = down |
||||||
|
case vk_insert: |
||||||
|
s.key = insert |
||||||
|
case vk_delete: |
||||||
|
s.key = del |
||||||
|
case vk_f1: |
||||||
|
s.key = f1 |
||||||
|
case vk_f2: |
||||||
|
s.key = f2 |
||||||
|
case vk_f3: |
||||||
|
s.key = f3 |
||||||
|
case vk_f4: |
||||||
|
s.key = f4 |
||||||
|
case vk_f5: |
||||||
|
s.key = f5 |
||||||
|
case vk_f6: |
||||||
|
s.key = f6 |
||||||
|
case vk_f7: |
||||||
|
s.key = f7 |
||||||
|
case vk_f8: |
||||||
|
s.key = f8 |
||||||
|
case vk_f9: |
||||||
|
s.key = f9 |
||||||
|
case vk_f10: |
||||||
|
s.key = f10 |
||||||
|
case vk_f11: |
||||||
|
s.key = f11 |
||||||
|
case vk_f12: |
||||||
|
s.key = f12 |
||||||
|
default: |
||||||
|
// Eat modifier keys
|
||||||
|
// TODO: return Action(Unknown) if the key isn't a
|
||||||
|
// modifier.
|
||||||
|
continue |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ke.RepeatCount > 1 { |
||||||
|
s.repeat = ke.RepeatCount - 1 |
||||||
|
} |
||||||
|
return s.key, nil |
||||||
|
} |
||||||
|
return unknown, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Close returns the terminal to its previous mode
|
||||||
|
func (s *State) Close() error { |
||||||
|
s.origMode.ApplyMode() |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) startPrompt() { |
||||||
|
if m, err := TerminalMode(); err == nil { |
||||||
|
s.defaultMode = m.(inputMode) |
||||||
|
mode := s.defaultMode |
||||||
|
mode &^= enableProcessedInput |
||||||
|
mode.ApplyMode() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) restartPrompt() { |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) stopPrompt() { |
||||||
|
s.defaultMode.ApplyMode() |
||||||
|
} |
||||||
|
|
||||||
|
// TerminalSupported returns true because line editing is always
|
||||||
|
// supported on Windows.
|
||||||
|
func TerminalSupported() bool { |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
func (mode inputMode) ApplyMode() error { |
||||||
|
hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle)) |
||||||
|
if hIn == invalid_handle_value || hIn == 0 { |
||||||
|
return err |
||||||
|
} |
||||||
|
ok, _, err := procSetConsoleMode.Call(hIn, uintptr(mode)) |
||||||
|
if ok != 0 { |
||||||
|
err = nil |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// TerminalMode returns the current terminal input mode as an InputModeSetter.
|
||||||
|
//
|
||||||
|
// This function is provided for convenience, and should
|
||||||
|
// not be necessary for most users of liner.
|
||||||
|
func TerminalMode() (ModeApplier, error) { |
||||||
|
var mode inputMode |
||||||
|
hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle)) |
||||||
|
if hIn == invalid_handle_value || hIn == 0 { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
ok, _, err := procGetConsoleMode.Call(hIn, uintptr(unsafe.Pointer(&mode))) |
||||||
|
if ok != 0 { |
||||||
|
err = nil |
||||||
|
} |
||||||
|
return mode, err |
||||||
|
} |
@ -0,0 +1,864 @@ |
|||||||
|
// +build windows linux darwin openbsd freebsd netbsd
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"container/ring" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"strings" |
||||||
|
"unicode" |
||||||
|
"unicode/utf8" |
||||||
|
) |
||||||
|
|
||||||
|
type action int |
||||||
|
|
||||||
|
const ( |
||||||
|
left action = iota |
||||||
|
right |
||||||
|
up |
||||||
|
down |
||||||
|
home |
||||||
|
end |
||||||
|
insert |
||||||
|
del |
||||||
|
pageUp |
||||||
|
pageDown |
||||||
|
f1 |
||||||
|
f2 |
||||||
|
f3 |
||||||
|
f4 |
||||||
|
f5 |
||||||
|
f6 |
||||||
|
f7 |
||||||
|
f8 |
||||||
|
f9 |
||||||
|
f10 |
||||||
|
f11 |
||||||
|
f12 |
||||||
|
altY |
||||||
|
shiftTab |
||||||
|
wordLeft |
||||||
|
wordRight |
||||||
|
winch |
||||||
|
unknown |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
ctrlA = 1 |
||||||
|
ctrlB = 2 |
||||||
|
ctrlC = 3 |
||||||
|
ctrlD = 4 |
||||||
|
ctrlE = 5 |
||||||
|
ctrlF = 6 |
||||||
|
ctrlG = 7 |
||||||
|
ctrlH = 8 |
||||||
|
tab = 9 |
||||||
|
lf = 10 |
||||||
|
ctrlK = 11 |
||||||
|
ctrlL = 12 |
||||||
|
cr = 13 |
||||||
|
ctrlN = 14 |
||||||
|
ctrlO = 15 |
||||||
|
ctrlP = 16 |
||||||
|
ctrlQ = 17 |
||||||
|
ctrlR = 18 |
||||||
|
ctrlS = 19 |
||||||
|
ctrlT = 20 |
||||||
|
ctrlU = 21 |
||||||
|
ctrlV = 22 |
||||||
|
ctrlW = 23 |
||||||
|
ctrlX = 24 |
||||||
|
ctrlY = 25 |
||||||
|
ctrlZ = 26 |
||||||
|
esc = 27 |
||||||
|
bs = 127 |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
beep = "\a" |
||||||
|
) |
||||||
|
|
||||||
|
type tabDirection int |
||||||
|
|
||||||
|
const ( |
||||||
|
tabForward tabDirection = iota |
||||||
|
tabReverse |
||||||
|
) |
||||||
|
|
||||||
|
func (s *State) refresh(prompt []rune, buf []rune, pos int) error { |
||||||
|
s.cursorPos(0) |
||||||
|
_, err := fmt.Print(string(prompt)) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
pLen := countGlyphs(prompt) |
||||||
|
bLen := countGlyphs(buf) |
||||||
|
pos = countGlyphs(buf[:pos]) |
||||||
|
if pLen+bLen < s.columns { |
||||||
|
_, err = fmt.Print(string(buf)) |
||||||
|
s.eraseLine() |
||||||
|
s.cursorPos(pLen + pos) |
||||||
|
} else { |
||||||
|
// Find space available
|
||||||
|
space := s.columns - pLen |
||||||
|
space-- // space for cursor
|
||||||
|
start := pos - space/2 |
||||||
|
end := start + space |
||||||
|
if end > bLen { |
||||||
|
end = bLen |
||||||
|
start = end - space |
||||||
|
} |
||||||
|
if start < 0 { |
||||||
|
start = 0 |
||||||
|
end = space |
||||||
|
} |
||||||
|
pos -= start |
||||||
|
|
||||||
|
// Leave space for markers
|
||||||
|
if start > 0 { |
||||||
|
start++ |
||||||
|
} |
||||||
|
if end < bLen { |
||||||
|
end-- |
||||||
|
} |
||||||
|
startRune := len(getPrefixGlyphs(buf, start)) |
||||||
|
line := getPrefixGlyphs(buf[startRune:], end-start) |
||||||
|
|
||||||
|
// Output
|
||||||
|
if start > 0 { |
||||||
|
fmt.Print("{") |
||||||
|
} |
||||||
|
fmt.Print(string(line)) |
||||||
|
if end < bLen { |
||||||
|
fmt.Print("}") |
||||||
|
} |
||||||
|
|
||||||
|
// Set cursor position
|
||||||
|
s.eraseLine() |
||||||
|
s.cursorPos(pLen + pos) |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func longestCommonPrefix(strs []string) string { |
||||||
|
if len(strs) == 0 { |
||||||
|
return "" |
||||||
|
} |
||||||
|
longest := strs[0] |
||||||
|
|
||||||
|
for _, str := range strs[1:] { |
||||||
|
for !strings.HasPrefix(str, longest) { |
||||||
|
longest = longest[:len(longest)-1] |
||||||
|
} |
||||||
|
} |
||||||
|
// Remove trailing partial runes
|
||||||
|
longest = strings.TrimRight(longest, "\uFFFD") |
||||||
|
return longest |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) circularTabs(items []string) func(tabDirection) (string, error) { |
||||||
|
item := -1 |
||||||
|
return func(direction tabDirection) (string, error) { |
||||||
|
if direction == tabForward { |
||||||
|
if item < len(items)-1 { |
||||||
|
item++ |
||||||
|
} else { |
||||||
|
item = 0 |
||||||
|
} |
||||||
|
} else if direction == tabReverse { |
||||||
|
if item > 0 { |
||||||
|
item-- |
||||||
|
} else { |
||||||
|
item = len(items) - 1 |
||||||
|
} |
||||||
|
} |
||||||
|
return items[item], nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) printedTabs(items []string) func(tabDirection) (string, error) { |
||||||
|
numTabs := 1 |
||||||
|
prefix := longestCommonPrefix(items) |
||||||
|
return func(direction tabDirection) (string, error) { |
||||||
|
if len(items) == 1 { |
||||||
|
return items[0], nil |
||||||
|
} |
||||||
|
|
||||||
|
if numTabs == 2 { |
||||||
|
if len(items) > 100 { |
||||||
|
fmt.Printf("\nDisplay all %d possibilities? (y or n) ", len(items)) |
||||||
|
for { |
||||||
|
next, err := s.readNext() |
||||||
|
if err != nil { |
||||||
|
return prefix, err |
||||||
|
} |
||||||
|
|
||||||
|
if key, ok := next.(rune); ok { |
||||||
|
if unicode.ToLower(key) == 'n' { |
||||||
|
return prefix, nil |
||||||
|
} else if unicode.ToLower(key) == 'y' { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
fmt.Println("") |
||||||
|
maxWidth := 0 |
||||||
|
for _, item := range items { |
||||||
|
if len(item) >= maxWidth { |
||||||
|
maxWidth = len(item) + 1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
numColumns := s.columns / maxWidth |
||||||
|
numRows := len(items) / numColumns |
||||||
|
if len(items)%numColumns > 0 { |
||||||
|
numRows++ |
||||||
|
} |
||||||
|
|
||||||
|
if len(items) <= numColumns { |
||||||
|
maxWidth = 0 |
||||||
|
} |
||||||
|
for i := 0; i < numRows; i++ { |
||||||
|
for j := 0; j < numColumns*numRows; j += numRows { |
||||||
|
if i+j < len(items) { |
||||||
|
if maxWidth > 0 { |
||||||
|
fmt.Printf("%-*s", maxWidth, items[i+j]) |
||||||
|
} else { |
||||||
|
fmt.Printf("%v ", items[i+j]) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
fmt.Println("") |
||||||
|
} |
||||||
|
} else { |
||||||
|
numTabs++ |
||||||
|
} |
||||||
|
return prefix, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interface{}, error) { |
||||||
|
if s.completer == nil { |
||||||
|
return line, pos, rune(esc), nil |
||||||
|
} |
||||||
|
head, list, tail := s.completer(string(line), pos) |
||||||
|
if len(list) <= 0 { |
||||||
|
return line, pos, rune(esc), nil |
||||||
|
} |
||||||
|
hl := utf8.RuneCountInString(head) |
||||||
|
if len(list) == 1 { |
||||||
|
s.refresh(p, []rune(head+list[0]+tail), hl+utf8.RuneCountInString(list[0])) |
||||||
|
return []rune(head + list[0] + tail), hl + utf8.RuneCountInString(list[0]), rune(esc), nil |
||||||
|
} |
||||||
|
|
||||||
|
direction := tabForward |
||||||
|
tabPrinter := s.circularTabs(list) |
||||||
|
if s.tabStyle == TabPrints { |
||||||
|
tabPrinter = s.printedTabs(list) |
||||||
|
} |
||||||
|
|
||||||
|
for { |
||||||
|
pick, err := tabPrinter(direction) |
||||||
|
if err != nil { |
||||||
|
return line, pos, rune(esc), err |
||||||
|
} |
||||||
|
s.refresh(p, []rune(head+pick+tail), hl+utf8.RuneCountInString(pick)) |
||||||
|
|
||||||
|
next, err := s.readNext() |
||||||
|
if err != nil { |
||||||
|
return line, pos, rune(esc), err |
||||||
|
} |
||||||
|
if key, ok := next.(rune); ok { |
||||||
|
if key == tab { |
||||||
|
direction = tabForward |
||||||
|
continue |
||||||
|
} |
||||||
|
if key == esc { |
||||||
|
return line, pos, rune(esc), nil |
||||||
|
} |
||||||
|
} |
||||||
|
if a, ok := next.(action); ok && a == shiftTab { |
||||||
|
direction = tabReverse |
||||||
|
continue |
||||||
|
} |
||||||
|
return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil |
||||||
|
} |
||||||
|
// Not reached
|
||||||
|
return line, pos, rune(esc), nil |
||||||
|
} |
||||||
|
|
||||||
|
// reverse intelligent search, implements a bash-like history search.
|
||||||
|
func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) { |
||||||
|
p := "(reverse-i-search)`': " |
||||||
|
s.refresh([]rune(p), origLine, origPos) |
||||||
|
|
||||||
|
line := []rune{} |
||||||
|
pos := 0 |
||||||
|
foundLine := string(origLine) |
||||||
|
foundPos := origPos |
||||||
|
|
||||||
|
getLine := func() ([]rune, []rune, int) { |
||||||
|
search := string(line) |
||||||
|
prompt := "(reverse-i-search)`%s': " |
||||||
|
return []rune(fmt.Sprintf(prompt, search)), []rune(foundLine), foundPos |
||||||
|
} |
||||||
|
|
||||||
|
history, positions := s.getHistoryByPattern(string(line)) |
||||||
|
historyPos := len(history) - 1 |
||||||
|
|
||||||
|
for { |
||||||
|
next, err := s.readNext() |
||||||
|
if err != nil { |
||||||
|
return []rune(foundLine), foundPos, rune(esc), err |
||||||
|
} |
||||||
|
|
||||||
|
switch v := next.(type) { |
||||||
|
case rune: |
||||||
|
switch v { |
||||||
|
case ctrlR: // Search backwards
|
||||||
|
if historyPos > 0 && historyPos < len(history) { |
||||||
|
historyPos-- |
||||||
|
foundLine = history[historyPos] |
||||||
|
foundPos = positions[historyPos] |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case ctrlS: // Search forward
|
||||||
|
if historyPos < len(history)-1 && historyPos >= 0 { |
||||||
|
historyPos++ |
||||||
|
foundLine = history[historyPos] |
||||||
|
foundPos = positions[historyPos] |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case ctrlH, bs: // Backspace
|
||||||
|
if pos <= 0 { |
||||||
|
fmt.Print(beep) |
||||||
|
} else { |
||||||
|
n := len(getSuffixGlyphs(line[:pos], 1)) |
||||||
|
line = append(line[:pos-n], line[pos:]...) |
||||||
|
pos -= n |
||||||
|
|
||||||
|
// For each char deleted, display the last matching line of history
|
||||||
|
history, positions := s.getHistoryByPattern(string(line)) |
||||||
|
historyPos = len(history) - 1 |
||||||
|
if len(history) > 0 { |
||||||
|
foundLine = history[historyPos] |
||||||
|
foundPos = positions[historyPos] |
||||||
|
} else { |
||||||
|
foundLine = "" |
||||||
|
foundPos = 0 |
||||||
|
} |
||||||
|
} |
||||||
|
case ctrlG: // Cancel
|
||||||
|
return origLine, origPos, rune(esc), err |
||||||
|
|
||||||
|
case tab, cr, lf, ctrlA, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK, |
||||||
|
ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ: |
||||||
|
fallthrough |
||||||
|
case 0, ctrlC, esc, 28, 29, 30, 31: |
||||||
|
return []rune(foundLine), foundPos, next, err |
||||||
|
default: |
||||||
|
line = append(line[:pos], append([]rune{v}, line[pos:]...)...) |
||||||
|
pos++ |
||||||
|
|
||||||
|
// For each keystroke typed, display the last matching line of history
|
||||||
|
history, positions = s.getHistoryByPattern(string(line)) |
||||||
|
historyPos = len(history) - 1 |
||||||
|
if len(history) > 0 { |
||||||
|
foundLine = history[historyPos] |
||||||
|
foundPos = positions[historyPos] |
||||||
|
} else { |
||||||
|
foundLine = "" |
||||||
|
foundPos = 0 |
||||||
|
} |
||||||
|
} |
||||||
|
case action: |
||||||
|
return []rune(foundLine), foundPos, next, err |
||||||
|
} |
||||||
|
s.refresh(getLine()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// addToKillRing adds some text to the kill ring. If mode is 0 it adds it to a
|
||||||
|
// new node in the end of the kill ring, and move the current pointer to the new
|
||||||
|
// node. If mode is 1 or 2 it appends or prepends the text to the current entry
|
||||||
|
// of the killRing.
|
||||||
|
func (s *State) addToKillRing(text []rune, mode int) { |
||||||
|
// Don't use the same underlying array as text
|
||||||
|
killLine := make([]rune, len(text)) |
||||||
|
copy(killLine, text) |
||||||
|
|
||||||
|
// Point killRing to a newNode, procedure depends on the killring state and
|
||||||
|
// append mode.
|
||||||
|
if mode == 0 { // Add new node to killRing
|
||||||
|
if s.killRing == nil { // if killring is empty, create a new one
|
||||||
|
s.killRing = ring.New(1) |
||||||
|
} else if s.killRing.Len() >= KillRingMax { // if killring is "full"
|
||||||
|
s.killRing = s.killRing.Next() |
||||||
|
} else { // Normal case
|
||||||
|
s.killRing.Link(ring.New(1)) |
||||||
|
s.killRing = s.killRing.Next() |
||||||
|
} |
||||||
|
} else { |
||||||
|
if s.killRing == nil { // if killring is empty, create a new one
|
||||||
|
s.killRing = ring.New(1) |
||||||
|
s.killRing.Value = []rune{} |
||||||
|
} |
||||||
|
if mode == 1 { // Append to last entry
|
||||||
|
killLine = append(s.killRing.Value.([]rune), killLine...) |
||||||
|
} else if mode == 2 { // Prepend to last entry
|
||||||
|
killLine = append(killLine, s.killRing.Value.([]rune)...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Save text in the current killring node
|
||||||
|
s.killRing.Value = killLine |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) yank(p []rune, text []rune, pos int) ([]rune, int, interface{}, error) { |
||||||
|
if s.killRing == nil { |
||||||
|
return text, pos, rune(esc), nil |
||||||
|
} |
||||||
|
|
||||||
|
lineStart := text[:pos] |
||||||
|
lineEnd := text[pos:] |
||||||
|
var line []rune |
||||||
|
|
||||||
|
for { |
||||||
|
value := s.killRing.Value.([]rune) |
||||||
|
line = make([]rune, 0) |
||||||
|
line = append(line, lineStart...) |
||||||
|
line = append(line, value...) |
||||||
|
line = append(line, lineEnd...) |
||||||
|
|
||||||
|
pos = len(lineStart) + len(value) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
|
||||||
|
next, err := s.readNext() |
||||||
|
if err != nil { |
||||||
|
return line, pos, next, err |
||||||
|
} |
||||||
|
|
||||||
|
switch v := next.(type) { |
||||||
|
case rune: |
||||||
|
return line, pos, next, nil |
||||||
|
case action: |
||||||
|
switch v { |
||||||
|
case altY: |
||||||
|
s.killRing = s.killRing.Prev() |
||||||
|
default: |
||||||
|
return line, pos, next, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return line, pos, esc, nil |
||||||
|
} |
||||||
|
|
||||||
|
// Prompt displays p, and then waits for user input. Prompt allows line editing
|
||||||
|
// if the terminal supports it.
|
||||||
|
func (s *State) Prompt(prompt string) (string, error) { |
||||||
|
if s.inputRedirected { |
||||||
|
return s.promptUnsupported(prompt) |
||||||
|
} |
||||||
|
if s.outputRedirected { |
||||||
|
return "", ErrNotTerminalOutput |
||||||
|
} |
||||||
|
if !s.terminalSupported { |
||||||
|
return s.promptUnsupported(prompt) |
||||||
|
} |
||||||
|
|
||||||
|
s.historyMutex.RLock() |
||||||
|
defer s.historyMutex.RUnlock() |
||||||
|
|
||||||
|
s.startPrompt() |
||||||
|
defer s.stopPrompt() |
||||||
|
s.getColumns() |
||||||
|
|
||||||
|
fmt.Print(prompt) |
||||||
|
p := []rune(prompt) |
||||||
|
var line []rune |
||||||
|
pos := 0 |
||||||
|
historyEnd := "" |
||||||
|
prefixHistory := s.getHistoryByPrefix(string(line)) |
||||||
|
historyPos := len(prefixHistory) |
||||||
|
historyAction := false // used to mark history related actions
|
||||||
|
killAction := 0 // used to mark kill related actions
|
||||||
|
mainLoop: |
||||||
|
for { |
||||||
|
next, err := s.readNext() |
||||||
|
haveNext: |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
historyAction = false |
||||||
|
switch v := next.(type) { |
||||||
|
case rune: |
||||||
|
switch v { |
||||||
|
case cr, lf: |
||||||
|
fmt.Println() |
||||||
|
break mainLoop |
||||||
|
case ctrlA: // Start of line
|
||||||
|
pos = 0 |
||||||
|
s.refresh(p, line, pos) |
||||||
|
case ctrlE: // End of line
|
||||||
|
pos = len(line) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
case ctrlB: // left
|
||||||
|
if pos > 0 { |
||||||
|
pos -= len(getSuffixGlyphs(line[:pos], 1)) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case ctrlF: // right
|
||||||
|
if pos < len(line) { |
||||||
|
pos += len(getPrefixGlyphs(line[pos:], 1)) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case ctrlD: // del
|
||||||
|
if pos == 0 && len(line) == 0 { |
||||||
|
// exit
|
||||||
|
return "", io.EOF |
||||||
|
} |
||||||
|
|
||||||
|
// ctrlD is a potential EOF, so the rune reader shuts down.
|
||||||
|
// Therefore, if it isn't actually an EOF, we must re-startPrompt.
|
||||||
|
s.restartPrompt() |
||||||
|
|
||||||
|
if pos >= len(line) { |
||||||
|
fmt.Print(beep) |
||||||
|
} else { |
||||||
|
n := len(getPrefixGlyphs(line[pos:], 1)) |
||||||
|
line = append(line[:pos], line[pos+n:]...) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} |
||||||
|
case ctrlK: // delete remainder of line
|
||||||
|
if pos >= len(line) { |
||||||
|
fmt.Print(beep) |
||||||
|
} else { |
||||||
|
if killAction > 0 { |
||||||
|
s.addToKillRing(line[pos:], 1) // Add in apend mode
|
||||||
|
} else { |
||||||
|
s.addToKillRing(line[pos:], 0) // Add in normal mode
|
||||||
|
} |
||||||
|
|
||||||
|
killAction = 2 // Mark that there was a kill action
|
||||||
|
line = line[:pos] |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} |
||||||
|
case ctrlP: // up
|
||||||
|
historyAction = true |
||||||
|
if historyPos > 0 { |
||||||
|
if historyPos == len(prefixHistory) { |
||||||
|
historyEnd = string(line) |
||||||
|
} |
||||||
|
historyPos-- |
||||||
|
line = []rune(prefixHistory[historyPos]) |
||||||
|
pos = len(line) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case ctrlN: // down
|
||||||
|
historyAction = true |
||||||
|
if historyPos < len(prefixHistory) { |
||||||
|
historyPos++ |
||||||
|
if historyPos == len(prefixHistory) { |
||||||
|
line = []rune(historyEnd) |
||||||
|
} else { |
||||||
|
line = []rune(prefixHistory[historyPos]) |
||||||
|
} |
||||||
|
pos = len(line) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case ctrlT: // transpose prev glyph with glyph under cursor
|
||||||
|
if len(line) < 2 || pos < 1 { |
||||||
|
fmt.Print(beep) |
||||||
|
} else { |
||||||
|
if pos == len(line) { |
||||||
|
pos -= len(getSuffixGlyphs(line, 1)) |
||||||
|
} |
||||||
|
prev := getSuffixGlyphs(line[:pos], 1) |
||||||
|
next := getPrefixGlyphs(line[pos:], 1) |
||||||
|
scratch := make([]rune, len(prev)) |
||||||
|
copy(scratch, prev) |
||||||
|
copy(line[pos-len(prev):], next) |
||||||
|
copy(line[pos-len(prev)+len(next):], scratch) |
||||||
|
pos += len(next) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} |
||||||
|
case ctrlL: // clear screen
|
||||||
|
s.eraseScreen() |
||||||
|
s.refresh(p, line, pos) |
||||||
|
case ctrlC: // reset
|
||||||
|
fmt.Println("^C") |
||||||
|
if s.ctrlCAborts { |
||||||
|
return "", ErrPromptAborted |
||||||
|
} |
||||||
|
line = line[:0] |
||||||
|
pos = 0 |
||||||
|
fmt.Print(prompt) |
||||||
|
s.restartPrompt() |
||||||
|
case ctrlH, bs: // Backspace
|
||||||
|
if pos <= 0 { |
||||||
|
fmt.Print(beep) |
||||||
|
} else { |
||||||
|
n := len(getSuffixGlyphs(line[:pos], 1)) |
||||||
|
line = append(line[:pos-n], line[pos:]...) |
||||||
|
pos -= n |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} |
||||||
|
case ctrlU: // Erase line before cursor
|
||||||
|
if killAction > 0 { |
||||||
|
s.addToKillRing(line[:pos], 2) // Add in prepend mode
|
||||||
|
} else { |
||||||
|
s.addToKillRing(line[:pos], 0) // Add in normal mode
|
||||||
|
} |
||||||
|
|
||||||
|
killAction = 2 // Mark that there was some killing
|
||||||
|
line = line[pos:] |
||||||
|
pos = 0 |
||||||
|
s.refresh(p, line, pos) |
||||||
|
case ctrlW: // Erase word
|
||||||
|
if pos == 0 { |
||||||
|
fmt.Print(beep) |
||||||
|
break |
||||||
|
} |
||||||
|
// Remove whitespace to the left
|
||||||
|
var buf []rune // Store the deleted chars in a buffer
|
||||||
|
for { |
||||||
|
if pos == 0 || !unicode.IsSpace(line[pos-1]) { |
||||||
|
break |
||||||
|
} |
||||||
|
buf = append(buf, line[pos-1]) |
||||||
|
line = append(line[:pos-1], line[pos:]...) |
||||||
|
pos-- |
||||||
|
} |
||||||
|
// Remove non-whitespace to the left
|
||||||
|
for { |
||||||
|
if pos == 0 || unicode.IsSpace(line[pos-1]) { |
||||||
|
break |
||||||
|
} |
||||||
|
buf = append(buf, line[pos-1]) |
||||||
|
line = append(line[:pos-1], line[pos:]...) |
||||||
|
pos-- |
||||||
|
} |
||||||
|
// Invert the buffer and save the result on the killRing
|
||||||
|
var newBuf []rune |
||||||
|
for i := len(buf) - 1; i >= 0; i-- { |
||||||
|
newBuf = append(newBuf, buf[i]) |
||||||
|
} |
||||||
|
if killAction > 0 { |
||||||
|
s.addToKillRing(newBuf, 2) // Add in prepend mode
|
||||||
|
} else { |
||||||
|
s.addToKillRing(newBuf, 0) // Add in normal mode
|
||||||
|
} |
||||||
|
killAction = 2 // Mark that there was some killing
|
||||||
|
|
||||||
|
s.refresh(p, line, pos) |
||||||
|
case ctrlY: // Paste from Yank buffer
|
||||||
|
line, pos, next, err = s.yank(p, line, pos) |
||||||
|
goto haveNext |
||||||
|
case ctrlR: // Reverse Search
|
||||||
|
line, pos, next, err = s.reverseISearch(line, pos) |
||||||
|
s.refresh(p, line, pos) |
||||||
|
goto haveNext |
||||||
|
case tab: // Tab completion
|
||||||
|
line, pos, next, err = s.tabComplete(p, line, pos) |
||||||
|
goto haveNext |
||||||
|
// Catch keys that do nothing, but you don't want them to beep
|
||||||
|
case esc: |
||||||
|
// DO NOTHING
|
||||||
|
// Unused keys
|
||||||
|
case ctrlG, ctrlO, ctrlQ, ctrlS, ctrlV, ctrlX, ctrlZ: |
||||||
|
fallthrough |
||||||
|
// Catch unhandled control codes (anything <= 31)
|
||||||
|
case 0, 28, 29, 30, 31: |
||||||
|
fmt.Print(beep) |
||||||
|
default: |
||||||
|
if pos == len(line) && len(p)+len(line) < s.columns-1 { |
||||||
|
line = append(line, v) |
||||||
|
fmt.Printf("%c", v) |
||||||
|
pos++ |
||||||
|
} else { |
||||||
|
line = append(line[:pos], append([]rune{v}, line[pos:]...)...) |
||||||
|
pos++ |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} |
||||||
|
} |
||||||
|
case action: |
||||||
|
switch v { |
||||||
|
case del: |
||||||
|
if pos >= len(line) { |
||||||
|
fmt.Print(beep) |
||||||
|
} else { |
||||||
|
n := len(getPrefixGlyphs(line[pos:], 1)) |
||||||
|
line = append(line[:pos], line[pos+n:]...) |
||||||
|
} |
||||||
|
case left: |
||||||
|
if pos > 0 { |
||||||
|
pos -= len(getSuffixGlyphs(line[:pos], 1)) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case wordLeft: |
||||||
|
if pos > 0 { |
||||||
|
for { |
||||||
|
pos-- |
||||||
|
if pos == 0 || unicode.IsSpace(line[pos-1]) { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case right: |
||||||
|
if pos < len(line) { |
||||||
|
pos += len(getPrefixGlyphs(line[pos:], 1)) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case wordRight: |
||||||
|
if pos < len(line) { |
||||||
|
for { |
||||||
|
pos++ |
||||||
|
if pos == len(line) || unicode.IsSpace(line[pos]) { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case up: |
||||||
|
historyAction = true |
||||||
|
if historyPos > 0 { |
||||||
|
if historyPos == len(prefixHistory) { |
||||||
|
historyEnd = string(line) |
||||||
|
} |
||||||
|
historyPos-- |
||||||
|
line = []rune(prefixHistory[historyPos]) |
||||||
|
pos = len(line) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case down: |
||||||
|
historyAction = true |
||||||
|
if historyPos < len(prefixHistory) { |
||||||
|
historyPos++ |
||||||
|
if historyPos == len(prefixHistory) { |
||||||
|
line = []rune(historyEnd) |
||||||
|
} else { |
||||||
|
line = []rune(prefixHistory[historyPos]) |
||||||
|
} |
||||||
|
pos = len(line) |
||||||
|
} else { |
||||||
|
fmt.Print(beep) |
||||||
|
} |
||||||
|
case home: // Start of line
|
||||||
|
pos = 0 |
||||||
|
case end: // End of line
|
||||||
|
pos = len(line) |
||||||
|
} |
||||||
|
s.refresh(p, line, pos) |
||||||
|
} |
||||||
|
if !historyAction { |
||||||
|
prefixHistory = s.getHistoryByPrefix(string(line)) |
||||||
|
historyPos = len(prefixHistory) |
||||||
|
} |
||||||
|
if killAction > 0 { |
||||||
|
killAction-- |
||||||
|
} |
||||||
|
} |
||||||
|
return string(line), nil |
||||||
|
} |
||||||
|
|
||||||
|
// PasswordPrompt displays p, and then waits for user input. The input typed by
|
||||||
|
// the user is not displayed in the terminal.
|
||||||
|
func (s *State) PasswordPrompt(prompt string) (string, error) { |
||||||
|
if s.inputRedirected { |
||||||
|
return s.promptUnsupported(prompt) |
||||||
|
} |
||||||
|
if s.outputRedirected { |
||||||
|
return "", ErrNotTerminalOutput |
||||||
|
} |
||||||
|
if !s.terminalSupported { |
||||||
|
return "", errors.New("liner: function not supported in this terminal") |
||||||
|
} |
||||||
|
|
||||||
|
s.startPrompt() |
||||||
|
defer s.stopPrompt() |
||||||
|
s.getColumns() |
||||||
|
|
||||||
|
fmt.Print(prompt) |
||||||
|
p := []rune(prompt) |
||||||
|
var line []rune |
||||||
|
pos := 0 |
||||||
|
|
||||||
|
mainLoop: |
||||||
|
for { |
||||||
|
next, err := s.readNext() |
||||||
|
if err != nil { |
||||||
|
return "", err |
||||||
|
} |
||||||
|
|
||||||
|
switch v := next.(type) { |
||||||
|
case rune: |
||||||
|
switch v { |
||||||
|
case cr, lf: |
||||||
|
fmt.Println() |
||||||
|
break mainLoop |
||||||
|
case ctrlD: // del
|
||||||
|
if pos == 0 && len(line) == 0 { |
||||||
|
// exit
|
||||||
|
return "", io.EOF |
||||||
|
} |
||||||
|
|
||||||
|
// ctrlD is a potential EOF, so the rune reader shuts down.
|
||||||
|
// Therefore, if it isn't actually an EOF, we must re-startPrompt.
|
||||||
|
s.restartPrompt() |
||||||
|
case ctrlL: // clear screen
|
||||||
|
s.eraseScreen() |
||||||
|
s.refresh(p, []rune{}, 0) |
||||||
|
case ctrlH, bs: // Backspace
|
||||||
|
if pos <= 0 { |
||||||
|
fmt.Print(beep) |
||||||
|
} else { |
||||||
|
n := len(getSuffixGlyphs(line[:pos], 1)) |
||||||
|
line = append(line[:pos-n], line[pos:]...) |
||||||
|
pos -= n |
||||||
|
} |
||||||
|
case ctrlC: |
||||||
|
fmt.Println("^C") |
||||||
|
if s.ctrlCAborts { |
||||||
|
return "", ErrPromptAborted |
||||||
|
} |
||||||
|
line = line[:0] |
||||||
|
pos = 0 |
||||||
|
fmt.Print(prompt) |
||||||
|
s.restartPrompt() |
||||||
|
// Unused keys
|
||||||
|
case esc, tab, ctrlA, ctrlB, ctrlE, ctrlF, ctrlG, ctrlK, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlR, ctrlS, |
||||||
|
ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ: |
||||||
|
fallthrough |
||||||
|
// Catch unhandled control codes (anything <= 31)
|
||||||
|
case 0, 28, 29, 30, 31: |
||||||
|
fmt.Print(beep) |
||||||
|
default: |
||||||
|
line = append(line[:pos], append([]rune{v}, line[pos:]...)...) |
||||||
|
pos++ |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return string(line), nil |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestAppend(t *testing.T) { |
||||||
|
var s State |
||||||
|
s.AppendHistory("foo") |
||||||
|
s.AppendHistory("bar") |
||||||
|
|
||||||
|
var out bytes.Buffer |
||||||
|
num, err := s.WriteHistory(&out) |
||||||
|
if err != nil { |
||||||
|
t.Fatal("Unexpected error writing history", err) |
||||||
|
} |
||||||
|
if num != 2 { |
||||||
|
t.Fatalf("Expected 2 history entries, got %d", num) |
||||||
|
} |
||||||
|
|
||||||
|
s.AppendHistory("baz") |
||||||
|
num, err = s.WriteHistory(&out) |
||||||
|
if err != nil { |
||||||
|
t.Fatal("Unexpected error writing history", err) |
||||||
|
} |
||||||
|
if num != 3 { |
||||||
|
t.Fatalf("Expected 3 history entries, got %d", num) |
||||||
|
} |
||||||
|
|
||||||
|
s.AppendHistory("baz") |
||||||
|
num, err = s.WriteHistory(&out) |
||||||
|
if err != nil { |
||||||
|
t.Fatal("Unexpected error writing history", err) |
||||||
|
} |
||||||
|
if num != 3 { |
||||||
|
t.Fatalf("Expected 3 history entries after duplicate append, got %d", num) |
||||||
|
} |
||||||
|
|
||||||
|
s.AppendHistory("baz") |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func TestHistory(t *testing.T) { |
||||||
|
input := `foo |
||||||
|
bar |
||||||
|
baz |
||||||
|
quux |
||||||
|
dingle` |
||||||
|
|
||||||
|
var s State |
||||||
|
num, err := s.ReadHistory(strings.NewReader(input)) |
||||||
|
if err != nil { |
||||||
|
t.Fatal("Unexpected error reading history", err) |
||||||
|
} |
||||||
|
if num != 5 { |
||||||
|
t.Fatal("Wrong number of history entries read") |
||||||
|
} |
||||||
|
|
||||||
|
var out bytes.Buffer |
||||||
|
num, err = s.WriteHistory(&out) |
||||||
|
if err != nil { |
||||||
|
t.Fatal("Unexpected error writing history", err) |
||||||
|
} |
||||||
|
if num != 5 { |
||||||
|
t.Fatal("Wrong number of history entries written") |
||||||
|
} |
||||||
|
if strings.TrimSpace(out.String()) != input { |
||||||
|
t.Fatal("Round-trip failure") |
||||||
|
} |
||||||
|
|
||||||
|
// Test reading with a trailing newline present
|
||||||
|
var s2 State |
||||||
|
num, err = s2.ReadHistory(&out) |
||||||
|
if err != nil { |
||||||
|
t.Fatal("Unexpected error reading history the 2nd time", err) |
||||||
|
} |
||||||
|
if num != 5 { |
||||||
|
t.Fatal("Wrong number of history entries read the 2nd time") |
||||||
|
} |
||||||
|
|
||||||
|
num, err = s.ReadHistory(strings.NewReader(input + "\n\xff")) |
||||||
|
if err == nil { |
||||||
|
t.Fatal("Unexpected success reading corrupted history", err) |
||||||
|
} |
||||||
|
if num != 5 { |
||||||
|
t.Fatal("Wrong number of history entries read the 3rd time") |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,63 @@ |
|||||||
|
// +build linux darwin openbsd freebsd netbsd
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"strings" |
||||||
|
"syscall" |
||||||
|
"unsafe" |
||||||
|
) |
||||||
|
|
||||||
|
func (s *State) cursorPos(x int) { |
||||||
|
if s.useCHA { |
||||||
|
// 'G' is "Cursor Character Absolute (CHA)"
|
||||||
|
fmt.Printf("\x1b[%dG", x+1) |
||||||
|
} else { |
||||||
|
// 'C' is "Cursor Forward (CUF)"
|
||||||
|
fmt.Print("\r") |
||||||
|
if x > 0 { |
||||||
|
fmt.Printf("\x1b[%dC", x) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) eraseLine() { |
||||||
|
fmt.Print("\x1b[0K") |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) eraseScreen() { |
||||||
|
fmt.Print("\x1b[H\x1b[2J") |
||||||
|
} |
||||||
|
|
||||||
|
type winSize struct { |
||||||
|
row, col uint16 |
||||||
|
xpixel, ypixel uint16 |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) getColumns() { |
||||||
|
var ws winSize |
||||||
|
ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), |
||||||
|
syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))) |
||||||
|
if ok < 0 { |
||||||
|
s.columns = 80 |
||||||
|
} |
||||||
|
s.columns = int(ws.col) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) checkOutput() { |
||||||
|
// xterm is known to support CHA
|
||||||
|
if strings.Contains(strings.ToLower(os.Getenv("TERM")), "xterm") { |
||||||
|
s.useCHA = true |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// The test for functional ANSI CHA is unreliable (eg the Windows
|
||||||
|
// telnet command does not support reading the cursor position with
|
||||||
|
// an ANSI DSR request, despite setting TERM=ansi)
|
||||||
|
|
||||||
|
// Assume CHA isn't supported (which should be safe, although it
|
||||||
|
// does result in occasional visible cursor jitter)
|
||||||
|
s.useCHA = false |
||||||
|
} |
@ -0,0 +1,54 @@ |
|||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"unsafe" |
||||||
|
) |
||||||
|
|
||||||
|
type coord struct { |
||||||
|
x, y int16 |
||||||
|
} |
||||||
|
type smallRect struct { |
||||||
|
left, top, right, bottom int16 |
||||||
|
} |
||||||
|
|
||||||
|
type consoleScreenBufferInfo struct { |
||||||
|
dwSize coord |
||||||
|
dwCursorPosition coord |
||||||
|
wAttributes int16 |
||||||
|
srWindow smallRect |
||||||
|
dwMaximumWindowSize coord |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) cursorPos(x int) { |
||||||
|
var sbi consoleScreenBufferInfo |
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) |
||||||
|
procSetConsoleCursorPosition.Call(uintptr(s.hOut), |
||||||
|
uintptr(int(x)&0xFFFF|int(sbi.dwCursorPosition.y)<<16)) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) eraseLine() { |
||||||
|
var sbi consoleScreenBufferInfo |
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) |
||||||
|
var numWritten uint32 |
||||||
|
procFillConsoleOutputCharacter.Call(uintptr(s.hOut), uintptr(' '), |
||||||
|
uintptr(sbi.dwSize.x-sbi.dwCursorPosition.x), |
||||||
|
uintptr(int(sbi.dwCursorPosition.x)&0xFFFF|int(sbi.dwCursorPosition.y)<<16), |
||||||
|
uintptr(unsafe.Pointer(&numWritten))) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) eraseScreen() { |
||||||
|
var sbi consoleScreenBufferInfo |
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) |
||||||
|
var numWritten uint32 |
||||||
|
procFillConsoleOutputCharacter.Call(uintptr(s.hOut), uintptr(' '), |
||||||
|
uintptr(sbi.dwSize.x)*uintptr(sbi.dwSize.y), |
||||||
|
0, |
||||||
|
uintptr(unsafe.Pointer(&numWritten))) |
||||||
|
procSetConsoleCursorPosition.Call(uintptr(s.hOut), 0) |
||||||
|
} |
||||||
|
|
||||||
|
func (s *State) getColumns() { |
||||||
|
var sbi consoleScreenBufferInfo |
||||||
|
procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) |
||||||
|
s.columns = int(sbi.dwSize.x) |
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
// +build windows linux darwin openbsd freebsd netbsd
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import "testing" |
||||||
|
|
||||||
|
type testItem struct { |
||||||
|
list []string |
||||||
|
prefix string |
||||||
|
} |
||||||
|
|
||||||
|
func TestPrefix(t *testing.T) { |
||||||
|
list := []testItem{ |
||||||
|
{[]string{"food", "foot"}, "foo"}, |
||||||
|
{[]string{"foo", "foot"}, "foo"}, |
||||||
|
{[]string{"food", "foo"}, "foo"}, |
||||||
|
{[]string{"food", "foe", "foot"}, "fo"}, |
||||||
|
{[]string{"food", "foot", "barbeque"}, ""}, |
||||||
|
{[]string{"cafeteria", "café"}, "caf"}, |
||||||
|
{[]string{"cafe", "café"}, "caf"}, |
||||||
|
{[]string{"cafè", "café"}, "caf"}, |
||||||
|
{[]string{"cafés", "café"}, "café"}, |
||||||
|
{[]string{"áéíóú", "áéíóú"}, "áéíóú"}, |
||||||
|
{[]string{"éclairs", "éclairs"}, "éclairs"}, |
||||||
|
{[]string{"éclairs are the best", "éclairs are great", "éclairs"}, "éclairs"}, |
||||||
|
{[]string{"éclair", "éclairs"}, "éclair"}, |
||||||
|
{[]string{"éclairs", "éclair"}, "éclair"}, |
||||||
|
{[]string{"éclair", "élan"}, "é"}, |
||||||
|
} |
||||||
|
|
||||||
|
for _, test := range list { |
||||||
|
lcp := longestCommonPrefix(test.list) |
||||||
|
if lcp != test.prefix { |
||||||
|
t.Errorf("%s != %s for %+v", lcp, test.prefix, test.list) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,44 @@ |
|||||||
|
// +build race
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"sync" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func TestWriteHistory(t *testing.T) { |
||||||
|
oldout := os.Stdout |
||||||
|
defer func() { os.Stdout = oldout }() |
||||||
|
oldin := os.Stdout |
||||||
|
defer func() { os.Stdin = oldin }() |
||||||
|
|
||||||
|
newinr, newinw, err := os.Pipe() |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
os.Stdin = newinr |
||||||
|
newoutr, newoutw, err := os.Pipe() |
||||||
|
if err != nil { |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
defer newoutr.Close() |
||||||
|
os.Stdout = newoutw |
||||||
|
|
||||||
|
var wait sync.WaitGroup |
||||||
|
wait.Add(1) |
||||||
|
s := NewLiner() |
||||||
|
go func() { |
||||||
|
s.AppendHistory("foo") |
||||||
|
s.AppendHistory("bar") |
||||||
|
s.Prompt("") |
||||||
|
wait.Done() |
||||||
|
}() |
||||||
|
|
||||||
|
s.WriteHistory(ioutil.Discard) |
||||||
|
|
||||||
|
newinw.Close() |
||||||
|
wait.Wait() |
||||||
|
} |
@ -0,0 +1,12 @@ |
|||||||
|
// +build go1.1,!windows
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
"os/signal" |
||||||
|
) |
||||||
|
|
||||||
|
func stopSignal(c chan<- os.Signal) { |
||||||
|
signal.Stop(c) |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
// +build !go1.1,!windows
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
func stopSignal(c chan<- os.Signal) { |
||||||
|
// signal.Stop does not exist before Go 1.1
|
||||||
|
} |
@ -0,0 +1,37 @@ |
|||||||
|
// +build linux darwin freebsd openbsd netbsd
|
||||||
|
|
||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"syscall" |
||||||
|
"unsafe" |
||||||
|
) |
||||||
|
|
||||||
|
func (mode *termios) ApplyMode() error { |
||||||
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdin), setTermios, uintptr(unsafe.Pointer(mode))) |
||||||
|
|
||||||
|
if errno != 0 { |
||||||
|
return errno |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// TerminalMode returns the current terminal input mode as an InputModeSetter.
|
||||||
|
//
|
||||||
|
// This function is provided for convenience, and should
|
||||||
|
// not be necessary for most users of liner.
|
||||||
|
func TerminalMode() (ModeApplier, error) { |
||||||
|
mode, errno := getMode(syscall.Stdin) |
||||||
|
|
||||||
|
if errno != 0 { |
||||||
|
return nil, errno |
||||||
|
} |
||||||
|
return mode, nil |
||||||
|
} |
||||||
|
|
||||||
|
func getMode(handle int) (*termios, syscall.Errno) { |
||||||
|
var mode termios |
||||||
|
_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(handle), getTermios, uintptr(unsafe.Pointer(&mode))) |
||||||
|
|
||||||
|
return &mode, errno |
||||||
|
} |
@ -0,0 +1,47 @@ |
|||||||
|
package liner |
||||||
|
|
||||||
|
import "unicode" |
||||||
|
|
||||||
|
// These character classes are mostly zero width (when combined).
|
||||||
|
// A few might not be, depending on the user's font. Fixing this
|
||||||
|
// is non-trivial, given that some terminals don't support
|
||||||
|
// ANSI DSR/CPR
|
||||||
|
var zeroWidth = []*unicode.RangeTable{ |
||||||
|
unicode.Mn, |
||||||
|
unicode.Me, |
||||||
|
unicode.Cc, |
||||||
|
unicode.Cf, |
||||||
|
} |
||||||
|
|
||||||
|
func countGlyphs(s []rune) int { |
||||||
|
n := 0 |
||||||
|
for _, r := range s { |
||||||
|
if !unicode.IsOneOf(zeroWidth, r) { |
||||||
|
n++ |
||||||
|
} |
||||||
|
} |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
func getPrefixGlyphs(s []rune, num int) []rune { |
||||||
|
p := 0 |
||||||
|
for n := 0; n < num && p < len(s); p++ { |
||||||
|
if !unicode.IsOneOf(zeroWidth, s[p]) { |
||||||
|
n++ |
||||||
|
} |
||||||
|
} |
||||||
|
for p < len(s) && unicode.IsOneOf(zeroWidth, s[p]) { |
||||||
|
p++ |
||||||
|
} |
||||||
|
return s[:p] |
||||||
|
} |
||||||
|
|
||||||
|
func getSuffixGlyphs(s []rune, num int) []rune { |
||||||
|
p := len(s) |
||||||
|
for n := 0; n < num && p > 0; p-- { |
||||||
|
if !unicode.IsOneOf(zeroWidth, s[p-1]) { |
||||||
|
n++ |
||||||
|
} |
||||||
|
} |
||||||
|
return s[p:] |
||||||
|
} |
@ -0,0 +1,87 @@ |
|||||||
|
package liner |
||||||
|
|
||||||
|
import ( |
||||||
|
"strconv" |
||||||
|
"testing" |
||||||
|
) |
||||||
|
|
||||||
|
func accent(in []rune) []rune { |
||||||
|
var out []rune |
||||||
|
for _, r := range in { |
||||||
|
out = append(out, r) |
||||||
|
out = append(out, '\u0301') |
||||||
|
} |
||||||
|
return out |
||||||
|
} |
||||||
|
|
||||||
|
var testString = []rune("query") |
||||||
|
|
||||||
|
func TestCountGlyphs(t *testing.T) { |
||||||
|
count := countGlyphs(testString) |
||||||
|
if count != len(testString) { |
||||||
|
t.Errorf("ASCII count incorrect. %d != %d", count, len(testString)) |
||||||
|
} |
||||||
|
count = countGlyphs(accent(testString)) |
||||||
|
if count != len(testString) { |
||||||
|
t.Errorf("Accent count incorrect. %d != %d", count, len(testString)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func compare(a, b []rune, name string, t *testing.T) { |
||||||
|
if len(a) != len(b) { |
||||||
|
t.Errorf(`"%s" != "%s" in %s"`, string(a), string(b), name) |
||||||
|
return |
||||||
|
} |
||||||
|
for i := range a { |
||||||
|
if a[i] != b[i] { |
||||||
|
t.Errorf(`"%s" != "%s" in %s"`, string(a), string(b), name) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestPrefixGlyphs(t *testing.T) { |
||||||
|
for i := 0; i <= len(testString); i++ { |
||||||
|
iter := strconv.Itoa(i) |
||||||
|
out := getPrefixGlyphs(testString, i) |
||||||
|
compare(out, testString[:i], "ascii prefix "+iter, t) |
||||||
|
out = getPrefixGlyphs(accent(testString), i) |
||||||
|
compare(out, accent(testString[:i]), "accent prefix "+iter, t) |
||||||
|
} |
||||||
|
out := getPrefixGlyphs(testString, 999) |
||||||
|
compare(out, testString, "ascii prefix overflow", t) |
||||||
|
out = getPrefixGlyphs(accent(testString), 999) |
||||||
|
compare(out, accent(testString), "accent prefix overflow", t) |
||||||
|
|
||||||
|
out = getPrefixGlyphs(testString, -3) |
||||||
|
if len(out) != 0 { |
||||||
|
t.Error("ascii prefix negative") |
||||||
|
} |
||||||
|
out = getPrefixGlyphs(accent(testString), -3) |
||||||
|
if len(out) != 0 { |
||||||
|
t.Error("accent prefix negative") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func TestSuffixGlyphs(t *testing.T) { |
||||||
|
for i := 0; i <= len(testString); i++ { |
||||||
|
iter := strconv.Itoa(i) |
||||||
|
out := getSuffixGlyphs(testString, i) |
||||||
|
compare(out, testString[len(testString)-i:], "ascii suffix "+iter, t) |
||||||
|
out = getSuffixGlyphs(accent(testString), i) |
||||||
|
compare(out, accent(testString[len(testString)-i:]), "accent suffix "+iter, t) |
||||||
|
} |
||||||
|
out := getSuffixGlyphs(testString, 999) |
||||||
|
compare(out, testString, "ascii suffix overflow", t) |
||||||
|
out = getSuffixGlyphs(accent(testString), 999) |
||||||
|
compare(out, accent(testString), "accent suffix overflow", t) |
||||||
|
|
||||||
|
out = getSuffixGlyphs(testString, -3) |
||||||
|
if len(out) != 0 { |
||||||
|
t.Error("ascii suffix negative") |
||||||
|
} |
||||||
|
out = getSuffixGlyphs(accent(testString), -3) |
||||||
|
if len(out) != 0 { |
||||||
|
t.Error("accent suffix negative") |
||||||
|
} |
||||||
|
} |
@ -1,112 +0,0 @@ |
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package websocket |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"crypto/tls" |
|
||||||
"io" |
|
||||||
"net" |
|
||||||
"net/http" |
|
||||||
"net/url" |
|
||||||
) |
|
||||||
|
|
||||||
// DialError is an error that occurs while dialling a websocket server.
|
|
||||||
type DialError struct { |
|
||||||
*Config |
|
||||||
Err error |
|
||||||
} |
|
||||||
|
|
||||||
func (e *DialError) Error() string { |
|
||||||
return "websocket.Dial " + e.Config.Location.String() + ": " + e.Err.Error() |
|
||||||
} |
|
||||||
|
|
||||||
// NewConfig creates a new WebSocket config for client connection.
|
|
||||||
func NewConfig(server, origin string) (config *Config, err error) { |
|
||||||
config = new(Config) |
|
||||||
config.Version = ProtocolVersionHybi13 |
|
||||||
config.Location, err = url.ParseRequestURI(server) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
config.Origin, err = url.ParseRequestURI(origin) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
config.Header = http.Header(make(map[string][]string)) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// NewClient creates a new WebSocket client connection over rwc.
|
|
||||||
func NewClient(config *Config, rwc io.ReadWriteCloser) (ws *Conn, err error) { |
|
||||||
br := bufio.NewReader(rwc) |
|
||||||
bw := bufio.NewWriter(rwc) |
|
||||||
err = hybiClientHandshake(config, br, bw) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
buf := bufio.NewReadWriter(br, bw) |
|
||||||
ws = newHybiClientConn(config, buf, rwc) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Dial opens a new client connection to a WebSocket.
|
|
||||||
func Dial(url_, protocol, origin string) (ws *Conn, err error) { |
|
||||||
config, err := NewConfig(url_, origin) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
if protocol != "" { |
|
||||||
config.Protocol = []string{protocol} |
|
||||||
} |
|
||||||
return DialConfig(config) |
|
||||||
} |
|
||||||
|
|
||||||
var portMap = map[string]string{ |
|
||||||
"ws": "80", |
|
||||||
"wss": "443", |
|
||||||
} |
|
||||||
|
|
||||||
func parseAuthority(location *url.URL) string { |
|
||||||
if _, ok := portMap[location.Scheme]; ok { |
|
||||||
if _, _, err := net.SplitHostPort(location.Host); err != nil { |
|
||||||
return net.JoinHostPort(location.Host, portMap[location.Scheme]) |
|
||||||
} |
|
||||||
} |
|
||||||
return location.Host |
|
||||||
} |
|
||||||
|
|
||||||
// DialConfig opens a new client connection to a WebSocket with a config.
|
|
||||||
func DialConfig(config *Config) (ws *Conn, err error) { |
|
||||||
var client net.Conn |
|
||||||
if config.Location == nil { |
|
||||||
return nil, &DialError{config, ErrBadWebSocketLocation} |
|
||||||
} |
|
||||||
if config.Origin == nil { |
|
||||||
return nil, &DialError{config, ErrBadWebSocketOrigin} |
|
||||||
} |
|
||||||
switch config.Location.Scheme { |
|
||||||
case "ws": |
|
||||||
client, err = net.Dial("tcp", parseAuthority(config.Location)) |
|
||||||
|
|
||||||
case "wss": |
|
||||||
client, err = tls.Dial("tcp", parseAuthority(config.Location), config.TlsConfig) |
|
||||||
|
|
||||||
default: |
|
||||||
err = ErrBadScheme |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
goto Error |
|
||||||
} |
|
||||||
|
|
||||||
ws, err = NewClient(config, client) |
|
||||||
if err != nil { |
|
||||||
goto Error |
|
||||||
} |
|
||||||
return |
|
||||||
|
|
||||||
Error: |
|
||||||
return nil, &DialError{config, err} |
|
||||||
} |
|
@ -1,31 +0,0 @@ |
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package websocket_test |
|
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"log" |
|
||||||
|
|
||||||
"golang.org/x/net/websocket" |
|
||||||
) |
|
||||||
|
|
||||||
// This example demonstrates a trivial client.
|
|
||||||
func ExampleDial() { |
|
||||||
origin := "http://localhost/" |
|
||||||
url := "ws://localhost:12345/ws" |
|
||||||
ws, err := websocket.Dial(url, "", origin) |
|
||||||
if err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
if _, err := ws.Write([]byte("hello, world!\n")); err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
var msg = make([]byte, 512) |
|
||||||
var n int |
|
||||||
if n, err = ws.Read(msg); err != nil { |
|
||||||
log.Fatal(err) |
|
||||||
} |
|
||||||
fmt.Printf("Received: %s.\n", msg[:n]) |
|
||||||
} |
|
@ -1,26 +0,0 @@ |
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package websocket_test |
|
||||||
|
|
||||||
import ( |
|
||||||
"io" |
|
||||||
"net/http" |
|
||||||
|
|
||||||
"golang.org/x/net/websocket" |
|
||||||
) |
|
||||||
|
|
||||||
// Echo the data received on the WebSocket.
|
|
||||||
func EchoServer(ws *websocket.Conn) { |
|
||||||
io.Copy(ws, ws) |
|
||||||
} |
|
||||||
|
|
||||||
// This example demonstrates a trivial echo server.
|
|
||||||
func ExampleHandler() { |
|
||||||
http.Handle("/echo", websocket.Handler(EchoServer)) |
|
||||||
err := http.ListenAndServe(":12345", nil) |
|
||||||
if err != nil { |
|
||||||
panic("ListenAndServe: " + err.Error()) |
|
||||||
} |
|
||||||
} |
|
@ -1,564 +0,0 @@ |
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package websocket |
|
||||||
|
|
||||||
// This file implements a protocol of hybi draft.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"bytes" |
|
||||||
"crypto/rand" |
|
||||||
"crypto/sha1" |
|
||||||
"encoding/base64" |
|
||||||
"encoding/binary" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"io/ioutil" |
|
||||||
"net/http" |
|
||||||
"net/url" |
|
||||||
"strings" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" |
|
||||||
|
|
||||||
closeStatusNormal = 1000 |
|
||||||
closeStatusGoingAway = 1001 |
|
||||||
closeStatusProtocolError = 1002 |
|
||||||
closeStatusUnsupportedData = 1003 |
|
||||||
closeStatusFrameTooLarge = 1004 |
|
||||||
closeStatusNoStatusRcvd = 1005 |
|
||||||
closeStatusAbnormalClosure = 1006 |
|
||||||
closeStatusBadMessageData = 1007 |
|
||||||
closeStatusPolicyViolation = 1008 |
|
||||||
closeStatusTooBigData = 1009 |
|
||||||
closeStatusExtensionMismatch = 1010 |
|
||||||
|
|
||||||
maxControlFramePayloadLength = 125 |
|
||||||
) |
|
||||||
|
|
||||||
var ( |
|
||||||
ErrBadMaskingKey = &ProtocolError{"bad masking key"} |
|
||||||
ErrBadPongMessage = &ProtocolError{"bad pong message"} |
|
||||||
ErrBadClosingStatus = &ProtocolError{"bad closing status"} |
|
||||||
ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"} |
|
||||||
ErrNotImplemented = &ProtocolError{"not implemented"} |
|
||||||
|
|
||||||
handshakeHeader = map[string]bool{ |
|
||||||
"Host": true, |
|
||||||
"Upgrade": true, |
|
||||||
"Connection": true, |
|
||||||
"Sec-Websocket-Key": true, |
|
||||||
"Sec-Websocket-Origin": true, |
|
||||||
"Sec-Websocket-Version": true, |
|
||||||
"Sec-Websocket-Protocol": true, |
|
||||||
"Sec-Websocket-Accept": true, |
|
||||||
} |
|
||||||
) |
|
||||||
|
|
||||||
// A hybiFrameHeader is a frame header as defined in hybi draft.
|
|
||||||
type hybiFrameHeader struct { |
|
||||||
Fin bool |
|
||||||
Rsv [3]bool |
|
||||||
OpCode byte |
|
||||||
Length int64 |
|
||||||
MaskingKey []byte |
|
||||||
|
|
||||||
data *bytes.Buffer |
|
||||||
} |
|
||||||
|
|
||||||
// A hybiFrameReader is a reader for hybi frame.
|
|
||||||
type hybiFrameReader struct { |
|
||||||
reader io.Reader |
|
||||||
|
|
||||||
header hybiFrameHeader |
|
||||||
pos int64 |
|
||||||
length int |
|
||||||
} |
|
||||||
|
|
||||||
func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) { |
|
||||||
n, err = frame.reader.Read(msg) |
|
||||||
if err != nil { |
|
||||||
return 0, err |
|
||||||
} |
|
||||||
if frame.header.MaskingKey != nil { |
|
||||||
for i := 0; i < n; i++ { |
|
||||||
msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4] |
|
||||||
frame.pos++ |
|
||||||
} |
|
||||||
} |
|
||||||
return n, err |
|
||||||
} |
|
||||||
|
|
||||||
func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode } |
|
||||||
|
|
||||||
func (frame *hybiFrameReader) HeaderReader() io.Reader { |
|
||||||
if frame.header.data == nil { |
|
||||||
return nil |
|
||||||
} |
|
||||||
if frame.header.data.Len() == 0 { |
|
||||||
return nil |
|
||||||
} |
|
||||||
return frame.header.data |
|
||||||
} |
|
||||||
|
|
||||||
func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil } |
|
||||||
|
|
||||||
func (frame *hybiFrameReader) Len() (n int) { return frame.length } |
|
||||||
|
|
||||||
// A hybiFrameReaderFactory creates new frame reader based on its frame type.
|
|
||||||
type hybiFrameReaderFactory struct { |
|
||||||
*bufio.Reader |
|
||||||
} |
|
||||||
|
|
||||||
// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
|
|
||||||
// See Section 5.2 Base Framing protocol for detail.
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
|
|
||||||
func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) { |
|
||||||
hybiFrame := new(hybiFrameReader) |
|
||||||
frame = hybiFrame |
|
||||||
var header []byte |
|
||||||
var b byte |
|
||||||
// First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
|
|
||||||
b, err = buf.ReadByte() |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
header = append(header, b) |
|
||||||
hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0 |
|
||||||
for i := 0; i < 3; i++ { |
|
||||||
j := uint(6 - i) |
|
||||||
hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0 |
|
||||||
} |
|
||||||
hybiFrame.header.OpCode = header[0] & 0x0f |
|
||||||
|
|
||||||
// Second byte. Mask/Payload len(7bits)
|
|
||||||
b, err = buf.ReadByte() |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
header = append(header, b) |
|
||||||
mask := (b & 0x80) != 0 |
|
||||||
b &= 0x7f |
|
||||||
lengthFields := 0 |
|
||||||
switch { |
|
||||||
case b <= 125: // Payload length 7bits.
|
|
||||||
hybiFrame.header.Length = int64(b) |
|
||||||
case b == 126: // Payload length 7+16bits
|
|
||||||
lengthFields = 2 |
|
||||||
case b == 127: // Payload length 7+64bits
|
|
||||||
lengthFields = 8 |
|
||||||
} |
|
||||||
for i := 0; i < lengthFields; i++ { |
|
||||||
b, err = buf.ReadByte() |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
header = append(header, b) |
|
||||||
hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b) |
|
||||||
} |
|
||||||
if mask { |
|
||||||
// Masking key. 4 bytes.
|
|
||||||
for i := 0; i < 4; i++ { |
|
||||||
b, err = buf.ReadByte() |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
header = append(header, b) |
|
||||||
hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b) |
|
||||||
} |
|
||||||
} |
|
||||||
hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length) |
|
||||||
hybiFrame.header.data = bytes.NewBuffer(header) |
|
||||||
hybiFrame.length = len(header) + int(hybiFrame.header.Length) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// A HybiFrameWriter is a writer for hybi frame.
|
|
||||||
type hybiFrameWriter struct { |
|
||||||
writer *bufio.Writer |
|
||||||
|
|
||||||
header *hybiFrameHeader |
|
||||||
} |
|
||||||
|
|
||||||
func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) { |
|
||||||
var header []byte |
|
||||||
var b byte |
|
||||||
if frame.header.Fin { |
|
||||||
b |= 0x80 |
|
||||||
} |
|
||||||
for i := 0; i < 3; i++ { |
|
||||||
if frame.header.Rsv[i] { |
|
||||||
j := uint(6 - i) |
|
||||||
b |= 1 << j |
|
||||||
} |
|
||||||
} |
|
||||||
b |= frame.header.OpCode |
|
||||||
header = append(header, b) |
|
||||||
if frame.header.MaskingKey != nil { |
|
||||||
b = 0x80 |
|
||||||
} else { |
|
||||||
b = 0 |
|
||||||
} |
|
||||||
lengthFields := 0 |
|
||||||
length := len(msg) |
|
||||||
switch { |
|
||||||
case length <= 125: |
|
||||||
b |= byte(length) |
|
||||||
case length < 65536: |
|
||||||
b |= 126 |
|
||||||
lengthFields = 2 |
|
||||||
default: |
|
||||||
b |= 127 |
|
||||||
lengthFields = 8 |
|
||||||
} |
|
||||||
header = append(header, b) |
|
||||||
for i := 0; i < lengthFields; i++ { |
|
||||||
j := uint((lengthFields - i - 1) * 8) |
|
||||||
b = byte((length >> j) & 0xff) |
|
||||||
header = append(header, b) |
|
||||||
} |
|
||||||
if frame.header.MaskingKey != nil { |
|
||||||
if len(frame.header.MaskingKey) != 4 { |
|
||||||
return 0, ErrBadMaskingKey |
|
||||||
} |
|
||||||
header = append(header, frame.header.MaskingKey...) |
|
||||||
frame.writer.Write(header) |
|
||||||
data := make([]byte, length) |
|
||||||
for i := range data { |
|
||||||
data[i] = msg[i] ^ frame.header.MaskingKey[i%4] |
|
||||||
} |
|
||||||
frame.writer.Write(data) |
|
||||||
err = frame.writer.Flush() |
|
||||||
return length, err |
|
||||||
} |
|
||||||
frame.writer.Write(header) |
|
||||||
frame.writer.Write(msg) |
|
||||||
err = frame.writer.Flush() |
|
||||||
return length, err |
|
||||||
} |
|
||||||
|
|
||||||
func (frame *hybiFrameWriter) Close() error { return nil } |
|
||||||
|
|
||||||
type hybiFrameWriterFactory struct { |
|
||||||
*bufio.Writer |
|
||||||
needMaskingKey bool |
|
||||||
} |
|
||||||
|
|
||||||
func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) { |
|
||||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType} |
|
||||||
if buf.needMaskingKey { |
|
||||||
frameHeader.MaskingKey, err = generateMaskingKey() |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
} |
|
||||||
return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil |
|
||||||
} |
|
||||||
|
|
||||||
type hybiFrameHandler struct { |
|
||||||
conn *Conn |
|
||||||
payloadType byte |
|
||||||
} |
|
||||||
|
|
||||||
func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (r frameReader, err error) { |
|
||||||
if handler.conn.IsServerConn() { |
|
||||||
// The client MUST mask all frames sent to the server.
|
|
||||||
if frame.(*hybiFrameReader).header.MaskingKey == nil { |
|
||||||
handler.WriteClose(closeStatusProtocolError) |
|
||||||
return nil, io.EOF |
|
||||||
} |
|
||||||
} else { |
|
||||||
// The server MUST NOT mask all frames.
|
|
||||||
if frame.(*hybiFrameReader).header.MaskingKey != nil { |
|
||||||
handler.WriteClose(closeStatusProtocolError) |
|
||||||
return nil, io.EOF |
|
||||||
} |
|
||||||
} |
|
||||||
if header := frame.HeaderReader(); header != nil { |
|
||||||
io.Copy(ioutil.Discard, header) |
|
||||||
} |
|
||||||
switch frame.PayloadType() { |
|
||||||
case ContinuationFrame: |
|
||||||
frame.(*hybiFrameReader).header.OpCode = handler.payloadType |
|
||||||
case TextFrame, BinaryFrame: |
|
||||||
handler.payloadType = frame.PayloadType() |
|
||||||
case CloseFrame: |
|
||||||
return nil, io.EOF |
|
||||||
case PingFrame: |
|
||||||
pingMsg := make([]byte, maxControlFramePayloadLength) |
|
||||||
n, err := io.ReadFull(frame, pingMsg) |
|
||||||
if err != nil && err != io.ErrUnexpectedEOF { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
io.Copy(ioutil.Discard, frame) |
|
||||||
n, err = handler.WritePong(pingMsg[:n]) |
|
||||||
if err != nil { |
|
||||||
return nil, err |
|
||||||
} |
|
||||||
return nil, nil |
|
||||||
case PongFrame: |
|
||||||
return nil, ErrNotImplemented |
|
||||||
} |
|
||||||
return frame, nil |
|
||||||
} |
|
||||||
|
|
||||||
func (handler *hybiFrameHandler) WriteClose(status int) (err error) { |
|
||||||
handler.conn.wio.Lock() |
|
||||||
defer handler.conn.wio.Unlock() |
|
||||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
msg := make([]byte, 2) |
|
||||||
binary.BigEndian.PutUint16(msg, uint16(status)) |
|
||||||
_, err = w.Write(msg) |
|
||||||
w.Close() |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) { |
|
||||||
handler.conn.wio.Lock() |
|
||||||
defer handler.conn.wio.Unlock() |
|
||||||
w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame) |
|
||||||
if err != nil { |
|
||||||
return 0, err |
|
||||||
} |
|
||||||
n, err = w.Write(msg) |
|
||||||
w.Close() |
|
||||||
return n, err |
|
||||||
} |
|
||||||
|
|
||||||
// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
|
|
||||||
func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { |
|
||||||
if buf == nil { |
|
||||||
br := bufio.NewReader(rwc) |
|
||||||
bw := bufio.NewWriter(rwc) |
|
||||||
buf = bufio.NewReadWriter(br, bw) |
|
||||||
} |
|
||||||
ws := &Conn{config: config, request: request, buf: buf, rwc: rwc, |
|
||||||
frameReaderFactory: hybiFrameReaderFactory{buf.Reader}, |
|
||||||
frameWriterFactory: hybiFrameWriterFactory{ |
|
||||||
buf.Writer, request == nil}, |
|
||||||
PayloadType: TextFrame, |
|
||||||
defaultCloseStatus: closeStatusNormal} |
|
||||||
ws.frameHandler = &hybiFrameHandler{conn: ws} |
|
||||||
return ws |
|
||||||
} |
|
||||||
|
|
||||||
// generateMaskingKey generates a masking key for a frame.
|
|
||||||
func generateMaskingKey() (maskingKey []byte, err error) { |
|
||||||
maskingKey = make([]byte, 4) |
|
||||||
if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// generateNonce generates a nonce consisting of a randomly selected 16-byte
|
|
||||||
// value that has been base64-encoded.
|
|
||||||
func generateNonce() (nonce []byte) { |
|
||||||
key := make([]byte, 16) |
|
||||||
if _, err := io.ReadFull(rand.Reader, key); err != nil { |
|
||||||
panic(err) |
|
||||||
} |
|
||||||
nonce = make([]byte, 24) |
|
||||||
base64.StdEncoding.Encode(nonce, key) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
|
|
||||||
// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
|
|
||||||
func getNonceAccept(nonce []byte) (expected []byte, err error) { |
|
||||||
h := sha1.New() |
|
||||||
if _, err = h.Write(nonce); err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
if _, err = h.Write([]byte(websocketGUID)); err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
expected = make([]byte, 28) |
|
||||||
base64.StdEncoding.Encode(expected, h.Sum(nil)) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
|
|
||||||
func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) { |
|
||||||
bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n") |
|
||||||
|
|
||||||
bw.WriteString("Host: " + config.Location.Host + "\r\n") |
|
||||||
bw.WriteString("Upgrade: websocket\r\n") |
|
||||||
bw.WriteString("Connection: Upgrade\r\n") |
|
||||||
nonce := generateNonce() |
|
||||||
if config.handshakeData != nil { |
|
||||||
nonce = []byte(config.handshakeData["key"]) |
|
||||||
} |
|
||||||
bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n") |
|
||||||
bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n") |
|
||||||
|
|
||||||
if config.Version != ProtocolVersionHybi13 { |
|
||||||
return ErrBadProtocolVersion |
|
||||||
} |
|
||||||
|
|
||||||
bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n") |
|
||||||
if len(config.Protocol) > 0 { |
|
||||||
bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n") |
|
||||||
} |
|
||||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
|
||||||
err = config.Header.WriteSubset(bw, handshakeHeader) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
bw.WriteString("\r\n") |
|
||||||
if err = bw.Flush(); err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
resp, err := http.ReadResponse(br, &http.Request{Method: "GET"}) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if resp.StatusCode != 101 { |
|
||||||
return ErrBadStatus |
|
||||||
} |
|
||||||
if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" || |
|
||||||
strings.ToLower(resp.Header.Get("Connection")) != "upgrade" { |
|
||||||
return ErrBadUpgrade |
|
||||||
} |
|
||||||
expectedAccept, err := getNonceAccept(nonce) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) { |
|
||||||
return ErrChallengeResponse |
|
||||||
} |
|
||||||
if resp.Header.Get("Sec-WebSocket-Extensions") != "" { |
|
||||||
return ErrUnsupportedExtensions |
|
||||||
} |
|
||||||
offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol") |
|
||||||
if offeredProtocol != "" { |
|
||||||
protocolMatched := false |
|
||||||
for i := 0; i < len(config.Protocol); i++ { |
|
||||||
if config.Protocol[i] == offeredProtocol { |
|
||||||
protocolMatched = true |
|
||||||
break |
|
||||||
} |
|
||||||
} |
|
||||||
if !protocolMatched { |
|
||||||
return ErrBadWebSocketProtocol |
|
||||||
} |
|
||||||
config.Protocol = []string{offeredProtocol} |
|
||||||
} |
|
||||||
|
|
||||||
return nil |
|
||||||
} |
|
||||||
|
|
||||||
// newHybiClientConn creates a client WebSocket connection after handshake.
|
|
||||||
func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn { |
|
||||||
return newHybiConn(config, buf, rwc, nil) |
|
||||||
} |
|
||||||
|
|
||||||
// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
|
|
||||||
type hybiServerHandshaker struct { |
|
||||||
*Config |
|
||||||
accept []byte |
|
||||||
} |
|
||||||
|
|
||||||
func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) { |
|
||||||
c.Version = ProtocolVersionHybi13 |
|
||||||
if req.Method != "GET" { |
|
||||||
return http.StatusMethodNotAllowed, ErrBadRequestMethod |
|
||||||
} |
|
||||||
// HTTP version can be safely ignored.
|
|
||||||
|
|
||||||
if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" || |
|
||||||
!strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") { |
|
||||||
return http.StatusBadRequest, ErrNotWebSocket |
|
||||||
} |
|
||||||
|
|
||||||
key := req.Header.Get("Sec-Websocket-Key") |
|
||||||
if key == "" { |
|
||||||
return http.StatusBadRequest, ErrChallengeResponse |
|
||||||
} |
|
||||||
version := req.Header.Get("Sec-Websocket-Version") |
|
||||||
switch version { |
|
||||||
case "13": |
|
||||||
c.Version = ProtocolVersionHybi13 |
|
||||||
default: |
|
||||||
return http.StatusBadRequest, ErrBadWebSocketVersion |
|
||||||
} |
|
||||||
var scheme string |
|
||||||
if req.TLS != nil { |
|
||||||
scheme = "wss" |
|
||||||
} else { |
|
||||||
scheme = "ws" |
|
||||||
} |
|
||||||
c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI()) |
|
||||||
if err != nil { |
|
||||||
return http.StatusBadRequest, err |
|
||||||
} |
|
||||||
protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol")) |
|
||||||
if protocol != "" { |
|
||||||
protocols := strings.Split(protocol, ",") |
|
||||||
for i := 0; i < len(protocols); i++ { |
|
||||||
c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i])) |
|
||||||
} |
|
||||||
} |
|
||||||
c.accept, err = getNonceAccept([]byte(key)) |
|
||||||
if err != nil { |
|
||||||
return http.StatusInternalServerError, err |
|
||||||
} |
|
||||||
return http.StatusSwitchingProtocols, nil |
|
||||||
} |
|
||||||
|
|
||||||
// Origin parses Origin header in "req".
|
|
||||||
// If origin is "null", returns (nil, nil).
|
|
||||||
func Origin(config *Config, req *http.Request) (*url.URL, error) { |
|
||||||
var origin string |
|
||||||
switch config.Version { |
|
||||||
case ProtocolVersionHybi13: |
|
||||||
origin = req.Header.Get("Origin") |
|
||||||
} |
|
||||||
if origin == "null" { |
|
||||||
return nil, nil |
|
||||||
} |
|
||||||
return url.ParseRequestURI(origin) |
|
||||||
} |
|
||||||
|
|
||||||
func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) { |
|
||||||
if len(c.Protocol) > 0 { |
|
||||||
if len(c.Protocol) != 1 { |
|
||||||
// You need choose a Protocol in Handshake func in Server.
|
|
||||||
return ErrBadWebSocketProtocol |
|
||||||
} |
|
||||||
} |
|
||||||
buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n") |
|
||||||
buf.WriteString("Upgrade: websocket\r\n") |
|
||||||
buf.WriteString("Connection: Upgrade\r\n") |
|
||||||
buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n") |
|
||||||
if len(c.Protocol) > 0 { |
|
||||||
buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n") |
|
||||||
} |
|
||||||
// TODO(ukai): send Sec-WebSocket-Extensions.
|
|
||||||
if c.Header != nil { |
|
||||||
err := c.Header.WriteSubset(buf, handshakeHeader) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
} |
|
||||||
buf.WriteString("\r\n") |
|
||||||
return buf.Flush() |
|
||||||
} |
|
||||||
|
|
||||||
func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { |
|
||||||
return newHybiServerConn(c.Config, buf, rwc, request) |
|
||||||
} |
|
||||||
|
|
||||||
// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
|
|
||||||
func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn { |
|
||||||
return newHybiConn(config, buf, rwc, request) |
|
||||||
} |
|
@ -1,590 +0,0 @@ |
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package websocket |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"bytes" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"net/http" |
|
||||||
"net/url" |
|
||||||
"strings" |
|
||||||
"testing" |
|
||||||
) |
|
||||||
|
|
||||||
// Test the getNonceAccept function with values in
|
|
||||||
// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
|
||||||
func TestSecWebSocketAccept(t *testing.T) { |
|
||||||
nonce := []byte("dGhlIHNhbXBsZSBub25jZQ==") |
|
||||||
expected := []byte("s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") |
|
||||||
accept, err := getNonceAccept(nonce) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("getNonceAccept: returned error %v", err) |
|
||||||
return |
|
||||||
} |
|
||||||
if !bytes.Equal(expected, accept) { |
|
||||||
t.Errorf("getNonceAccept: expected %q got %q", expected, accept) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiClientHandshake(t *testing.T) { |
|
||||||
b := bytes.NewBuffer([]byte{}) |
|
||||||
bw := bufio.NewWriter(b) |
|
||||||
br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols |
|
||||||
Upgrade: websocket |
|
||||||
Connection: Upgrade |
|
||||||
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= |
|
||||||
Sec-WebSocket-Protocol: chat |
|
||||||
|
|
||||||
`)) |
|
||||||
var err error |
|
||||||
config := new(Config) |
|
||||||
config.Location, err = url.ParseRequestURI("ws://server.example.com/chat") |
|
||||||
if err != nil { |
|
||||||
t.Fatal("location url", err) |
|
||||||
} |
|
||||||
config.Origin, err = url.ParseRequestURI("http://example.com") |
|
||||||
if err != nil { |
|
||||||
t.Fatal("origin url", err) |
|
||||||
} |
|
||||||
config.Protocol = append(config.Protocol, "chat") |
|
||||||
config.Protocol = append(config.Protocol, "superchat") |
|
||||||
config.Version = ProtocolVersionHybi13 |
|
||||||
|
|
||||||
config.handshakeData = map[string]string{ |
|
||||||
"key": "dGhlIHNhbXBsZSBub25jZQ==", |
|
||||||
} |
|
||||||
err = hybiClientHandshake(config, br, bw) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake failed: %v", err) |
|
||||||
} |
|
||||||
req, err := http.ReadRequest(bufio.NewReader(b)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("read request: %v", err) |
|
||||||
} |
|
||||||
if req.Method != "GET" { |
|
||||||
t.Errorf("request method expected GET, but got %q", req.Method) |
|
||||||
} |
|
||||||
if req.URL.Path != "/chat" { |
|
||||||
t.Errorf("request path expected /chat, but got %q", req.URL.Path) |
|
||||||
} |
|
||||||
if req.Proto != "HTTP/1.1" { |
|
||||||
t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto) |
|
||||||
} |
|
||||||
if req.Host != "server.example.com" { |
|
||||||
t.Errorf("request Host expected server.example.com, but got %v", req.Host) |
|
||||||
} |
|
||||||
var expectedHeader = map[string]string{ |
|
||||||
"Connection": "Upgrade", |
|
||||||
"Upgrade": "websocket", |
|
||||||
"Sec-Websocket-Key": config.handshakeData["key"], |
|
||||||
"Origin": config.Origin.String(), |
|
||||||
"Sec-Websocket-Protocol": "chat, superchat", |
|
||||||
"Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13), |
|
||||||
} |
|
||||||
for k, v := range expectedHeader { |
|
||||||
if req.Header.Get(k) != v { |
|
||||||
t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k))) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiClientHandshakeWithHeader(t *testing.T) { |
|
||||||
b := bytes.NewBuffer([]byte{}) |
|
||||||
bw := bufio.NewWriter(b) |
|
||||||
br := bufio.NewReader(strings.NewReader(`HTTP/1.1 101 Switching Protocols |
|
||||||
Upgrade: websocket |
|
||||||
Connection: Upgrade |
|
||||||
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= |
|
||||||
Sec-WebSocket-Protocol: chat |
|
||||||
|
|
||||||
`)) |
|
||||||
var err error |
|
||||||
config := new(Config) |
|
||||||
config.Location, err = url.ParseRequestURI("ws://server.example.com/chat") |
|
||||||
if err != nil { |
|
||||||
t.Fatal("location url", err) |
|
||||||
} |
|
||||||
config.Origin, err = url.ParseRequestURI("http://example.com") |
|
||||||
if err != nil { |
|
||||||
t.Fatal("origin url", err) |
|
||||||
} |
|
||||||
config.Protocol = append(config.Protocol, "chat") |
|
||||||
config.Protocol = append(config.Protocol, "superchat") |
|
||||||
config.Version = ProtocolVersionHybi13 |
|
||||||
config.Header = http.Header(make(map[string][]string)) |
|
||||||
config.Header.Add("User-Agent", "test") |
|
||||||
|
|
||||||
config.handshakeData = map[string]string{ |
|
||||||
"key": "dGhlIHNhbXBsZSBub25jZQ==", |
|
||||||
} |
|
||||||
err = hybiClientHandshake(config, br, bw) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake failed: %v", err) |
|
||||||
} |
|
||||||
req, err := http.ReadRequest(bufio.NewReader(b)) |
|
||||||
if err != nil { |
|
||||||
t.Fatalf("read request: %v", err) |
|
||||||
} |
|
||||||
if req.Method != "GET" { |
|
||||||
t.Errorf("request method expected GET, but got %q", req.Method) |
|
||||||
} |
|
||||||
if req.URL.Path != "/chat" { |
|
||||||
t.Errorf("request path expected /chat, but got %q", req.URL.Path) |
|
||||||
} |
|
||||||
if req.Proto != "HTTP/1.1" { |
|
||||||
t.Errorf("request proto expected HTTP/1.1, but got %q", req.Proto) |
|
||||||
} |
|
||||||
if req.Host != "server.example.com" { |
|
||||||
t.Errorf("request Host expected server.example.com, but got %v", req.Host) |
|
||||||
} |
|
||||||
var expectedHeader = map[string]string{ |
|
||||||
"Connection": "Upgrade", |
|
||||||
"Upgrade": "websocket", |
|
||||||
"Sec-Websocket-Key": config.handshakeData["key"], |
|
||||||
"Origin": config.Origin.String(), |
|
||||||
"Sec-Websocket-Protocol": "chat, superchat", |
|
||||||
"Sec-Websocket-Version": fmt.Sprintf("%d", ProtocolVersionHybi13), |
|
||||||
"User-Agent": "test", |
|
||||||
} |
|
||||||
for k, v := range expectedHeader { |
|
||||||
if req.Header.Get(k) != v { |
|
||||||
t.Errorf(fmt.Sprintf("%s expected %q but got %q", k, v, req.Header.Get(k))) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiServerHandshake(t *testing.T) { |
|
||||||
config := new(Config) |
|
||||||
handshaker := &hybiServerHandshaker{Config: config} |
|
||||||
br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 |
|
||||||
Host: server.example.com |
|
||||||
Upgrade: websocket |
|
||||||
Connection: Upgrade |
|
||||||
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== |
|
||||||
Origin: http://example.com
|
|
||||||
Sec-WebSocket-Protocol: chat, superchat |
|
||||||
Sec-WebSocket-Version: 13 |
|
||||||
|
|
||||||
`)) |
|
||||||
req, err := http.ReadRequest(br) |
|
||||||
if err != nil { |
|
||||||
t.Fatal("request", err) |
|
||||||
} |
|
||||||
code, err := handshaker.ReadHandshake(br, req) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake failed: %v", err) |
|
||||||
} |
|
||||||
if code != http.StatusSwitchingProtocols { |
|
||||||
t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) |
|
||||||
} |
|
||||||
expectedProtocols := []string{"chat", "superchat"} |
|
||||||
if fmt.Sprintf("%v", config.Protocol) != fmt.Sprintf("%v", expectedProtocols) { |
|
||||||
t.Errorf("protocol expected %q but got %q", expectedProtocols, config.Protocol) |
|
||||||
} |
|
||||||
b := bytes.NewBuffer([]byte{}) |
|
||||||
bw := bufio.NewWriter(b) |
|
||||||
|
|
||||||
config.Protocol = config.Protocol[:1] |
|
||||||
|
|
||||||
err = handshaker.AcceptHandshake(bw) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake response failed: %v", err) |
|
||||||
} |
|
||||||
expectedResponse := strings.Join([]string{ |
|
||||||
"HTTP/1.1 101 Switching Protocols", |
|
||||||
"Upgrade: websocket", |
|
||||||
"Connection: Upgrade", |
|
||||||
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", |
|
||||||
"Sec-WebSocket-Protocol: chat", |
|
||||||
"", ""}, "\r\n") |
|
||||||
|
|
||||||
if b.String() != expectedResponse { |
|
||||||
t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiServerHandshakeNoSubProtocol(t *testing.T) { |
|
||||||
config := new(Config) |
|
||||||
handshaker := &hybiServerHandshaker{Config: config} |
|
||||||
br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 |
|
||||||
Host: server.example.com |
|
||||||
Upgrade: websocket |
|
||||||
Connection: Upgrade |
|
||||||
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== |
|
||||||
Origin: http://example.com
|
|
||||||
Sec-WebSocket-Version: 13 |
|
||||||
|
|
||||||
`)) |
|
||||||
req, err := http.ReadRequest(br) |
|
||||||
if err != nil { |
|
||||||
t.Fatal("request", err) |
|
||||||
} |
|
||||||
code, err := handshaker.ReadHandshake(br, req) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake failed: %v", err) |
|
||||||
} |
|
||||||
if code != http.StatusSwitchingProtocols { |
|
||||||
t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) |
|
||||||
} |
|
||||||
if len(config.Protocol) != 0 { |
|
||||||
t.Errorf("len(config.Protocol) expected 0, but got %q", len(config.Protocol)) |
|
||||||
} |
|
||||||
b := bytes.NewBuffer([]byte{}) |
|
||||||
bw := bufio.NewWriter(b) |
|
||||||
|
|
||||||
err = handshaker.AcceptHandshake(bw) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake response failed: %v", err) |
|
||||||
} |
|
||||||
expectedResponse := strings.Join([]string{ |
|
||||||
"HTTP/1.1 101 Switching Protocols", |
|
||||||
"Upgrade: websocket", |
|
||||||
"Connection: Upgrade", |
|
||||||
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", |
|
||||||
"", ""}, "\r\n") |
|
||||||
|
|
||||||
if b.String() != expectedResponse { |
|
||||||
t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiServerHandshakeHybiBadVersion(t *testing.T) { |
|
||||||
config := new(Config) |
|
||||||
handshaker := &hybiServerHandshaker{Config: config} |
|
||||||
br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 |
|
||||||
Host: server.example.com |
|
||||||
Upgrade: websocket |
|
||||||
Connection: Upgrade |
|
||||||
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== |
|
||||||
Sec-WebSocket-Origin: http://example.com
|
|
||||||
Sec-WebSocket-Protocol: chat, superchat |
|
||||||
Sec-WebSocket-Version: 9 |
|
||||||
|
|
||||||
`)) |
|
||||||
req, err := http.ReadRequest(br) |
|
||||||
if err != nil { |
|
||||||
t.Fatal("request", err) |
|
||||||
} |
|
||||||
code, err := handshaker.ReadHandshake(br, req) |
|
||||||
if err != ErrBadWebSocketVersion { |
|
||||||
t.Errorf("handshake expected err %q but got %q", ErrBadWebSocketVersion, err) |
|
||||||
} |
|
||||||
if code != http.StatusBadRequest { |
|
||||||
t.Errorf("status expected %q but got %q", http.StatusBadRequest, code) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func testHybiFrame(t *testing.T, testHeader, testPayload, testMaskedPayload []byte, frameHeader *hybiFrameHeader) { |
|
||||||
b := bytes.NewBuffer([]byte{}) |
|
||||||
frameWriterFactory := &hybiFrameWriterFactory{bufio.NewWriter(b), false} |
|
||||||
w, _ := frameWriterFactory.NewFrameWriter(TextFrame) |
|
||||||
w.(*hybiFrameWriter).header = frameHeader |
|
||||||
_, err := w.Write(testPayload) |
|
||||||
w.Close() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Write error %q", err) |
|
||||||
} |
|
||||||
var expectedFrame []byte |
|
||||||
expectedFrame = append(expectedFrame, testHeader...) |
|
||||||
expectedFrame = append(expectedFrame, testMaskedPayload...) |
|
||||||
if !bytes.Equal(expectedFrame, b.Bytes()) { |
|
||||||
t.Errorf("frame expected %q got %q", expectedFrame, b.Bytes()) |
|
||||||
} |
|
||||||
frameReaderFactory := &hybiFrameReaderFactory{bufio.NewReader(b)} |
|
||||||
r, err := frameReaderFactory.NewFrameReader() |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Read error %q", err) |
|
||||||
} |
|
||||||
if header := r.HeaderReader(); header == nil { |
|
||||||
t.Errorf("no header") |
|
||||||
} else { |
|
||||||
actualHeader := make([]byte, r.Len()) |
|
||||||
n, err := header.Read(actualHeader) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("Read header error %q", err) |
|
||||||
} else { |
|
||||||
if n < len(testHeader) { |
|
||||||
t.Errorf("header too short %q got %q", testHeader, actualHeader[:n]) |
|
||||||
} |
|
||||||
if !bytes.Equal(testHeader, actualHeader[:n]) { |
|
||||||
t.Errorf("header expected %q got %q", testHeader, actualHeader[:n]) |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if trailer := r.TrailerReader(); trailer != nil { |
|
||||||
t.Errorf("unexpected trailer %q", trailer) |
|
||||||
} |
|
||||||
frame := r.(*hybiFrameReader) |
|
||||||
if frameHeader.Fin != frame.header.Fin || |
|
||||||
frameHeader.OpCode != frame.header.OpCode || |
|
||||||
len(testPayload) != int(frame.header.Length) { |
|
||||||
t.Errorf("mismatch %v (%d) vs %v", frameHeader, len(testPayload), frame) |
|
||||||
} |
|
||||||
payload := make([]byte, len(testPayload)) |
|
||||||
_, err = r.Read(payload) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("read %v", err) |
|
||||||
} |
|
||||||
if !bytes.Equal(testPayload, payload) { |
|
||||||
t.Errorf("payload %q vs %q", testPayload, payload) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiShortTextFrame(t *testing.T) { |
|
||||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame} |
|
||||||
payload := []byte("hello") |
|
||||||
testHybiFrame(t, []byte{0x81, 0x05}, payload, payload, frameHeader) |
|
||||||
|
|
||||||
payload = make([]byte, 125) |
|
||||||
testHybiFrame(t, []byte{0x81, 125}, payload, payload, frameHeader) |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiShortMaskedTextFrame(t *testing.T) { |
|
||||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame, |
|
||||||
MaskingKey: []byte{0xcc, 0x55, 0x80, 0x20}} |
|
||||||
payload := []byte("hello") |
|
||||||
maskedPayload := []byte{0xa4, 0x30, 0xec, 0x4c, 0xa3} |
|
||||||
header := []byte{0x81, 0x85} |
|
||||||
header = append(header, frameHeader.MaskingKey...) |
|
||||||
testHybiFrame(t, header, payload, maskedPayload, frameHeader) |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiShortBinaryFrame(t *testing.T) { |
|
||||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: BinaryFrame} |
|
||||||
payload := []byte("hello") |
|
||||||
testHybiFrame(t, []byte{0x82, 0x05}, payload, payload, frameHeader) |
|
||||||
|
|
||||||
payload = make([]byte, 125) |
|
||||||
testHybiFrame(t, []byte{0x82, 125}, payload, payload, frameHeader) |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiControlFrame(t *testing.T) { |
|
||||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: PingFrame} |
|
||||||
payload := []byte("hello") |
|
||||||
testHybiFrame(t, []byte{0x89, 0x05}, payload, payload, frameHeader) |
|
||||||
|
|
||||||
frameHeader = &hybiFrameHeader{Fin: true, OpCode: PongFrame} |
|
||||||
testHybiFrame(t, []byte{0x8A, 0x05}, payload, payload, frameHeader) |
|
||||||
|
|
||||||
frameHeader = &hybiFrameHeader{Fin: true, OpCode: CloseFrame} |
|
||||||
payload = []byte{0x03, 0xe8} // 1000
|
|
||||||
testHybiFrame(t, []byte{0x88, 0x02}, payload, payload, frameHeader) |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiLongFrame(t *testing.T) { |
|
||||||
frameHeader := &hybiFrameHeader{Fin: true, OpCode: TextFrame} |
|
||||||
payload := make([]byte, 126) |
|
||||||
testHybiFrame(t, []byte{0x81, 126, 0x00, 126}, payload, payload, frameHeader) |
|
||||||
|
|
||||||
payload = make([]byte, 65535) |
|
||||||
testHybiFrame(t, []byte{0x81, 126, 0xff, 0xff}, payload, payload, frameHeader) |
|
||||||
|
|
||||||
payload = make([]byte, 65536) |
|
||||||
testHybiFrame(t, []byte{0x81, 127, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00}, payload, payload, frameHeader) |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiClientRead(t *testing.T) { |
|
||||||
wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o', |
|
||||||
0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
|
|
||||||
0x81, 0x05, 'w', 'o', 'r', 'l', 'd'} |
|
||||||
br := bufio.NewReader(bytes.NewBuffer(wireData)) |
|
||||||
bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) |
|
||||||
conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) |
|
||||||
|
|
||||||
msg := make([]byte, 512) |
|
||||||
n, err := conn.Read(msg) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("read 1st frame, error %q", err) |
|
||||||
} |
|
||||||
if n != 5 { |
|
||||||
t.Errorf("read 1st frame, expect 5, got %d", n) |
|
||||||
} |
|
||||||
if !bytes.Equal(wireData[2:7], msg[:n]) { |
|
||||||
t.Errorf("read 1st frame %v, got %v", wireData[2:7], msg[:n]) |
|
||||||
} |
|
||||||
n, err = conn.Read(msg) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("read 2nd frame, error %q", err) |
|
||||||
} |
|
||||||
if n != 5 { |
|
||||||
t.Errorf("read 2nd frame, expect 5, got %d", n) |
|
||||||
} |
|
||||||
if !bytes.Equal(wireData[16:21], msg[:n]) { |
|
||||||
t.Errorf("read 2nd frame %v, got %v", wireData[16:21], msg[:n]) |
|
||||||
} |
|
||||||
n, err = conn.Read(msg) |
|
||||||
if err == nil { |
|
||||||
t.Errorf("read not EOF") |
|
||||||
} |
|
||||||
if n != 0 { |
|
||||||
t.Errorf("expect read 0, got %d", n) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiShortRead(t *testing.T) { |
|
||||||
wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o', |
|
||||||
0x89, 0x05, 'h', 'e', 'l', 'l', 'o', // ping
|
|
||||||
0x81, 0x05, 'w', 'o', 'r', 'l', 'd'} |
|
||||||
br := bufio.NewReader(bytes.NewBuffer(wireData)) |
|
||||||
bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) |
|
||||||
conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) |
|
||||||
|
|
||||||
step := 0 |
|
||||||
pos := 0 |
|
||||||
expectedPos := []int{2, 5, 16, 19} |
|
||||||
expectedLen := []int{3, 2, 3, 2} |
|
||||||
for { |
|
||||||
msg := make([]byte, 3) |
|
||||||
n, err := conn.Read(msg) |
|
||||||
if step >= len(expectedPos) { |
|
||||||
if err == nil { |
|
||||||
t.Errorf("read not EOF") |
|
||||||
} |
|
||||||
if n != 0 { |
|
||||||
t.Errorf("expect read 0, got %d", n) |
|
||||||
} |
|
||||||
return |
|
||||||
} |
|
||||||
pos = expectedPos[step] |
|
||||||
endPos := pos + expectedLen[step] |
|
||||||
if err != nil { |
|
||||||
t.Errorf("read from %d, got error %q", pos, err) |
|
||||||
return |
|
||||||
} |
|
||||||
if n != endPos-pos { |
|
||||||
t.Errorf("read from %d, expect %d, got %d", pos, endPos-pos, n) |
|
||||||
} |
|
||||||
if !bytes.Equal(wireData[pos:endPos], msg[:n]) { |
|
||||||
t.Errorf("read from %d, frame %v, got %v", pos, wireData[pos:endPos], msg[:n]) |
|
||||||
} |
|
||||||
step++ |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiServerRead(t *testing.T) { |
|
||||||
wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20, |
|
||||||
0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
|
|
||||||
0x89, 0x85, 0xcc, 0x55, 0x80, 0x20, |
|
||||||
0xa4, 0x30, 0xec, 0x4c, 0xa3, // ping: hello
|
|
||||||
0x81, 0x85, 0xed, 0x83, 0xb4, 0x24, |
|
||||||
0x9a, 0xec, 0xc6, 0x48, 0x89, // world
|
|
||||||
} |
|
||||||
br := bufio.NewReader(bytes.NewBuffer(wireData)) |
|
||||||
bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) |
|
||||||
conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request)) |
|
||||||
|
|
||||||
expected := [][]byte{[]byte("hello"), []byte("world")} |
|
||||||
|
|
||||||
msg := make([]byte, 512) |
|
||||||
n, err := conn.Read(msg) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("read 1st frame, error %q", err) |
|
||||||
} |
|
||||||
if n != 5 { |
|
||||||
t.Errorf("read 1st frame, expect 5, got %d", n) |
|
||||||
} |
|
||||||
if !bytes.Equal(expected[0], msg[:n]) { |
|
||||||
t.Errorf("read 1st frame %q, got %q", expected[0], msg[:n]) |
|
||||||
} |
|
||||||
|
|
||||||
n, err = conn.Read(msg) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("read 2nd frame, error %q", err) |
|
||||||
} |
|
||||||
if n != 5 { |
|
||||||
t.Errorf("read 2nd frame, expect 5, got %d", n) |
|
||||||
} |
|
||||||
if !bytes.Equal(expected[1], msg[:n]) { |
|
||||||
t.Errorf("read 2nd frame %q, got %q", expected[1], msg[:n]) |
|
||||||
} |
|
||||||
|
|
||||||
n, err = conn.Read(msg) |
|
||||||
if err == nil { |
|
||||||
t.Errorf("read not EOF") |
|
||||||
} |
|
||||||
if n != 0 { |
|
||||||
t.Errorf("expect read 0, got %d", n) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiServerReadWithoutMasking(t *testing.T) { |
|
||||||
wireData := []byte{0x81, 0x05, 'h', 'e', 'l', 'l', 'o'} |
|
||||||
br := bufio.NewReader(bytes.NewBuffer(wireData)) |
|
||||||
bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) |
|
||||||
conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, new(http.Request)) |
|
||||||
// server MUST close the connection upon receiving a non-masked frame.
|
|
||||||
msg := make([]byte, 512) |
|
||||||
_, err := conn.Read(msg) |
|
||||||
if err != io.EOF { |
|
||||||
t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
func TestHybiClientReadWithMasking(t *testing.T) { |
|
||||||
wireData := []byte{0x81, 0x85, 0xcc, 0x55, 0x80, 0x20, |
|
||||||
0xa4, 0x30, 0xec, 0x4c, 0xa3, // hello
|
|
||||||
} |
|
||||||
br := bufio.NewReader(bytes.NewBuffer(wireData)) |
|
||||||
bw := bufio.NewWriter(bytes.NewBuffer([]byte{})) |
|
||||||
conn := newHybiConn(newConfig(t, "/"), bufio.NewReadWriter(br, bw), nil, nil) |
|
||||||
|
|
||||||
// client MUST close the connection upon receiving a masked frame.
|
|
||||||
msg := make([]byte, 512) |
|
||||||
_, err := conn.Read(msg) |
|
||||||
if err != io.EOF { |
|
||||||
t.Errorf("read 1st frame, expect %q, but got %q", io.EOF, err) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
// Test the hybiServerHandshaker supports firefox implementation and
|
|
||||||
// checks Connection request header include (but it's not necessary
|
|
||||||
// equal to) "upgrade"
|
|
||||||
func TestHybiServerFirefoxHandshake(t *testing.T) { |
|
||||||
config := new(Config) |
|
||||||
handshaker := &hybiServerHandshaker{Config: config} |
|
||||||
br := bufio.NewReader(strings.NewReader(`GET /chat HTTP/1.1 |
|
||||||
Host: server.example.com |
|
||||||
Upgrade: websocket |
|
||||||
Connection: keep-alive, upgrade |
|
||||||
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== |
|
||||||
Origin: http://example.com
|
|
||||||
Sec-WebSocket-Protocol: chat, superchat |
|
||||||
Sec-WebSocket-Version: 13 |
|
||||||
|
|
||||||
`)) |
|
||||||
req, err := http.ReadRequest(br) |
|
||||||
if err != nil { |
|
||||||
t.Fatal("request", err) |
|
||||||
} |
|
||||||
code, err := handshaker.ReadHandshake(br, req) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake failed: %v", err) |
|
||||||
} |
|
||||||
if code != http.StatusSwitchingProtocols { |
|
||||||
t.Errorf("status expected %q but got %q", http.StatusSwitchingProtocols, code) |
|
||||||
} |
|
||||||
b := bytes.NewBuffer([]byte{}) |
|
||||||
bw := bufio.NewWriter(b) |
|
||||||
|
|
||||||
config.Protocol = []string{"chat"} |
|
||||||
|
|
||||||
err = handshaker.AcceptHandshake(bw) |
|
||||||
if err != nil { |
|
||||||
t.Errorf("handshake response failed: %v", err) |
|
||||||
} |
|
||||||
expectedResponse := strings.Join([]string{ |
|
||||||
"HTTP/1.1 101 Switching Protocols", |
|
||||||
"Upgrade: websocket", |
|
||||||
"Connection: Upgrade", |
|
||||||
"Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", |
|
||||||
"Sec-WebSocket-Protocol: chat", |
|
||||||
"", ""}, "\r\n") |
|
||||||
|
|
||||||
if b.String() != expectedResponse { |
|
||||||
t.Errorf("handshake expected %q but got %q", expectedResponse, b.String()) |
|
||||||
} |
|
||||||
} |
|
@ -1,114 +0,0 @@ |
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package websocket |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"fmt" |
|
||||||
"io" |
|
||||||
"net/http" |
|
||||||
) |
|
||||||
|
|
||||||
func newServerConn(rwc io.ReadWriteCloser, buf *bufio.ReadWriter, req *http.Request, config *Config, handshake func(*Config, *http.Request) error) (conn *Conn, err error) { |
|
||||||
var hs serverHandshaker = &hybiServerHandshaker{Config: config} |
|
||||||
code, err := hs.ReadHandshake(buf.Reader, req) |
|
||||||
if err == ErrBadWebSocketVersion { |
|
||||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) |
|
||||||
fmt.Fprintf(buf, "Sec-WebSocket-Version: %s\r\n", SupportedProtocolVersion) |
|
||||||
buf.WriteString("\r\n") |
|
||||||
buf.WriteString(err.Error()) |
|
||||||
buf.Flush() |
|
||||||
return |
|
||||||
} |
|
||||||
if err != nil { |
|
||||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) |
|
||||||
buf.WriteString("\r\n") |
|
||||||
buf.WriteString(err.Error()) |
|
||||||
buf.Flush() |
|
||||||
return |
|
||||||
} |
|
||||||
if handshake != nil { |
|
||||||
err = handshake(config, req) |
|
||||||
if err != nil { |
|
||||||
code = http.StatusForbidden |
|
||||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) |
|
||||||
buf.WriteString("\r\n") |
|
||||||
buf.Flush() |
|
||||||
return |
|
||||||
} |
|
||||||
} |
|
||||||
err = hs.AcceptHandshake(buf.Writer) |
|
||||||
if err != nil { |
|
||||||
code = http.StatusBadRequest |
|
||||||
fmt.Fprintf(buf, "HTTP/1.1 %03d %s\r\n", code, http.StatusText(code)) |
|
||||||
buf.WriteString("\r\n") |
|
||||||
buf.Flush() |
|
||||||
return |
|
||||||
} |
|
||||||
conn = hs.NewServerConn(buf, rwc, req) |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
// Server represents a server of a WebSocket.
|
|
||||||
type Server struct { |
|
||||||
// Config is a WebSocket configuration for new WebSocket connection.
|
|
||||||
Config |
|
||||||
|
|
||||||
// Handshake is an optional function in WebSocket handshake.
|
|
||||||
// For example, you can check, or don't check Origin header.
|
|
||||||
// Another example, you can select config.Protocol.
|
|
||||||
Handshake func(*Config, *http.Request) error |
|
||||||
|
|
||||||
// Handler handles a WebSocket connection.
|
|
||||||
Handler |
|
||||||
} |
|
||||||
|
|
||||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
|
||||||
func (s Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
|
||||||
s.serveWebSocket(w, req) |
|
||||||
} |
|
||||||
|
|
||||||
func (s Server) serveWebSocket(w http.ResponseWriter, req *http.Request) { |
|
||||||
rwc, buf, err := w.(http.Hijacker).Hijack() |
|
||||||
if err != nil { |
|
||||||
panic("Hijack failed: " + err.Error()) |
|
||||||
return |
|
||||||
} |
|
||||||
// The server should abort the WebSocket connection if it finds
|
|
||||||
// the client did not send a handshake that matches with protocol
|
|
||||||
// specification.
|
|
||||||
defer rwc.Close() |
|
||||||
conn, err := newServerConn(rwc, buf, req, &s.Config, s.Handshake) |
|
||||||
if err != nil { |
|
||||||
return |
|
||||||
} |
|
||||||
if conn == nil { |
|
||||||
panic("unexpected nil conn") |
|
||||||
} |
|
||||||
s.Handler(conn) |
|
||||||
} |
|
||||||
|
|
||||||
// Handler is a simple interface to a WebSocket browser client.
|
|
||||||
// It checks if Origin header is valid URL by default.
|
|
||||||
// You might want to verify websocket.Conn.Config().Origin in the func.
|
|
||||||
// If you use Server instead of Handler, you could call websocket.Origin and
|
|
||||||
// check the origin in your Handshake func. So, if you want to accept
|
|
||||||
// non-browser client, which doesn't send Origin header, you could use Server
|
|
||||||
//. that doesn't check origin in its Handshake.
|
|
||||||
type Handler func(*Conn) |
|
||||||
|
|
||||||
func checkOrigin(config *Config, req *http.Request) (err error) { |
|
||||||
config.Origin, err = Origin(config, req) |
|
||||||
if err == nil && config.Origin == nil { |
|
||||||
return fmt.Errorf("null origin") |
|
||||||
} |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// ServeHTTP implements the http.Handler interface for a WebSocket
|
|
||||||
func (h Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { |
|
||||||
s := Server{Handler: h, Handshake: checkOrigin} |
|
||||||
s.serveWebSocket(w, req) |
|
||||||
} |
|
@ -1,411 +0,0 @@ |
|||||||
// Copyright 2009 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package websocket implements a client and server for the WebSocket protocol
|
|
||||||
// as specified in RFC 6455.
|
|
||||||
package websocket |
|
||||||
|
|
||||||
import ( |
|
||||||
"bufio" |
|
||||||
"crypto/tls" |
|
||||||
"encoding/json" |
|
||||||
"errors" |
|
||||||
"io" |
|
||||||
"io/ioutil" |
|
||||||
"net" |
|
||||||
"net/http" |
|
||||||
"net/url" |
|
||||||
"sync" |
|
||||||
"time" |
|
||||||
) |
|
||||||
|
|
||||||
const ( |
|
||||||
ProtocolVersionHybi13 = 13 |
|
||||||
ProtocolVersionHybi = ProtocolVersionHybi13 |
|
||||||
SupportedProtocolVersion = "13" |
|
||||||
|
|
||||||
ContinuationFrame = 0 |
|
||||||
TextFrame = 1 |
|
||||||
BinaryFrame = 2 |
|
||||||
CloseFrame = 8 |
|
||||||
PingFrame = 9 |
|
||||||
PongFrame = 10 |
|
||||||
UnknownFrame = 255 |
|
||||||
) |
|
||||||
|
|
||||||
// ProtocolError represents WebSocket protocol errors.
|
|
||||||
type ProtocolError struct { |
|
||||||
ErrorString string |
|
||||||
} |
|
||||||
|
|
||||||
func (err *ProtocolError) Error() string { return err.ErrorString } |
|
||||||
|
|
||||||
var ( |
|
||||||
ErrBadProtocolVersion = &ProtocolError{"bad protocol version"} |
|
||||||
ErrBadScheme = &ProtocolError{"bad scheme"} |
|
||||||
ErrBadStatus = &ProtocolError{"bad status"} |
|
||||||
ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"} |
|
||||||
ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"} |
|
||||||
ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"} |
|
||||||
ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"} |
|
||||||
ErrBadWebSocketVersion = &ProtocolError{"missing or bad WebSocket Version"} |
|
||||||
ErrChallengeResponse = &ProtocolError{"mismatch challenge/response"} |
|
||||||
ErrBadFrame = &ProtocolError{"bad frame"} |
|
||||||
ErrBadFrameBoundary = &ProtocolError{"not on frame boundary"} |
|
||||||
ErrNotWebSocket = &ProtocolError{"not websocket protocol"} |
|
||||||
ErrBadRequestMethod = &ProtocolError{"bad method"} |
|
||||||
ErrNotSupported = &ProtocolError{"not supported"} |
|
||||||
) |
|
||||||
|
|
||||||
// Addr is an implementation of net.Addr for WebSocket.
|
|
||||||
type Addr struct { |
|
||||||
*url.URL |
|
||||||
} |
|
||||||
|
|
||||||
// Network returns the network type for a WebSocket, "websocket".
|
|
||||||
func (addr *Addr) Network() string { return "websocket" } |
|
||||||
|
|
||||||
// Config is a WebSocket configuration
|
|
||||||
type Config struct { |
|
||||||
// A WebSocket server address.
|
|
||||||
Location *url.URL |
|
||||||
|
|
||||||
// A Websocket client origin.
|
|
||||||
Origin *url.URL |
|
||||||
|
|
||||||
// WebSocket subprotocols.
|
|
||||||
Protocol []string |
|
||||||
|
|
||||||
// WebSocket protocol version.
|
|
||||||
Version int |
|
||||||
|
|
||||||
// TLS config for secure WebSocket (wss).
|
|
||||||
TlsConfig *tls.Config |
|
||||||
|
|
||||||
// Additional header fields to be sent in WebSocket opening handshake.
|
|
||||||
Header http.Header |
|
||||||
|
|
||||||
handshakeData map[string]string |
|
||||||
} |
|
||||||
|
|
||||||
// serverHandshaker is an interface to handle WebSocket server side handshake.
|
|
||||||
type serverHandshaker interface { |
|
||||||
// ReadHandshake reads handshake request message from client.
|
|
||||||
// Returns http response code and error if any.
|
|
||||||
ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) |
|
||||||
|
|
||||||
// AcceptHandshake accepts the client handshake request and sends
|
|
||||||
// handshake response back to client.
|
|
||||||
AcceptHandshake(buf *bufio.Writer) (err error) |
|
||||||
|
|
||||||
// NewServerConn creates a new WebSocket connection.
|
|
||||||
NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) (conn *Conn) |
|
||||||
} |
|
||||||
|
|
||||||
// frameReader is an interface to read a WebSocket frame.
|
|
||||||
type frameReader interface { |
|
||||||
// Reader is to read payload of the frame.
|
|
||||||
io.Reader |
|
||||||
|
|
||||||
// PayloadType returns payload type.
|
|
||||||
PayloadType() byte |
|
||||||
|
|
||||||
// HeaderReader returns a reader to read header of the frame.
|
|
||||||
HeaderReader() io.Reader |
|
||||||
|
|
||||||
// TrailerReader returns a reader to read trailer of the frame.
|
|
||||||
// If it returns nil, there is no trailer in the frame.
|
|
||||||
TrailerReader() io.Reader |
|
||||||
|
|
||||||
// Len returns total length of the frame, including header and trailer.
|
|
||||||
Len() int |
|
||||||
} |
|
||||||
|
|
||||||
// frameReaderFactory is an interface to creates new frame reader.
|
|
||||||
type frameReaderFactory interface { |
|
||||||
NewFrameReader() (r frameReader, err error) |
|
||||||
} |
|
||||||
|
|
||||||
// frameWriter is an interface to write a WebSocket frame.
|
|
||||||
type frameWriter interface { |
|
||||||
// Writer is to write payload of the frame.
|
|
||||||
io.WriteCloser |
|
||||||
} |
|
||||||
|
|
||||||
// frameWriterFactory is an interface to create new frame writer.
|
|
||||||
type frameWriterFactory interface { |
|
||||||
NewFrameWriter(payloadType byte) (w frameWriter, err error) |
|
||||||
} |
|
||||||
|
|
||||||
type frameHandler interface { |
|
||||||
HandleFrame(frame frameReader) (r frameReader, err error) |
|
||||||
WriteClose(status int) (err error) |
|
||||||
} |
|
||||||
|
|
||||||
// Conn represents a WebSocket connection.
|
|
||||||
type Conn struct { |
|
||||||
config *Config |
|
||||||
request *http.Request |
|
||||||
|
|
||||||
buf *bufio.ReadWriter |
|
||||||
rwc io.ReadWriteCloser |
|
||||||
|
|
||||||
rio sync.Mutex |
|
||||||
frameReaderFactory |
|
||||||
frameReader |
|
||||||
|
|
||||||
wio sync.Mutex |
|
||||||
frameWriterFactory |
|
||||||
|
|
||||||
frameHandler |
|
||||||
PayloadType byte |
|
||||||
defaultCloseStatus int |
|
||||||
} |
|
||||||
|
|
||||||
// Read implements the io.Reader interface:
|
|
||||||
// it reads data of a frame from the WebSocket connection.
|
|
||||||
// if msg is not large enough for the frame data, it fills the msg and next Read
|
|
||||||
// will read the rest of the frame data.
|
|
||||||
// it reads Text frame or Binary frame.
|
|
||||||
func (ws *Conn) Read(msg []byte) (n int, err error) { |
|
||||||
ws.rio.Lock() |
|
||||||
defer ws.rio.Unlock() |
|
||||||
again: |
|
||||||
if ws.frameReader == nil { |
|
||||||
frame, err := ws.frameReaderFactory.NewFrameReader() |
|
||||||
if err != nil { |
|
||||||
return 0, err |
|
||||||
} |
|
||||||
ws.frameReader, err = ws.frameHandler.HandleFrame(frame) |
|
||||||
if err != nil { |
|
||||||
return 0, err |
|
||||||
} |
|
||||||
if ws.frameReader == nil { |
|
||||||
goto again |
|
||||||
} |
|
||||||
} |
|
||||||
n, err = ws.frameReader.Read(msg) |
|
||||||
if err == io.EOF { |
|
||||||
if trailer := ws.frameReader.TrailerReader(); trailer != nil { |
|
||||||
io.Copy(ioutil.Discard, trailer) |
|
||||||
} |
|
||||||
ws.frameReader = nil |
|
||||||
goto again |
|
||||||
} |
|
||||||
return n, err |
|
||||||
} |
|
||||||
|
|
||||||
// Write implements the io.Writer interface:
|
|
||||||
// it writes data as a frame to the WebSocket connection.
|
|
||||||
func (ws *Conn) Write(msg []byte) (n int, err error) { |
|
||||||
ws.wio.Lock() |
|
||||||
defer ws.wio.Unlock() |
|
||||||
w, err := ws.frameWriterFactory.NewFrameWriter(ws.PayloadType) |
|
||||||
if err != nil { |
|
||||||
return 0, err |
|
||||||
} |
|
||||||
n, err = w.Write(msg) |
|
||||||
w.Close() |
|
||||||
if err != nil { |
|
||||||
return n, err |
|
||||||
} |
|
||||||
return n, err |
|
||||||
} |
|
||||||
|
|
||||||
// Close implements the io.Closer interface.
|
|
||||||
func (ws *Conn) Close() error { |
|
||||||
err := ws.frameHandler.WriteClose(ws.defaultCloseStatus) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return ws.rwc.Close() |
|
||||||
} |
|
||||||
|
|
||||||
func (ws *Conn) IsClientConn() bool { return ws.request == nil } |
|
||||||
func (ws *Conn) IsServerConn() bool { return ws.request != nil } |
|
||||||
|
|
||||||
// LocalAddr returns the WebSocket Origin for the connection for client, or
|
|
||||||
// the WebSocket location for server.
|
|
||||||
func (ws *Conn) LocalAddr() net.Addr { |
|
||||||
if ws.IsClientConn() { |
|
||||||
return &Addr{ws.config.Origin} |
|
||||||
} |
|
||||||
return &Addr{ws.config.Location} |
|
||||||
} |
|
||||||
|
|
||||||
// RemoteAddr returns the WebSocket location for the connection for client, or
|
|
||||||
// the Websocket Origin for server.
|
|
||||||
func (ws *Conn) RemoteAddr() net.Addr { |
|
||||||
if ws.IsClientConn() { |
|
||||||
return &Addr{ws.config.Location} |
|
||||||
} |
|
||||||
return &Addr{ws.config.Origin} |
|
||||||
} |
|
||||||
|
|
||||||
var errSetDeadline = errors.New("websocket: cannot set deadline: not using a net.Conn") |
|
||||||
|
|
||||||
// SetDeadline sets the connection's network read & write deadlines.
|
|
||||||
func (ws *Conn) SetDeadline(t time.Time) error { |
|
||||||
if conn, ok := ws.rwc.(net.Conn); ok { |
|
||||||
return conn.SetDeadline(t) |
|
||||||
} |
|
||||||
return errSetDeadline |
|
||||||
} |
|
||||||
|
|
||||||
// SetReadDeadline sets the connection's network read deadline.
|
|
||||||
func (ws *Conn) SetReadDeadline(t time.Time) error { |
|
||||||
if conn, ok := ws.rwc.(net.Conn); ok { |
|
||||||
return conn.SetReadDeadline(t) |
|
||||||
} |
|
||||||
return errSetDeadline |
|
||||||
} |
|
||||||
|
|
||||||
// SetWriteDeadline sets the connection's network write deadline.
|
|
||||||
func (ws *Conn) SetWriteDeadline(t time.Time) error { |
|
||||||
if conn, ok := ws.rwc.(net.Conn); ok { |
|
||||||
return conn.SetWriteDeadline(t) |
|
||||||
} |
|
||||||
return errSetDeadline |
|
||||||
} |
|
||||||
|
|
||||||
// Config returns the WebSocket config.
|
|
||||||
func (ws *Conn) Config() *Config { return ws.config } |
|
||||||
|
|
||||||
// Request returns the http request upgraded to the WebSocket.
|
|
||||||
// It is nil for client side.
|
|
||||||
func (ws *Conn) Request() *http.Request { return ws.request } |
|
||||||
|
|
||||||
// Codec represents a symmetric pair of functions that implement a codec.
|
|
||||||
type Codec struct { |
|
||||||
Marshal func(v interface{}) (data []byte, payloadType byte, err error) |
|
||||||
Unmarshal func(data []byte, payloadType byte, v interface{}) (err error) |
|
||||||
} |
|
||||||
|
|
||||||
// Send sends v marshaled by cd.Marshal as single frame to ws.
|
|
||||||
func (cd Codec) Send(ws *Conn, v interface{}) (err error) { |
|
||||||
data, payloadType, err := cd.Marshal(v) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
ws.wio.Lock() |
|
||||||
defer ws.wio.Unlock() |
|
||||||
w, err := ws.frameWriterFactory.NewFrameWriter(payloadType) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
_, err = w.Write(data) |
|
||||||
w.Close() |
|
||||||
return err |
|
||||||
} |
|
||||||
|
|
||||||
// Receive receives single frame from ws, unmarshaled by cd.Unmarshal and stores in v.
|
|
||||||
func (cd Codec) Receive(ws *Conn, v interface{}) (err error) { |
|
||||||
ws.rio.Lock() |
|
||||||
defer ws.rio.Unlock() |
|
||||||
if ws.frameReader != nil { |
|
||||||
_, err = io.Copy(ioutil.Discard, ws.frameReader) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
ws.frameReader = nil |
|
||||||
} |
|
||||||
again: |
|
||||||
frame, err := ws.frameReaderFactory.NewFrameReader() |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
frame, err = ws.frameHandler.HandleFrame(frame) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
if frame == nil { |
|
||||||
goto again |
|
||||||
} |
|
||||||
payloadType := frame.PayloadType() |
|
||||||
data, err := ioutil.ReadAll(frame) |
|
||||||
if err != nil { |
|
||||||
return err |
|
||||||
} |
|
||||||
return cd.Unmarshal(data, payloadType, v) |
|
||||||
} |
|
||||||
|
|
||||||
func marshal(v interface{}) (msg []byte, payloadType byte, err error) { |
|
||||||
switch data := v.(type) { |
|
||||||
case string: |
|
||||||
return []byte(data), TextFrame, nil |
|
||||||
case []byte: |
|
||||||
return data, BinaryFrame, nil |
|
||||||
} |
|
||||||
return nil, UnknownFrame, ErrNotSupported |
|
||||||
} |
|
||||||
|
|
||||||
func unmarshal(msg []byte, payloadType byte, v interface{}) (err error) { |
|
||||||
switch data := v.(type) { |
|
||||||
case *string: |
|
||||||
*data = string(msg) |
|
||||||
return nil |
|
||||||
case *[]byte: |
|
||||||
*data = msg |
|
||||||
return nil |
|
||||||
} |
|
||||||
return ErrNotSupported |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
Message is a codec to send/receive text/binary data in a frame on WebSocket connection. |
|
||||||
To send/receive text frame, use string type. |
|
||||||
To send/receive binary frame, use []byte type. |
|
||||||
|
|
||||||
Trivial usage: |
|
||||||
|
|
||||||
import "websocket" |
|
||||||
|
|
||||||
// receive text frame
|
|
||||||
var message string |
|
||||||
websocket.Message.Receive(ws, &message) |
|
||||||
|
|
||||||
// send text frame
|
|
||||||
message = "hello" |
|
||||||
websocket.Message.Send(ws, message) |
|
||||||
|
|
||||||
// receive binary frame
|
|
||||||
var data []byte |
|
||||||
websocket.Message.Receive(ws, &data) |
|
||||||
|
|
||||||
// send binary frame
|
|
||||||
data = []byte{0, 1, 2} |
|
||||||
websocket.Message.Send(ws, data) |
|
||||||
|
|
||||||
*/ |
|
||||||
var Message = Codec{marshal, unmarshal} |
|
||||||
|
|
||||||
func jsonMarshal(v interface{}) (msg []byte, payloadType byte, err error) { |
|
||||||
msg, err = json.Marshal(v) |
|
||||||
return msg, TextFrame, err |
|
||||||
} |
|
||||||
|
|
||||||
func jsonUnmarshal(msg []byte, payloadType byte, v interface{}) (err error) { |
|
||||||
return json.Unmarshal(msg, v) |
|
||||||
} |
|
||||||
|
|
||||||
/* |
|
||||||
JSON is a codec to send/receive JSON data in a frame from a WebSocket connection. |
|
||||||
|
|
||||||
Trivial usage: |
|
||||||
|
|
||||||
import "websocket" |
|
||||||
|
|
||||||
type T struct { |
|
||||||
Msg string |
|
||||||
Count int |
|
||||||
} |
|
||||||
|
|
||||||
// receive JSON type T
|
|
||||||
var data T |
|
||||||
websocket.JSON.Receive(ws, &data) |
|
||||||
|
|
||||||
// send JSON type T
|
|
||||||
websocket.JSON.Send(ws, data) |
|
||||||
*/ |
|
||||||
var JSON = Codec{jsonMarshal, jsonUnmarshal} |
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue