1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-10-18 07:34:22 +00:00

Implement MFA verification for logins (#10)

This commit is contained in:
Knut Ahlers 2018-12-24 10:07:49 +01:00 committed by GitHub
parent 3bf7477e98
commit f6d622d1b8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
93 changed files with 8739 additions and 44 deletions

25
Gopkg.lock generated
View file

@ -25,6 +25,18 @@
revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e" revision = "7aef1d393c1e2d0758901853b59981c7adc67c7e"
version = "v1.2.0" version = "v1.2.0"
[[projects]]
digest = "1:b529f4bf748979caa18b599d40d13e8b6e591a74b340f315ce4f95e119c288c2"
name = "github.com/boombuler/barcode"
packages = [
".",
"qr",
"utils",
]
pruneopts = ""
revision = "3cfea5ab600ae37946be2b763b8ec2c1cf2d272d"
version = "v1.0.0"
[[projects]] [[projects]]
branch = "master" branch = "master"
digest = "1:b7eb152b61f41f0c9bcb6cf1187e1470a91d5e01d1e08f8b7bfd722578851720" digest = "1:b7eb152b61f41f0c9bcb6cf1187e1470a91d5e01d1e08f8b7bfd722578851720"
@ -81,6 +93,18 @@
revision = "645ef00459ed84a119197bfb8d8205042c6df63d" revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0" version = "v0.8.0"
[[projects]]
digest = "1:4aea8409c0c3472587636033ab1fa10a2cec1d716449c55d06de4ad96dec0ec1"
name = "github.com/pquerna/otp"
packages = [
".",
"hotp",
"totp",
]
pruneopts = ""
revision = "b7b89250c468c06871d3837bee02e2d5c155ae19"
version = "v1.0.0"
[[projects]] [[projects]]
digest = "1:42a42c4bc67bed17f40fddf0f24d4403e25e7b96488456cf4248e6d16659d370" digest = "1:42a42c4bc67bed17f40fddf0f24d4403e25e7b96488456cf4248e6d16659d370"
name = "github.com/sirupsen/logrus" name = "github.com/sirupsen/logrus"
@ -164,6 +188,7 @@
"github.com/gorilla/sessions", "github.com/gorilla/sessions",
"github.com/jda/go-crowd", "github.com/jda/go-crowd",
"github.com/pkg/errors", "github.com/pkg/errors",
"github.com/pquerna/otp/totp",
"github.com/sirupsen/logrus", "github.com/sirupsen/logrus",
"golang.org/x/crypto/bcrypt", "golang.org/x/crypto/bcrypt",
"gopkg.in/ldap.v2", "gopkg.in/ldap.v2",

View file

@ -92,6 +92,7 @@ The login form can be customized with its wording and the default login method.
login: login:
title: "luzifer.io - Login" title: "luzifer.io - Login"
default_method: "simple" default_method: "simple"
hide_mfa_field: false
names: names:
simple: "Username / Password" simple: "Username / Password"
yubikey: "Yubikey" yubikey: "Yubikey"
@ -99,6 +100,8 @@ login:
Most options should explain themselves, the `names` dictionary maps IDs of the authentication methods (shown in the title of their config section below) to human readable strings. You can set any string you need and your user recognizes. Most options should explain themselves, the `names` dictionary maps IDs of the authentication methods (shown in the title of their config section below) to human readable strings. You can set any string you need and your user recognizes.
In case you don't want to show up the "MFA Token" fields even though the providers being used does support them (for example if you are not using MFA and don't want to confuse your users) you can set the `hide_mfa_field` flag to hide them.
### Main configuration: Cookie Settings ### Main configuration: Cookie Settings
Most of the cookie settings are pre-set to sane defaults but you definitly need to configure some. Most of the cookie settings are pre-set to sane defaults but you definitly need to configure some.
@ -175,6 +178,50 @@ Each `rules` entry has two mandantory and three optional fields of which at leas
The `allow` and `deny` directives are arrays of users and groups. Groups are prefixed using an `@` sign. There is a simple logic: Users before groups, denies before allows. So if you allow the group `@test` containing the user `mike` but deny the user `mike`, mike will not be able to access the matching sites. The `allow` and `deny` directives are arrays of users and groups. Groups are prefixed using an `@` sign. There is a simple logic: Users before groups, denies before allows. So if you allow the group `@test` containing the user `mike` but deny the user `mike`, mike will not be able to access the matching sites.
### MFA Configuration
Each provider supporting MFA does have some kind of configuration for the MFA providers. As there are multiple MFA providers the configuration sadly isn't that simple and needs to have the following format:
```yaml
provider: <provider name>
attributes:
<mapping of attributes>
```
#### Google Authenticator
The provider name here is `google` while the only supported argument at the moment is `secret`. The secret is what you need to provide to your users for them to add the config to their authenticator. (It MUST be base32 encoded!)
Here is an example of the URI to provide in a QRCode:
```yaml
provider: google
attributes:
secret: MZXW6YTBOIFA
```
`otpauth://totp/Example:myusername?secret=myverysecretsecret` ([Docs](https://github.com/google/google-authenticator/wiki/Key-Uri-Format))
#### Yubikey
This provider needs a configuration to function correctly:
```yaml
mfa:
yubikey:
# Get your client / secret from https://upgrade.yubico.com/getapikey/
client_id: "12345"
secret_key: "foobar"
```
The corresponding expected MFA configuration is as following:
```yaml
provider: yubikey
attributes:
device: ccccccfcvuul
```
### Provider configuration: Atlassian Crowd (`crowd`) ### Provider configuration: Atlassian Crowd (`crowd`)
The crowd auth provider connects nginx-sso with an Atlassian Crowd directory server. The SSO authentication cookie used by Jira and Confluence is also used by nginx-sso which means a login in Jira will also perform a login on nginx-sso and vice versa. The crowd auth provider connects nginx-sso with an Atlassian Crowd directory server. The SSO authentication cookie used by Jira and Confluence is also used by nginx-sso which means a login in Jira will also perform a login on nginx-sso and vice versa.
@ -270,12 +317,21 @@ providers:
groups: groups:
admins: ["luzifer"] admins: ["luzifer"]
users: ["mike"] users: ["mike"]
# MFA configs: Username to configs mapping
mfa:
luzifer:
- provider: google
attributes:
secret: asdgsdfhgshf
``` ```
You can see how to configure the provider the example above: No surprises, just ensure you are using bcrypt hashes for the passwords, no other hash functions are supported. You can see how to configure the provider the example above: No surprises, just ensure you are using bcrypt hashes for the passwords, no other hash functions are supported.
If `enable_basic_auth` is set to `true` the credentials can also be submitted through basic auth. This is useful for services whose clients does not support other types of authentication. If `enable_basic_auth` is set to `true` the credentials can also be submitted through basic auth. This is useful for services whose clients does not support other types of authentication.
When there is at least one MFA configuration provided for the user inside the `mfa` block the user will be forced to enter a MFA token during login or otherwise the login will fail.
### Provider configuration: Token Auth (`token`) ### Provider configuration: Token Auth (`token`)
The token auth provider is intended to give machines access to endpoints. Users will not be able to "login" using tokens when they see the login form. The token auth provider is intended to give machines access to endpoints. Users will not be able to "login" using tokens when they see the login form.

View file

@ -28,7 +28,7 @@ func (a authCrowd) AuthenticatorID() string { return "crowd" }
// Configure loads the configuration for the Authenticator from the // Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice. // global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function // If no configuration for the Authenticator is supplied the function
// needs to return the errAuthenticatorUnconfigured // needs to return the errProviderUnconfigured
func (a *authCrowd) Configure(yamlSource []byte) error { func (a *authCrowd) Configure(yamlSource []byte) error {
envelope := struct { envelope := struct {
Providers struct { Providers struct {
@ -41,7 +41,7 @@ func (a *authCrowd) Configure(yamlSource []byte) error {
} }
if envelope.Providers.Crowd == nil { if envelope.Providers.Crowd == nil {
return errAuthenticatorUnconfigured return errProviderUnconfigured
} }
a.URL = envelope.Providers.Crowd.URL a.URL = envelope.Providers.Crowd.URL
@ -49,7 +49,7 @@ func (a *authCrowd) Configure(yamlSource []byte) error {
a.AppPassword = envelope.Providers.Crowd.AppPassword a.AppPassword = envelope.Providers.Crowd.AppPassword
if a.AppName == "" || a.AppPassword == "" { if a.AppName == "" || a.AppPassword == "" {
return errAuthenticatorUnconfigured return errProviderUnconfigured
} }
var err error var err error
@ -106,13 +106,13 @@ func (a authCrowd) DetectUser(res http.ResponseWriter, r *http.Request) (string,
// in order to use DetectUser for the next login. // in order to use DetectUser for the next login.
// If the user did not login correctly the errNoValidUserFound // If the user did not login correctly the errNoValidUserFound
// needs to be returned // needs to be returned
func (a authCrowd) Login(res http.ResponseWriter, r *http.Request) error { func (a authCrowd) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-")) username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-"))
password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-")) password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-"))
cc, err := a.crowd.GetCookieConfig() cc, err := a.crowd.GetCookieConfig()
if err != nil { if err != nil {
return err return "", nil, err
} }
sess, err := a.crowd.NewSession(username, password, r.RemoteAddr) sess, err := a.crowd.NewSession(username, password, r.RemoteAddr)
@ -120,7 +120,7 @@ func (a authCrowd) Login(res http.ResponseWriter, r *http.Request) error {
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"username": username, "username": username,
}).WithError(err).Debug("Crowd authentication failed") }).WithError(err).Debug("Crowd authentication failed")
return errNoValidUserFound return "", nil, errNoValidUserFound
} }
http.SetCookie(res, &http.Cookie{ http.SetCookie(res, &http.Cookie{
@ -133,7 +133,7 @@ func (a authCrowd) Login(res http.ResponseWriter, r *http.Request) error {
HttpOnly: true, HttpOnly: true,
}) })
return nil return username, nil, nil
} }
// LoginFields needs to return the fields required for this login // LoginFields needs to return the fields required for this login
@ -176,3 +176,10 @@ func (a authCrowd) Logout(res http.ResponseWriter, r *http.Request) (err error)
return nil return nil
} }
// SupportsMFA returns the MFA detection capabilities of the login
// provider. If the provider can provide mfaConfig objects from its
// configuration return true. If this is true the login interface
// will display an additional field for this provider for the user
// to fill in their MFA token.
func (a authCrowd) SupportsMFA() bool { return false }

View file

@ -39,7 +39,7 @@ func (a authLDAP) AuthenticatorID() string { return "ldap" }
// Configure loads the configuration for the Authenticator from the // Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice. // global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function // If no configuration for the Authenticator is supplied the function
// needs to return the errAuthenticatorUnconfigured // needs to return the errProviderUnconfigured
func (a *authLDAP) Configure(yamlSource []byte) error { func (a *authLDAP) Configure(yamlSource []byte) error {
envelope := struct { envelope := struct {
Providers struct { Providers struct {
@ -52,7 +52,7 @@ func (a *authLDAP) Configure(yamlSource []byte) error {
} }
if envelope.Providers.LDAP == nil { if envelope.Providers.LDAP == nil {
return errAuthenticatorUnconfigured return errProviderUnconfigured
} }
a.EnableBasicAuth = envelope.Providers.LDAP.EnableBasicAuth a.EnableBasicAuth = envelope.Providers.LDAP.EnableBasicAuth
@ -141,7 +141,7 @@ func (a authLDAP) DetectUser(res http.ResponseWriter, r *http.Request) (string,
// in order to use DetectUser for the next login. // in order to use DetectUser for the next login.
// If the user did not login correctly the errNoValidUserFound // If the user did not login correctly the errNoValidUserFound
// needs to be returned // needs to be returned
func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) error { func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-")) username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-"))
password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-")) password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-"))
@ -152,14 +152,14 @@ func (a authLDAP) Login(res http.ResponseWriter, r *http.Request) error {
) )
if userDN, alias, err = a.checkLogin(username, password, a.UsernameAttribute); err != nil { if userDN, alias, err = a.checkLogin(username, password, a.UsernameAttribute); err != nil {
return err return "", nil, err
} }
sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-")) sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-"))
sess.Options = mainCfg.GetSessionOpts() sess.Options = mainCfg.GetSessionOpts()
sess.Values["user"] = userDN sess.Values["user"] = userDN
sess.Values["alias"] = alias sess.Values["alias"] = alias
return sess.Save(r, res) return userDN, nil, sess.Save(r, res)
} }
// LoginFields needs to return the fields required for this login // LoginFields needs to return the fields required for this login
@ -328,3 +328,10 @@ func (a authLDAP) getUserGroups(userDN, alias string) ([]string, error) {
return groups, nil return groups, nil
} }
// SupportsMFA returns the MFA detection capabilities of the login
// provider. If the provider can provide mfaConfig objects from its
// configuration return true. If this is true the login interface
// will display an additional field for this provider for the user
// to fill in their MFA token.
func (a authLDAP) SupportsMFA() bool { return false } // TODO: Implement

View file

@ -4,9 +4,10 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/Luzifer/go_helpers/str"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
yaml "gopkg.in/yaml.v2" yaml "gopkg.in/yaml.v2"
"github.com/Luzifer/go_helpers/str"
) )
func init() { func init() {
@ -14,9 +15,10 @@ func init() {
} }
type authSimple struct { type authSimple struct {
EnableBasicAuth bool `yaml:"enable_basic_auth"` EnableBasicAuth bool `yaml:"enable_basic_auth"`
Users map[string]string `yaml:"users"` Users map[string]string `yaml:"users"`
Groups map[string][]string `yaml:"groups"` Groups map[string][]string `yaml:"groups"`
MFA map[string][]mfaConfig `yaml:"mfa"`
} }
// AuthenticatorID needs to return an unique string to identify // AuthenticatorID needs to return an unique string to identify
@ -26,7 +28,7 @@ func (a authSimple) AuthenticatorID() string { return "simple" }
// Configure loads the configuration for the Authenticator from the // Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice. // global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function // If no configuration for the Authenticator is supplied the function
// needs to return the errAuthenticatorUnconfigured // needs to return the errProviderUnconfigured
func (a *authSimple) Configure(yamlSource []byte) error { func (a *authSimple) Configure(yamlSource []byte) error {
envelope := struct { envelope := struct {
Providers struct { Providers struct {
@ -39,12 +41,13 @@ func (a *authSimple) Configure(yamlSource []byte) error {
} }
if envelope.Providers.Simple == nil { if envelope.Providers.Simple == nil {
return errAuthenticatorUnconfigured return errProviderUnconfigured
} }
a.EnableBasicAuth = envelope.Providers.Simple.EnableBasicAuth a.EnableBasicAuth = envelope.Providers.Simple.EnableBasicAuth
a.Users = envelope.Providers.Simple.Users a.Users = envelope.Providers.Simple.Users
a.Groups = envelope.Providers.Simple.Groups a.Groups = envelope.Providers.Simple.Groups
a.MFA = envelope.Providers.Simple.MFA
return nil return nil
} }
@ -106,7 +109,7 @@ func (a authSimple) DetectUser(res http.ResponseWriter, r *http.Request) (string
// in order to use DetectUser for the next login. // in order to use DetectUser for the next login.
// If the user did not login correctly the errNoValidUserFound // If the user did not login correctly the errNoValidUserFound
// needs to be returned // needs to be returned
func (a authSimple) Login(res http.ResponseWriter, r *http.Request) error { func (a authSimple) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-")) username := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "username"}, "-"))
password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-")) password := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "password"}, "-"))
@ -121,10 +124,10 @@ func (a authSimple) Login(res http.ResponseWriter, r *http.Request) error {
sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-")) sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-"))
sess.Options = mainCfg.GetSessionOpts() sess.Options = mainCfg.GetSessionOpts()
sess.Values["user"] = u sess.Values["user"] = u
return sess.Save(r, res) return u, a.MFA[u], sess.Save(r, res)
} }
return errNoValidUserFound return "", nil, errNoValidUserFound
} }
// LoginFields needs to return the fields required for this login // LoginFields needs to return the fields required for this login
@ -155,3 +158,10 @@ func (a authSimple) Logout(res http.ResponseWriter, r *http.Request) (err error)
sess.Options.MaxAge = -1 // Instant delete sess.Options.MaxAge = -1 // Instant delete
return sess.Save(r, res) return sess.Save(r, res)
} }
// SupportsMFA returns the MFA detection capabilities of the login
// provider. If the provider can provide mfaConfig objects from its
// configuration return true. If this is true the login interface
// will display an additional field for this provider for the user
// to fill in their MFA token.
func (a authSimple) SupportsMFA() bool { return true }

View file

@ -25,7 +25,7 @@ func (a authToken) AuthenticatorID() string { return "token" }
// Configure loads the configuration for the Authenticator from the // Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice. // global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function // If no configuration for the Authenticator is supplied the function
// needs to return the errAuthenticatorUnconfigured // needs to return the errProviderUnconfigured
func (a *authToken) Configure(yamlSource []byte) error { func (a *authToken) Configure(yamlSource []byte) error {
envelope := struct { envelope := struct {
Providers struct { Providers struct {
@ -38,7 +38,7 @@ func (a *authToken) Configure(yamlSource []byte) error {
} }
if envelope.Providers.Token == nil { if envelope.Providers.Token == nil {
return errAuthenticatorUnconfigured return errProviderUnconfigured
} }
a.Tokens = envelope.Providers.Token.Tokens a.Tokens = envelope.Providers.Token.Tokens
@ -92,7 +92,9 @@ func (a authToken) DetectUser(res http.ResponseWriter, r *http.Request) (string,
// in order to use DetectUser for the next login. // in order to use DetectUser for the next login.
// If the user did not login correctly the errNoValidUserFound // If the user did not login correctly the errNoValidUserFound
// needs to be returned // needs to be returned
func (a authToken) Login(res http.ResponseWriter, r *http.Request) error { return errNoValidUserFound } func (a authToken) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
return "", nil, errNoValidUserFound
}
// LoginFields needs to return the fields required for this login // LoginFields needs to return the fields required for this login
// method. If no login using this method is possible the function // method. If no login using this method is possible the function
@ -102,3 +104,10 @@ func (a authToken) LoginFields() []loginField { return nil }
// Logout is called when the user visits the logout endpoint and // Logout is called when the user visits the logout endpoint and
// needs to destroy any persistent stored cookies // needs to destroy any persistent stored cookies
func (a authToken) Logout(res http.ResponseWriter, r *http.Request) error { return nil } func (a authToken) Logout(res http.ResponseWriter, r *http.Request) error { return nil }
// SupportsMFA returns the MFA detection capabilities of the login
// provider. If the provider can provide mfaConfig objects from its
// configuration return true. If this is true the login interface
// will display an additional field for this provider for the user
// to fill in their MFA token.
func (a authToken) SupportsMFA() bool { return false }

View file

@ -28,7 +28,7 @@ func (a authYubikey) AuthenticatorID() string { return "yubikey" }
// Configure loads the configuration for the Authenticator from the // Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice. // global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function // If no configuration for the Authenticator is supplied the function
// needs to return the errAuthenticatorUnconfigured // needs to return the errProviderUnconfigured
func (a *authYubikey) Configure(yamlSource []byte) error { func (a *authYubikey) Configure(yamlSource []byte) error {
envelope := struct { envelope := struct {
Providers struct { Providers struct {
@ -41,7 +41,7 @@ func (a *authYubikey) Configure(yamlSource []byte) error {
} }
if envelope.Providers.Yubikey == nil { if envelope.Providers.Yubikey == nil {
return errAuthenticatorUnconfigured return errProviderUnconfigured
} }
a.ClientID = envelope.Providers.Yubikey.ClientID a.ClientID = envelope.Providers.Yubikey.ClientID
@ -89,34 +89,34 @@ func (a authYubikey) DetectUser(res http.ResponseWriter, r *http.Request) (strin
// in order to use DetectUser for the next login. // in order to use DetectUser for the next login.
// If the user did not login correctly the errNoValidUserFound // If the user did not login correctly the errNoValidUserFound
// needs to be returned // needs to be returned
func (a authYubikey) Login(res http.ResponseWriter, r *http.Request) error { func (a authYubikey) Login(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
keyInput := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "key-input"}, "-")) keyInput := r.FormValue(strings.Join([]string{a.AuthenticatorID(), "key-input"}, "-"))
yubiAuth, err := yubigo.NewYubiAuth(a.ClientID, a.SecretKey) yubiAuth, err := yubigo.NewYubiAuth(a.ClientID, a.SecretKey)
if err != nil { if err != nil {
return err return "", nil, err
} }
_, ok, err := yubiAuth.Verify(keyInput) _, ok, err := yubiAuth.Verify(keyInput)
if err != nil && !strings.Contains(err.Error(), "OTP has wrong length.") { if err != nil && !strings.Contains(err.Error(), "OTP has wrong length.") {
return err return "", nil, err
} }
if !ok { if !ok {
// Not a valid authentication // Not a valid authentication
return errNoValidUserFound return "", nil, errNoValidUserFound
} }
user, ok := a.Devices[keyInput[:12]] user, ok := a.Devices[keyInput[:12]]
if !ok { if !ok {
// We do not have a definition for that key // We do not have a definition for that key
return errNoValidUserFound return "", nil, errNoValidUserFound
} }
sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-")) sess, _ := cookieStore.Get(r, strings.Join([]string{mainCfg.Cookie.Prefix, a.AuthenticatorID()}, "-"))
sess.Options = mainCfg.GetSessionOpts() sess.Options = mainCfg.GetSessionOpts()
sess.Values["user"] = user sess.Values["user"] = user
return sess.Save(r, res) return user, nil, sess.Save(r, res)
} }
// LoginFields needs to return the fields required for this login // LoginFields needs to return the fields required for this login
@ -141,3 +141,10 @@ func (a authYubikey) Logout(res http.ResponseWriter, r *http.Request) (err error
sess.Options.MaxAge = -1 // Instant delete sess.Options.MaxAge = -1 // Instant delete
return sess.Save(r, res) return sess.Save(r, res)
} }
// SupportsMFA returns the MFA detection capabilities of the login
// provider. If the provider can provide mfaConfig objects from its
// configuration return true. If this is true the login interface
// will display an additional field for this provider for the user
// to fill in their MFA token.
func (a authYubikey) SupportsMFA() bool { return false }

View file

@ -3,6 +3,7 @@
login: login:
title: "luzifer.io - Login" title: "luzifer.io - Login"
default_method: "simple" default_method: "simple"
hide_mfa_field: false
names: names:
simple: "Username / Password" simple: "Username / Password"
yubikey: "Yubikey" yubikey: "Yubikey"
@ -36,6 +37,12 @@ acl:
regexp: "^/api" regexp: "^/api"
allow: ["luzifer", "@admins"] allow: ["luzifer", "@admins"]
mfa:
yubikey:
# Get your client / secret from https://upgrade.yubico.com/getapikey/
client_id: "12345"
secret_key: "foobar"
providers: providers:
# Authentication against an Atlassian Crowd directory server # Authentication against an Atlassian Crowd directory server
# Supports: Users, Groups # Supports: Users, Groups
@ -74,7 +81,7 @@ providers:
allow_insecure: false allow_insecure: false
# Authentication against embedded user database # Authentication against embedded user database
# Supports: Users, Groups # Supports: Users, Groups, MFA
simple: simple:
enable_basic_auth: false enable_basic_auth: false
@ -86,6 +93,16 @@ providers:
groups: groups:
admins: ["luzifer"] admins: ["luzifer"]
# MFA configs: Username to configs mapping
mfa:
luzifer:
- provider: google
attributes:
secret: MZXW6YTBOIFA
- provider: yubikey
attributes:
device: ccccccfcvuul
# Authentication against embedded token directory # Authentication against embedded token directory
# Supports: Users, Groups # Supports: Users, Groups
token: token:

24
main.go
View file

@ -36,6 +36,7 @@ type mainConfig struct {
Login struct { Login struct {
Title string `yaml:"title"` Title string `yaml:"title"`
DefaultMethod string `yaml:"default_method"` DefaultMethod string `yaml:"default_method"`
HideMFAField bool `yaml:"hide_mfa_field"`
Names map[string]string `yaml:"names"` Names map[string]string `yaml:"names"`
} `yaml:"login"` } `yaml:"login"`
} }
@ -103,6 +104,10 @@ func loadConfiguration() error {
return fmt.Errorf("Unable to configure authentication: %s", err) return fmt.Errorf("Unable to configure authentication: %s", err)
} }
if err = initializeMFAProviders(yamlSource); err != nil {
log.WithError(err).Fatal("Unable to configure MFA providers")
}
return nil return nil
} }
@ -176,11 +181,27 @@ func handleLoginRequest(res http.ResponseWriter, r *http.Request) {
} }
if r.Method == "POST" { if r.Method == "POST" {
err := loginUser(res, r) // Simple authentication
user, mfaCfgs, err := loginUser(res, r)
switch err {
case errNoValidUserFound:
http.Redirect(res, r, "/login?go="+url.QueryEscape(r.FormValue("go")), http.StatusFound)
return
case nil:
// Don't handle for now, MFA validation comes first
default:
log.WithError(err).Error("Login failed with unexpected error")
http.Redirect(res, r, "/login?go="+url.QueryEscape(r.FormValue("go")), http.StatusFound)
return
}
// MFA validation against configs from login
err = validateMFA(res, r, user, mfaCfgs)
switch err { switch err {
case errNoValidUserFound: case errNoValidUserFound:
auditFields["reason"] = "invalid credentials" auditFields["reason"] = "invalid credentials"
mainCfg.AuditLog.Log(auditEventLoginFailure, r, auditFields) mainCfg.AuditLog.Log(auditEventLoginFailure, r, auditFields)
res.Header().Del("Set-Cookie") // Remove login cookie
http.Redirect(res, r, "/login?go="+url.QueryEscape(r.FormValue("go")), http.StatusFound) http.Redirect(res, r, "/login?go="+url.QueryEscape(r.FormValue("go")), http.StatusFound)
return return
@ -194,6 +215,7 @@ func handleLoginRequest(res http.ResponseWriter, r *http.Request) {
auditFields["error"] = err.Error() auditFields["error"] = err.Error()
mainCfg.AuditLog.Log(auditEventLoginFailure, r, auditFields) mainCfg.AuditLog.Log(auditEventLoginFailure, r, auditFields)
log.WithError(err).Error("Login failed with unexpected error") log.WithError(err).Error("Login failed with unexpected error")
res.Header().Del("Set-Cookie") // Remove login cookie
http.Redirect(res, r, "/login?go="+url.QueryEscape(r.FormValue("go")), http.StatusFound) http.Redirect(res, r, "/login?go="+url.QueryEscape(r.FormValue("go")), http.StatusFound)
return return
} }

115
mfa.go Normal file
View file

@ -0,0 +1,115 @@
package main
import (
"fmt"
"net/http"
"sync"
log "github.com/sirupsen/logrus"
)
const mfaLoginFieldName = "mfa-token"
var mfaLoginField = loginField{
Label: "MFA Token",
Name: mfaLoginFieldName,
Placeholder: "(optional)",
Type: "text",
}
type mfaConfig struct {
Provider string `yaml:"provider"`
Attributes map[string]interface{} `yaml:"attributes"`
}
func newMFAConfig(provider string, attrs map[string]interface{}) mfaConfig {
return mfaConfig{Provider: provider, Attributes: attrs}
}
func (m mfaConfig) AttributeString(key string) string {
if v, ok := m.Attributes[key]; ok {
if sv, ok := v.(string); ok {
return sv
}
}
return ""
}
type mfaProvider interface {
// ProviderID needs to return an unique string to identify
// this special MFA provider
ProviderID() (id string)
// Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function
// needs to return the errProviderUnconfigured
Configure(yamlSource []byte) (err error)
// ValidateMFA takes the user from the login cookie and performs a
// validation against the provided MFA configuration for this user
ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error
}
var (
mfaRegistry = []mfaProvider{}
mfaRegistryMutex sync.RWMutex
activeMFAProviders = []mfaProvider{}
)
func registerMFAProvider(m mfaProvider) {
mfaRegistryMutex.Lock()
defer mfaRegistryMutex.Unlock()
mfaRegistry = append(mfaRegistry, m)
}
func initializeMFAProviders(yamlSource []byte) error {
mfaRegistryMutex.Lock()
defer mfaRegistryMutex.Unlock()
for _, m := range mfaRegistry {
err := m.Configure(yamlSource)
switch err {
case nil:
activeMFAProviders = append(activeMFAProviders, m)
log.WithFields(log.Fields{"mfa_provider": m.ProviderID()}).Debug("Activated MFA provider")
case errProviderUnconfigured:
log.WithFields(log.Fields{"mfa_provider": m.ProviderID()}).Debug("MFA provider unconfigured")
// This is okay.
default:
return fmt.Errorf("MFA provider configuration caused an error: %s", err)
}
}
return nil
}
func validateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
if mfaCfgs == nil || len(mfaCfgs) == 0 {
// User has no configured MFA devices, their MFA is automatically valid
return nil
}
mfaRegistryMutex.RLock()
defer mfaRegistryMutex.RUnlock()
for _, m := range activeMFAProviders {
err := m.ValidateMFA(res, r, user, mfaCfgs)
switch err {
case nil:
// Validated successfully
return nil
case errNoValidUserFound:
// This is fine for now
default:
return err
}
}
// No method could verify the user
return errNoValidUserFound
}

63
mfa_google.go Normal file
View file

@ -0,0 +1,63 @@
package main
import (
"net/http"
"strings"
"time"
"github.com/pkg/errors"
"github.com/pquerna/otp/totp"
)
func init() {
registerMFAProvider(&mfaGoogle{})
}
type mfaGoogle struct{}
// ProviderID needs to return an unique string to identify
// this special MFA provider
func (m mfaGoogle) ProviderID() (id string) {
return "google"
}
// Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function
// needs to return the errProviderUnconfigured
func (m mfaGoogle) Configure(yamlSource []byte) (err error) { return nil }
// ValidateMFA takes the user from the login cookie and performs a
// validation against the provided MFA configuration for this user
func (m mfaGoogle) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
// Look for mfaConfigs with own provider name
for _, c := range mfaCfgs {
if c.Provider != m.ProviderID() {
continue
}
token, err := m.exec(c)
if err != nil {
return errors.Wrap(err, "Generating the MFA token failed")
}
for key, values := range r.Form {
if strings.HasSuffix(key, mfaLoginFieldName) && values[0] == token {
return nil
}
}
}
// Report this provider was not able to verify the MFA request
return errNoValidUserFound
}
func (m mfaGoogle) exec(c mfaConfig) (string, error) {
secret := c.AttributeString("secret")
if n := len(secret) % 8; n != 0 {
secret = secret + strings.Repeat("=", 8-n)
}
return totp.GenerateCode(strings.ToUpper(secret), time.Now())
}

87
mfa_yubikey.go Normal file
View file

@ -0,0 +1,87 @@
package main
import (
"net/http"
"strings"
"github.com/GeertJohan/yubigo"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
)
func init() {
registerMFAProvider(&mfaYubikey{})
}
type mfaYubikey struct {
ClientID string `yaml:"client_id"`
SecretKey string `yaml:"secret_key"`
}
// ProviderID needs to return an unique string to identify
// this special MFA provider
func (m mfaYubikey) ProviderID() (id string) { return "yubikey" }
// Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function
// needs to return the errProviderUnconfigured
func (m *mfaYubikey) Configure(yamlSource []byte) (err error) {
envelope := struct {
MFA struct {
Yubikey *mfaYubikey `yaml:"yubikey"`
} `yaml:"mfa"`
}{}
if err := yaml.Unmarshal(yamlSource, &envelope); err != nil {
return err
}
if envelope.MFA.Yubikey == nil {
return errProviderUnconfigured
}
m.ClientID = envelope.MFA.Yubikey.ClientID
m.SecretKey = envelope.MFA.Yubikey.SecretKey
return nil
}
// ValidateMFA takes the user from the login cookie and performs a
// validation against the provided MFA configuration for this user
func (m mfaYubikey) ValidateMFA(res http.ResponseWriter, r *http.Request, user string, mfaCfgs []mfaConfig) error {
var keyInput string
yubiAuth, err := yubigo.NewYubiAuth(m.ClientID, m.SecretKey)
if err != nil {
return errors.Wrap(err, "Unable to create Yubikey client")
}
for _, c := range mfaCfgs {
if c.Provider != m.ProviderID() {
continue
}
for key, values := range r.Form {
if strings.HasSuffix(key, mfaLoginFieldName) && strings.HasPrefix(values[0], c.AttributeString("device")) {
keyInput = values[0]
}
}
if keyInput == "" {
continue
}
_, ok, err := yubiAuth.Verify(keyInput)
if err != nil && !strings.Contains(err.Error(), "OTP has wrong length.") {
return errors.Wrap(err, "OTP verification failed")
}
if ok {
return nil
}
}
// Not a valid authentication
return errNoValidUserFound
}

View file

@ -17,7 +17,7 @@ type authenticator interface {
// Configure loads the configuration for the Authenticator from the // Configure loads the configuration for the Authenticator from the
// global config.yaml file which is passed as a byte-slice. // global config.yaml file which is passed as a byte-slice.
// If no configuration for the Authenticator is supplied the function // If no configuration for the Authenticator is supplied the function
// needs to return the errAuthenticatorUnconfigured // needs to return the errProviderUnconfigured
Configure(yamlSource []byte) (err error) Configure(yamlSource []byte) (err error)
// DetectUser is used to detect a user without a login form from // DetectUser is used to detect a user without a login form from
@ -30,9 +30,12 @@ type authenticator interface {
// to authenticate the user or throw an error. If the user has // to authenticate the user or throw an error. If the user has
// successfully logged in the persistent cookie should be written // successfully logged in the persistent cookie should be written
// in order to use DetectUser for the next login. // in order to use DetectUser for the next login.
// With the login result an array of mfaConfig must be returned. In
// case there is no MFA config or the provider does not support MFA
// return nil.
// If the user did not login correctly the errNoValidUserFound // If the user did not login correctly the errNoValidUserFound
// needs to be returned // needs to be returned
Login(res http.ResponseWriter, r *http.Request) (err error) Login(res http.ResponseWriter, r *http.Request) (user string, mfaConfigs []mfaConfig, err error)
// LoginFields needs to return the fields required for this login // LoginFields needs to return the fields required for this login
// method. If no login using this method is possible the function // method. If no login using this method is possible the function
@ -42,6 +45,13 @@ type authenticator interface {
// Logout is called when the user visits the logout endpoint and // Logout is called when the user visits the logout endpoint and
// needs to destroy any persistent stored cookies // needs to destroy any persistent stored cookies
Logout(res http.ResponseWriter, r *http.Request) (err error) Logout(res http.ResponseWriter, r *http.Request) (err error)
// SupportsMFA returns the MFA detection capabilities of the login
// provider. If the provider can provide mfaConfig objects from its
// configuration return true. If this is true the login interface
// will display an additional field for this provider for the user
// to fill in their MFA token.
SupportsMFA() bool
} }
type loginField struct { type loginField struct {
@ -52,8 +62,8 @@ type loginField struct {
} }
var ( var (
errAuthenticatorUnconfigured = errors.New("No valid configuration found for this authenticator") errProviderUnconfigured = errors.New("No valid configuration found for this provider")
errNoValidUserFound = errors.New("No valid users found") errNoValidUserFound = errors.New("No valid users found")
authenticatorRegistry = []authenticator{} authenticatorRegistry = []authenticator{}
authenticatorRegistryMutex sync.RWMutex authenticatorRegistryMutex sync.RWMutex
@ -80,7 +90,7 @@ func initializeAuthenticators(yamlSource []byte) error {
case nil: case nil:
tmp = append(tmp, a) tmp = append(tmp, a)
log.WithFields(log.Fields{"authenticator": a.AuthenticatorID()}).Debug("Activated authenticator") log.WithFields(log.Fields{"authenticator": a.AuthenticatorID()}).Debug("Activated authenticator")
case errAuthenticatorUnconfigured: case errProviderUnconfigured:
log.WithFields(log.Fields{"authenticator": a.AuthenticatorID()}).Debug("Authenticator unconfigured") log.WithFields(log.Fields{"authenticator": a.AuthenticatorID()}).Debug("Authenticator unconfigured")
// This is okay. // This is okay.
default: default:
@ -116,23 +126,23 @@ func detectUser(res http.ResponseWriter, r *http.Request) (string, []string, err
return "", nil, errNoValidUserFound return "", nil, errNoValidUserFound
} }
func loginUser(res http.ResponseWriter, r *http.Request) error { func loginUser(res http.ResponseWriter, r *http.Request) (string, []mfaConfig, error) {
authenticatorRegistryMutex.RLock() authenticatorRegistryMutex.RLock()
defer authenticatorRegistryMutex.RUnlock() defer authenticatorRegistryMutex.RUnlock()
for _, a := range activeAuthenticators { for _, a := range activeAuthenticators {
err := a.Login(res, r) user, mfaCfgs, err := a.Login(res, r)
switch err { switch err {
case nil: case nil:
return nil return user, mfaCfgs, nil
case errNoValidUserFound: case errNoValidUserFound:
// This is okay. // This is okay.
default: default:
return err return "", nil, err
} }
} }
return errNoValidUserFound return "", nil, errNoValidUserFound
} }
func logoutUser(res http.ResponseWriter, r *http.Request) error { func logoutUser(res http.ResponseWriter, r *http.Request) error {
@ -158,6 +168,10 @@ func getFrontendAuthenticators() map[string][]loginField {
continue continue
} }
output[a.AuthenticatorID()] = a.LoginFields() output[a.AuthenticatorID()] = a.LoginFields()
if a.SupportsMFA() && !mainCfg.Login.HideMFAField {
output[a.AuthenticatorID()] = append(output[a.AuthenticatorID()], mfaLoginField)
}
} }
return output return output

1
vendor/github.com/boombuler/barcode/.gitignore generated vendored Normal file
View file

@ -0,0 +1 @@
.vscode/

21
vendor/github.com/boombuler/barcode/LICENSE generated vendored Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2014 Florian Sundermann
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.

53
vendor/github.com/boombuler/barcode/README.md generated vendored Normal file
View file

@ -0,0 +1,53 @@
[![Join the chat at https://gitter.im/golang-barcode/Lobby](https://badges.gitter.im/golang-barcode/Lobby.svg)](https://gitter.im/golang-barcode/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
## Introduction ##
This is a package for GO which can be used to create different types of barcodes.
## Supported Barcode Types ##
* 2 of 5
* Aztec Code
* Codabar
* Code 128
* Code 39
* Code 93
* Datamatrix
* EAN 13
* EAN 8
* PDF 417
* QR Code
## Example ##
This is a simple example on how to create a QR-Code and write it to a png-file
```go
package main
import (
"image/png"
"os"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/qr"
)
func main() {
// Create the barcode
qrCode, _ := qr.Encode("Hello World", qr.M, qr.Auto)
// Scale the barcode to 200x200 pixels
qrCode, _ = barcode.Scale(qrCode, 200, 200)
// create the output file
file, _ := os.Create("qrcode.png")
defer file.Close()
// encode the barcode as png
png.Encode(file, qrCode)
}
```
## Documentation ##
See [GoDoc](https://godoc.org/github.com/boombuler/barcode)
To create a barcode use the Encode function from one of the subpackages.

View file

@ -0,0 +1,94 @@
package aztec
import (
"testing"
)
func encodeTest(t *testing.T, data, wanted string) {
result, err := Encode([]byte(data), DEFAULT_EC_PERCENT, DEFAULT_LAYERS)
if err != nil {
t.Error(err)
} else {
ac, ok := result.(*aztecCode)
if !ok {
t.Error("returned barcode is no aztec code...")
} else if draw := ac.string(); draw != wanted {
t.Errorf("Invalid Barcode returned:\n%s", draw)
}
}
}
func Test_Encode1(t *testing.T) {
encodeTest(t, "This is an example Aztec symbol for Wikipedia.",
"X X X X X X X X \n"+
"X X X X X X X X X X \n"+
"X X X X X X X X X X X \n"+
"X X X X X X X X X X X \n"+
" X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X \n"+
"X X X X X X X X X X \n"+
" X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X \n"+
" X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X \n"+
" X X X \n"+
" X X X X X X X X X X \n"+
" X X X X X X X X X X \n")
}
func Test_Encode2(t *testing.T) {
encodeTest(t, "Aztec Code is a public domain 2D matrix barcode symbology"+
" of nominally square symbols built on a square grid with a "+
"distinctive square bullseye pattern at their center.",
" X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X X X X X X X X X \n"+
" X X X X X X X X X X X X X X X X \n"+
"X X X X X X X X X X X X X \n")
}

62
vendor/github.com/boombuler/barcode/aztec/azteccode.go generated vendored Normal file
View file

@ -0,0 +1,62 @@
package aztec
import (
"bytes"
"image"
"image/color"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type aztecCode struct {
*utils.BitList
size int
content []byte
}
func newAztecCode(size int) *aztecCode {
return &aztecCode{utils.NewBitList(size * size), size, nil}
}
func (c *aztecCode) Content() string {
return string(c.content)
}
func (c *aztecCode) Metadata() barcode.Metadata {
return barcode.Metadata{barcode.TypeAztec, 2}
}
func (c *aztecCode) ColorModel() color.Model {
return color.Gray16Model
}
func (c *aztecCode) Bounds() image.Rectangle {
return image.Rect(0, 0, c.size, c.size)
}
func (c *aztecCode) At(x, y int) color.Color {
if c.GetBit(x*c.size + y) {
return color.Black
}
return color.White
}
func (c *aztecCode) set(x, y int) {
c.SetBit(x*c.size+y, true)
}
func (c *aztecCode) string() string {
buf := new(bytes.Buffer)
for y := 0; y < c.size; y++ {
for x := 0; x < c.size; x++ {
if c.GetBit(x*c.size + y) {
buf.WriteString("X ")
} else {
buf.WriteString(" ")
}
}
buf.WriteRune('\n')
}
return buf.String()
}

268
vendor/github.com/boombuler/barcode/aztec/encoder.go generated vendored Normal file
View file

@ -0,0 +1,268 @@
// Package aztec can create Aztec Code barcodes
package aztec
import (
"fmt"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
const (
DEFAULT_EC_PERCENT = 33
DEFAULT_LAYERS = 0
max_nb_bits = 32
max_nb_bits_compact = 4
)
var (
word_size = []int{
4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
}
)
func totalBitsInLayer(layers int, compact bool) int {
tmp := 112
if compact {
tmp = 88
}
return (tmp + 16*layers) * layers
}
func stuffBits(bits *utils.BitList, wordSize int) *utils.BitList {
out := new(utils.BitList)
n := bits.Len()
mask := (1 << uint(wordSize)) - 2
for i := 0; i < n; i += wordSize {
word := 0
for j := 0; j < wordSize; j++ {
if i+j >= n || bits.GetBit(i+j) {
word |= 1 << uint(wordSize-1-j)
}
}
if (word & mask) == mask {
out.AddBits(word&mask, byte(wordSize))
i--
} else if (word & mask) == 0 {
out.AddBits(word|1, byte(wordSize))
i--
} else {
out.AddBits(word, byte(wordSize))
}
}
return out
}
func generateModeMessage(compact bool, layers, messageSizeInWords int) *utils.BitList {
modeMessage := new(utils.BitList)
if compact {
modeMessage.AddBits(layers-1, 2)
modeMessage.AddBits(messageSizeInWords-1, 6)
modeMessage = generateCheckWords(modeMessage, 28, 4)
} else {
modeMessage.AddBits(layers-1, 5)
modeMessage.AddBits(messageSizeInWords-1, 11)
modeMessage = generateCheckWords(modeMessage, 40, 4)
}
return modeMessage
}
func drawModeMessage(matrix *aztecCode, compact bool, matrixSize int, modeMessage *utils.BitList) {
center := matrixSize / 2
if compact {
for i := 0; i < 7; i++ {
offset := center - 3 + i
if modeMessage.GetBit(i) {
matrix.set(offset, center-5)
}
if modeMessage.GetBit(i + 7) {
matrix.set(center+5, offset)
}
if modeMessage.GetBit(20 - i) {
matrix.set(offset, center+5)
}
if modeMessage.GetBit(27 - i) {
matrix.set(center-5, offset)
}
}
} else {
for i := 0; i < 10; i++ {
offset := center - 5 + i + i/5
if modeMessage.GetBit(i) {
matrix.set(offset, center-7)
}
if modeMessage.GetBit(i + 10) {
matrix.set(center+7, offset)
}
if modeMessage.GetBit(29 - i) {
matrix.set(offset, center+7)
}
if modeMessage.GetBit(39 - i) {
matrix.set(center-7, offset)
}
}
}
}
func drawBullsEye(matrix *aztecCode, center, size int) {
for i := 0; i < size; i += 2 {
for j := center - i; j <= center+i; j++ {
matrix.set(j, center-i)
matrix.set(j, center+i)
matrix.set(center-i, j)
matrix.set(center+i, j)
}
}
matrix.set(center-size, center-size)
matrix.set(center-size+1, center-size)
matrix.set(center-size, center-size+1)
matrix.set(center+size, center-size)
matrix.set(center+size, center-size+1)
matrix.set(center+size, center+size-1)
}
// Encode returns an aztec barcode with the given content
func Encode(data []byte, minECCPercent int, userSpecifiedLayers int) (barcode.Barcode, error) {
bits := highlevelEncode(data)
eccBits := ((bits.Len() * minECCPercent) / 100) + 11
totalSizeBits := bits.Len() + eccBits
var layers, TotalBitsInLayer, wordSize int
var compact bool
var stuffedBits *utils.BitList
if userSpecifiedLayers != DEFAULT_LAYERS {
compact = userSpecifiedLayers < 0
if compact {
layers = -userSpecifiedLayers
} else {
layers = userSpecifiedLayers
}
if (compact && layers > max_nb_bits_compact) || (!compact && layers > max_nb_bits) {
return nil, fmt.Errorf("Illegal value %d for layers", userSpecifiedLayers)
}
TotalBitsInLayer = totalBitsInLayer(layers, compact)
wordSize = word_size[layers]
usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize)
stuffedBits = stuffBits(bits, wordSize)
if stuffedBits.Len()+eccBits > usableBitsInLayers {
return nil, fmt.Errorf("Data to large for user specified layer")
}
if compact && stuffedBits.Len() > wordSize*64 {
return nil, fmt.Errorf("Data to large for user specified layer")
}
} else {
wordSize = 0
stuffedBits = nil
// We look at the possible table sizes in the order Compact1, Compact2, Compact3,
// Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1)
// is the same size, but has more data.
for i := 0; ; i++ {
if i > max_nb_bits {
return nil, fmt.Errorf("Data too large for an aztec code")
}
compact = i <= 3
layers = i
if compact {
layers = i + 1
}
TotalBitsInLayer = totalBitsInLayer(layers, compact)
if totalSizeBits > TotalBitsInLayer {
continue
}
// [Re]stuff the bits if this is the first opportunity, or if the
// wordSize has changed
if wordSize != word_size[layers] {
wordSize = word_size[layers]
stuffedBits = stuffBits(bits, wordSize)
}
usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize)
if compact && stuffedBits.Len() > wordSize*64 {
// Compact format only allows 64 data words, though C4 can hold more words than that
continue
}
if stuffedBits.Len()+eccBits <= usableBitsInLayers {
break
}
}
}
messageBits := generateCheckWords(stuffedBits, TotalBitsInLayer, wordSize)
messageSizeInWords := stuffedBits.Len() / wordSize
modeMessage := generateModeMessage(compact, layers, messageSizeInWords)
// allocate symbol
var baseMatrixSize int
if compact {
baseMatrixSize = 11 + layers*4
} else {
baseMatrixSize = 14 + layers*4
}
alignmentMap := make([]int, baseMatrixSize)
var matrixSize int
if compact {
// no alignment marks in compact mode, alignmentMap is a no-op
matrixSize = baseMatrixSize
for i := 0; i < len(alignmentMap); i++ {
alignmentMap[i] = i
}
} else {
matrixSize = baseMatrixSize + 1 + 2*((baseMatrixSize/2-1)/15)
origCenter := baseMatrixSize / 2
center := matrixSize / 2
for i := 0; i < origCenter; i++ {
newOffset := i + i/15
alignmentMap[origCenter-i-1] = center - newOffset - 1
alignmentMap[origCenter+i] = center + newOffset + 1
}
}
code := newAztecCode(matrixSize)
code.content = data
// draw data bits
for i, rowOffset := 0, 0; i < layers; i++ {
rowSize := (layers - i) * 4
if compact {
rowSize += 9
} else {
rowSize += 12
}
for j := 0; j < rowSize; j++ {
columnOffset := j * 2
for k := 0; k < 2; k++ {
if messageBits.GetBit(rowOffset + columnOffset + k) {
code.set(alignmentMap[i*2+k], alignmentMap[i*2+j])
}
if messageBits.GetBit(rowOffset + rowSize*2 + columnOffset + k) {
code.set(alignmentMap[i*2+j], alignmentMap[baseMatrixSize-1-i*2-k])
}
if messageBits.GetBit(rowOffset + rowSize*4 + columnOffset + k) {
code.set(alignmentMap[baseMatrixSize-1-i*2-k], alignmentMap[baseMatrixSize-1-i*2-j])
}
if messageBits.GetBit(rowOffset + rowSize*6 + columnOffset + k) {
code.set(alignmentMap[baseMatrixSize-1-i*2-j], alignmentMap[i*2+k])
}
}
}
rowOffset += rowSize * 8
}
// draw mode message
drawModeMessage(code, compact, matrixSize, modeMessage)
// draw alignment marks
if compact {
drawBullsEye(code, matrixSize/2, 5)
} else {
drawBullsEye(code, matrixSize/2, 7)
for i, j := 0, 0; i < baseMatrixSize/2-1; i, j = i+15, j+16 {
for k := (matrixSize / 2) & 1; k < matrixSize; k += 2 {
code.set(matrixSize/2-j, k)
code.set(matrixSize/2+j, k)
code.set(k, matrixSize/2-j)
code.set(k, matrixSize/2+j)
}
}
}
return code, nil
}

View file

@ -0,0 +1,58 @@
package aztec
import (
"strings"
"testing"
"github.com/boombuler/barcode/utils"
)
func Test_StuffBits(t *testing.T) {
testStuffBits := func(wordSize int, bits string, expected string) {
bl := new(utils.BitList)
for _, r := range bits {
if r == 'X' {
bl.AddBit(true)
} else if r == '.' {
bl.AddBit(false)
}
}
stuffed := stuffBits(bl, wordSize)
expectedBits := strings.Replace(expected, " ", "", -1)
result := bitStr(stuffed)
if result != expectedBits {
t.Errorf("stuffBits failed for %q\nGot: %q", bits, result)
}
}
testStuffBits(5, ".X.X. X.X.X .X.X.",
".X.X. X.X.X .X.X.")
testStuffBits(5, ".X.X. ..... .X.X",
".X.X. ....X ..X.X")
testStuffBits(3, "XX. ... ... ..X XXX .X. ..",
"XX. ..X ..X ..X ..X .XX XX. .X. ..X")
testStuffBits(6, ".X.X.. ...... ..X.XX",
".X.X.. .....X. ..X.XX XXXX.")
testStuffBits(6, ".X.X.. ...... ...... ..X.X.",
".X.X.. .....X .....X ....X. X.XXXX")
testStuffBits(6, ".X.X.. XXXXXX ...... ..X.XX",
".X.X.. XXXXX. X..... ...X.X XXXXX.")
testStuffBits(6,
"...... ..XXXX X..XX. .X.... .X.X.X .....X .X.... ...X.X .....X ....XX ..X... ....X. X..XXX X.XX.X",
".....X ...XXX XX..XX ..X... ..X.X. X..... X.X... ....X. X..... X....X X..X.. .....X X.X..X XXX.XX .XXXXX")
}
func Test_ModeMessage(t *testing.T) {
testModeMessage := func(compact bool, layers, words int, expected string) {
result := bitStr(generateModeMessage(compact, layers, words))
expectedBits := strings.Replace(expected, " ", "", -1)
if result != expectedBits {
t.Errorf("generateModeMessage(%v, %d, %d) failed.\nGot:%s", compact, layers, words, result)
}
}
testModeMessage(true, 2, 29, ".X .XXX.. ...X XX.. ..X .XX. .XX.X")
testModeMessage(true, 4, 64, "XX XXXXXX .X.. ...X ..XX .X.. XX..")
testModeMessage(false, 21, 660, "X.X.. .X.X..X..XX .XXX ..X.. .XXX. .X... ..XXX")
testModeMessage(false, 32, 4096, "XXXXX XXXXXXXXXXX X.X. ..... XXX.X ..X.. X.XXX")
}

View file

@ -0,0 +1,61 @@
package aztec
import (
"github.com/boombuler/barcode/utils"
)
func bitsToWords(stuffedBits *utils.BitList, wordSize int, wordCount int) []int {
message := make([]int, wordCount)
for i := 0; i < wordCount; i++ {
value := 0
for j := 0; j < wordSize; j++ {
if stuffedBits.GetBit(i*wordSize + j) {
value |= (1 << uint(wordSize-j-1))
}
}
message[i] = value
}
return message
}
func generateCheckWords(bits *utils.BitList, totalBits, wordSize int) *utils.BitList {
rs := utils.NewReedSolomonEncoder(getGF(wordSize))
// bits is guaranteed to be a multiple of the wordSize, so no padding needed
messageWordCount := bits.Len() / wordSize
totalWordCount := totalBits / wordSize
eccWordCount := totalWordCount - messageWordCount
messageWords := bitsToWords(bits, wordSize, messageWordCount)
eccWords := rs.Encode(messageWords, eccWordCount)
startPad := totalBits % wordSize
messageBits := new(utils.BitList)
messageBits.AddBits(0, byte(startPad))
for _, messageWord := range messageWords {
messageBits.AddBits(messageWord, byte(wordSize))
}
for _, eccWord := range eccWords {
messageBits.AddBits(eccWord, byte(wordSize))
}
return messageBits
}
func getGF(wordSize int) *utils.GaloisField {
switch wordSize {
case 4:
return utils.NewGaloisField(0x13, 16, 1)
case 6:
return utils.NewGaloisField(0x43, 64, 1)
case 8:
return utils.NewGaloisField(0x012D, 256, 1)
case 10:
return utils.NewGaloisField(0x409, 1024, 1)
case 12:
return utils.NewGaloisField(0x1069, 4096, 1)
default:
return nil
}
}

171
vendor/github.com/boombuler/barcode/aztec/highlevel.go generated vendored Normal file
View file

@ -0,0 +1,171 @@
package aztec
import (
"github.com/boombuler/barcode/utils"
)
func highlevelEncode(data []byte) *utils.BitList {
states := stateSlice{initialState}
for index := 0; index < len(data); index++ {
pairCode := 0
nextChar := byte(0)
if index+1 < len(data) {
nextChar = data[index+1]
}
switch cur := data[index]; {
case cur == '\r' && nextChar == '\n':
pairCode = 2
case cur == '.' && nextChar == ' ':
pairCode = 3
case cur == ',' && nextChar == ' ':
pairCode = 4
case cur == ':' && nextChar == ' ':
pairCode = 5
}
if pairCode > 0 {
// We have one of the four special PUNCT pairs. Treat them specially.
// Get a new set of states for the two new characters.
states = updateStateListForPair(states, data, index, pairCode)
index++
} else {
// Get a new set of states for the new character.
states = updateStateListForChar(states, data, index)
}
}
minBitCnt := int((^uint(0)) >> 1)
var result *state = nil
for _, s := range states {
if s.bitCount < minBitCnt {
minBitCnt = s.bitCount
result = s
}
}
if result != nil {
return result.toBitList(data)
} else {
return new(utils.BitList)
}
}
func simplifyStates(states stateSlice) stateSlice {
var result stateSlice = nil
for _, newState := range states {
add := true
var newResult stateSlice = nil
for _, oldState := range result {
if add && oldState.isBetterThanOrEqualTo(newState) {
add = false
}
if !(add && newState.isBetterThanOrEqualTo(oldState)) {
newResult = append(newResult, oldState)
}
}
if add {
result = append(newResult, newState)
} else {
result = newResult
}
}
return result
}
// We update a set of states for a new character by updating each state
// for the new character, merging the results, and then removing the
// non-optimal states.
func updateStateListForChar(states stateSlice, data []byte, index int) stateSlice {
var result stateSlice = nil
for _, s := range states {
if r := updateStateForChar(s, data, index); len(r) > 0 {
result = append(result, r...)
}
}
return simplifyStates(result)
}
// Return a set of states that represent the possible ways of updating this
// state for the next character. The resulting set of states are added to
// the "result" list.
func updateStateForChar(s *state, data []byte, index int) stateSlice {
var result stateSlice = nil
ch := data[index]
charInCurrentTable := charMap[s.mode][ch] > 0
var stateNoBinary *state = nil
for mode := mode_upper; mode <= mode_punct; mode++ {
charInMode := charMap[mode][ch]
if charInMode > 0 {
if stateNoBinary == nil {
// Only create stateNoBinary the first time it's required.
stateNoBinary = s.endBinaryShift(index)
}
// Try generating the character by latching to its mode
if !charInCurrentTable || mode == s.mode || mode == mode_digit {
// If the character is in the current table, we don't want to latch to
// any other mode except possibly digit (which uses only 4 bits). Any
// other latch would be equally successful *after* this character, and
// so wouldn't save any bits.
res := stateNoBinary.latchAndAppend(mode, charInMode)
result = append(result, res)
}
// Try generating the character by switching to its mode.
if _, ok := shiftTable[s.mode][mode]; !charInCurrentTable && ok {
// It never makes sense to temporarily shift to another mode if the
// character exists in the current mode. That can never save bits.
res := stateNoBinary.shiftAndAppend(mode, charInMode)
result = append(result, res)
}
}
}
if s.bShiftByteCount > 0 || charMap[s.mode][ch] == 0 {
// It's never worthwhile to go into binary shift mode if you're not already
// in binary shift mode, and the character exists in your current mode.
// That can never save bits over just outputting the char in the current mode.
res := s.addBinaryShiftChar(index)
result = append(result, res)
}
return result
}
// We update a set of states for a new character by updating each state
// for the new character, merging the results, and then removing the
// non-optimal states.
func updateStateListForPair(states stateSlice, data []byte, index int, pairCode int) stateSlice {
var result stateSlice = nil
for _, s := range states {
if r := updateStateForPair(s, data, index, pairCode); len(r) > 0 {
result = append(result, r...)
}
}
return simplifyStates(result)
}
func updateStateForPair(s *state, data []byte, index int, pairCode int) stateSlice {
var result stateSlice
stateNoBinary := s.endBinaryShift(index)
// Possibility 1. Latch to MODE_PUNCT, and then append this code
result = append(result, stateNoBinary.latchAndAppend(mode_punct, pairCode))
if s.mode != mode_punct {
// Possibility 2. Shift to MODE_PUNCT, and then append this code.
// Every state except MODE_PUNCT (handled above) can shift
result = append(result, stateNoBinary.shiftAndAppend(mode_punct, pairCode))
}
if pairCode == 3 || pairCode == 4 {
// both characters are in DIGITS. Sometimes better to just add two digits
digitState := stateNoBinary.
latchAndAppend(mode_digit, 16-pairCode). // period or comma in DIGIT
latchAndAppend(mode_digit, 1) // space in DIGIT
result = append(result, digitState)
}
if s.bShiftByteCount > 0 {
// It only makes sense to do the characters as binary if we're already
// in binary mode.
result = append(result, s.addBinaryShiftChar(index).addBinaryShiftChar(index+1))
}
return result
}

View file

@ -0,0 +1,132 @@
package aztec
import (
"bytes"
"strings"
"testing"
"github.com/boombuler/barcode/utils"
)
func bitStr(bl *utils.BitList) string {
buf := new(bytes.Buffer)
for i := 0; i < bl.Len(); i++ {
if bl.GetBit(i) {
buf.WriteRune('X')
} else {
buf.WriteRune('.')
}
}
return buf.String()
}
func testHighLevelEncodeString(t *testing.T, s, expectedBits string) {
bits := highlevelEncode([]byte(s))
result := bitStr(bits)
expectedBits = strings.Replace(expectedBits, " ", "", -1)
if result != expectedBits {
t.Errorf("invalid result for highlevelEncode(%q). Got:\n%s", s, result)
}
}
func testHighLevelEncodeStringCnt(t *testing.T, s string, expectedBitCnt int) {
bits := highlevelEncode([]byte(s))
if bits.Len() != expectedBitCnt {
t.Errorf("invalid result for highlevelEncode(%q). Got %d, expected %d bits", s, bits.Len(), expectedBitCnt)
}
}
func Test_HighLevelEncode(t *testing.T) {
testHighLevelEncodeString(t, "A. b.",
// 'A' P/S '. ' L/L b D/L '.'
"...X. ..... ...XX XXX.. ...XX XXXX. XX.X")
testHighLevelEncodeString(t, "Lorem ipsum.",
// 'L' L/L 'o' 'r' 'e' 'm' ' ' 'i' 'p' 's' 'u' 'm' D/L '.'
".XX.X XXX.. X.... X..XX ..XX. .XXX. ....X .X.X. X...X X.X.. X.XX. .XXX. XXXX. XX.X")
testHighLevelEncodeString(t, "Lo. Test 123.",
// 'L' L/L 'o' P/S '. ' U/S 'T' 'e' 's' 't' D/L ' ' '1' '2' '3' '.'
".XX.X XXX.. X.... ..... ...XX XXX.. X.X.X ..XX. X.X.. X.X.X XXXX. ...X ..XX .X.. .X.X XX.X")
testHighLevelEncodeString(t, "Lo...x",
// 'L' L/L 'o' D/L '.' '.' '.' U/L L/L 'x'
".XX.X XXX.. X.... XXXX. XX.X XX.X XX.X XXX. XXX.. XX..X")
testHighLevelEncodeString(t, ". x://abc/.",
//P/S '. ' L/L 'x' P/S ':' P/S '/' P/S '/' 'a' 'b' 'c' P/S '/' D/L '.'
"..... ...XX XXX.. XX..X ..... X.X.X ..... X.X.. ..... X.X.. ...X. ...XX ..X.. ..... X.X.. XXXX. XX.X")
// Uses Binary/Shift rather than Lower/Shift to save two bits.
testHighLevelEncodeString(t, "ABCdEFG",
//'A' 'B' 'C' B/S =1 'd' 'E' 'F' 'G'
"...X. ...XX ..X.. XXXXX ....X .XX..X.. ..XX. ..XXX .X...")
testHighLevelEncodeStringCnt(t,
// Found on an airline boarding pass. Several stretches of Binary shift are
// necessary to keep the bitcount so low.
"09 UAG ^160MEUCIQC0sYS/HpKxnBELR1uB85R20OoqqwFGa0q2uEi"+
"Ygh6utAIgLl1aBVM4EOTQtMQQYH9M2Z3Dp4qnA/fwWuQ+M8L3V8U=",
823)
}
func Test_HighLevelEncodeBinary(t *testing.T) {
// binary short form single byte
testHighLevelEncodeString(t, "N\u0000N",
// 'N' B/S =1 '\0' N
".XXXX XXXXX ....X ........ .XXXX") // Encode "N" in UPPER
testHighLevelEncodeString(t, "N\u0000n",
// 'N' B/S =2 '\0' 'n'
".XXXX XXXXX ...X. ........ .XX.XXX.") // Encode "n" in BINARY
// binary short form consecutive bytes
testHighLevelEncodeString(t, "N\x00\x80 A",
// 'N' B/S =2 '\0' \u0080 ' ' 'A'
".XXXX XXXXX ...X. ........ X....... ....X ...X.")
// binary skipping over single character
testHighLevelEncodeString(t, "\x00a\xFF\x80 A",
// B/S =4 '\0' 'a' '\3ff' '\200' ' ' 'A'
"XXXXX ..X.. ........ .XX....X XXXXXXXX X....... ....X ...X.")
// getting into binary mode from digit mode
testHighLevelEncodeString(t, "1234\u0000",
//D/L '1' '2' '3' '4' U/L B/S =1 \0
"XXXX. ..XX .X.. .X.X .XX. XXX. XXXXX ....X ........")
// Create a string in which every character requires binary
sb := new(bytes.Buffer)
for i := 0; i <= 3000; i++ {
sb.WriteByte(byte(128 + (i % 30)))
}
// Test the output generated by Binary/Switch, particularly near the
// places where the encoding changes: 31, 62, and 2047+31=2078
for _, i := range []int{1, 2, 3, 10, 29, 30, 31, 32, 33, 60, 61, 62, 63, 64, 2076, 2077, 2078, 2079, 2080, 2100} {
// This is the expected length of a binary string of length "i"
expectedLength := (8 * i)
switch {
case i <= 31:
expectedLength += 10
case i <= 62:
expectedLength += 20
case i <= 2078:
expectedLength += 21
default:
expectedLength += 31
}
data := string(sb.Bytes()[:i])
// Verify that we are correct about the length.
testHighLevelEncodeStringCnt(t, data, expectedLength)
if i != 1 && i != 32 && i != 2079 {
// The addition of an 'a' at the beginning or end gets merged into the binary code
// in those cases where adding another binary character only adds 8 or 9 bits to the result.
// So we exclude the border cases i=1,32,2079
// A lower case letter at the beginning will be merged into binary mode
testHighLevelEncodeStringCnt(t, "a"+string(sb.Bytes()[:i-1]), expectedLength)
// A lower case letter at the end will also be merged into binary mode
testHighLevelEncodeStringCnt(t, string(sb.Bytes()[:i-1])+"a", expectedLength)
}
// A lower case letter at both ends will enough to latch us into LOWER.
testHighLevelEncodeStringCnt(t, "a"+data+"b", expectedLength+15)
}
}

264
vendor/github.com/boombuler/barcode/aztec/state.go generated vendored Normal file
View file

@ -0,0 +1,264 @@
package aztec
import (
"fmt"
"github.com/boombuler/barcode/utils"
)
type encodingMode byte
const (
mode_upper encodingMode = iota // 5 bits
mode_lower // 5 bits
mode_digit // 4 bits
mode_mixed // 5 bits
mode_punct // 5 bits
)
var (
// The Latch Table shows, for each pair of Modes, the optimal method for
// getting from one mode to another. In the worst possible case, this can
// be up to 14 bits. In the best possible case, we are already there!
// The high half-word of each entry gives the number of bits.
// The low half-word of each entry are the actual bits necessary to change
latchTable = map[encodingMode]map[encodingMode]int{
mode_upper: {
mode_upper: 0,
mode_lower: (5 << 16) + 28,
mode_digit: (5 << 16) + 30,
mode_mixed: (5 << 16) + 29,
mode_punct: (10 << 16) + (29 << 5) + 30,
},
mode_lower: {
mode_upper: (9 << 16) + (30 << 4) + 14,
mode_lower: 0,
mode_digit: (5 << 16) + 30,
mode_mixed: (5 << 16) + 29,
mode_punct: (10 << 16) + (29 << 5) + 30,
},
mode_digit: {
mode_upper: (4 << 16) + 14,
mode_lower: (9 << 16) + (14 << 5) + 28,
mode_digit: 0,
mode_mixed: (9 << 16) + (14 << 5) + 29,
mode_punct: (14 << 16) + (14 << 10) + (29 << 5) + 30,
},
mode_mixed: {
mode_upper: (5 << 16) + 29,
mode_lower: (5 << 16) + 28,
mode_digit: (10 << 16) + (29 << 5) + 30,
mode_mixed: 0,
mode_punct: (5 << 16) + 30,
},
mode_punct: {
mode_upper: (5 << 16) + 31,
mode_lower: (10 << 16) + (31 << 5) + 28,
mode_digit: (10 << 16) + (31 << 5) + 30,
mode_mixed: (10 << 16) + (31 << 5) + 29,
mode_punct: 0,
},
}
// A map showing the available shift codes. (The shifts to BINARY are not shown)
shiftTable = map[encodingMode]map[encodingMode]int{
mode_upper: {
mode_punct: 0,
},
mode_lower: {
mode_punct: 0,
mode_upper: 28,
},
mode_mixed: {
mode_punct: 0,
},
mode_digit: {
mode_punct: 0,
mode_upper: 15,
},
}
charMap map[encodingMode][]int
)
type state struct {
mode encodingMode
tokens token
bShiftByteCount int
bitCount int
}
type stateSlice []*state
var initialState *state = &state{
mode: mode_upper,
tokens: nil,
bShiftByteCount: 0,
bitCount: 0,
}
func init() {
charMap = make(map[encodingMode][]int)
charMap[mode_upper] = make([]int, 256)
charMap[mode_lower] = make([]int, 256)
charMap[mode_digit] = make([]int, 256)
charMap[mode_mixed] = make([]int, 256)
charMap[mode_punct] = make([]int, 256)
charMap[mode_upper][' '] = 1
for c := 'A'; c <= 'Z'; c++ {
charMap[mode_upper][int(c)] = int(c - 'A' + 2)
}
charMap[mode_lower][' '] = 1
for c := 'a'; c <= 'z'; c++ {
charMap[mode_lower][c] = int(c - 'a' + 2)
}
charMap[mode_digit][' '] = 1
for c := '0'; c <= '9'; c++ {
charMap[mode_digit][c] = int(c - '0' + 2)
}
charMap[mode_digit][','] = 12
charMap[mode_digit]['.'] = 13
mixedTable := []int{
0, ' ', 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 27, 28, 29, 30, 31, '@', '\\', '^',
'_', '`', '|', '~', 127,
}
for i, v := range mixedTable {
charMap[mode_mixed][v] = i
}
punctTable := []int{
0, '\r', 0, 0, 0, 0, '!', '\'', '#', '$', '%', '&', '\'',
'(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?',
'[', ']', '{', '}',
}
for i, v := range punctTable {
if v > 0 {
charMap[mode_punct][v] = i
}
}
}
func (em encodingMode) BitCount() byte {
if em == mode_digit {
return 4
}
return 5
}
// Create a new state representing this state with a latch to a (not
// necessary different) mode, and then a code.
func (s *state) latchAndAppend(mode encodingMode, value int) *state {
bitCount := s.bitCount
tokens := s.tokens
if mode != s.mode {
latch := latchTable[s.mode][mode]
tokens = newSimpleToken(tokens, latch&0xFFFF, byte(latch>>16))
bitCount += latch >> 16
}
tokens = newSimpleToken(tokens, value, mode.BitCount())
return &state{
mode: mode,
tokens: tokens,
bShiftByteCount: 0,
bitCount: bitCount + int(mode.BitCount()),
}
}
// Create a new state representing this state, with a temporary shift
// to a different mode to output a single value.
func (s *state) shiftAndAppend(mode encodingMode, value int) *state {
tokens := s.tokens
// Shifts exist only to UPPER and PUNCT, both with tokens size 5.
tokens = newSimpleToken(tokens, shiftTable[s.mode][mode], s.mode.BitCount())
tokens = newSimpleToken(tokens, value, 5)
return &state{
mode: s.mode,
tokens: tokens,
bShiftByteCount: 0,
bitCount: s.bitCount + int(s.mode.BitCount()) + 5,
}
}
// Create a new state representing this state, but an additional character
// output in Binary Shift mode.
func (s *state) addBinaryShiftChar(index int) *state {
tokens := s.tokens
mode := s.mode
bitCnt := s.bitCount
if s.mode == mode_punct || s.mode == mode_digit {
latch := latchTable[s.mode][mode_upper]
tokens = newSimpleToken(tokens, latch&0xFFFF, byte(latch>>16))
bitCnt += latch >> 16
mode = mode_upper
}
deltaBitCount := 8
if s.bShiftByteCount == 0 || s.bShiftByteCount == 31 {
deltaBitCount = 18
} else if s.bShiftByteCount == 62 {
deltaBitCount = 9
}
result := &state{
mode: mode,
tokens: tokens,
bShiftByteCount: s.bShiftByteCount + 1,
bitCount: bitCnt + deltaBitCount,
}
if result.bShiftByteCount == 2047+31 {
// The string is as long as it's allowed to be. We should end it.
result = result.endBinaryShift(index + 1)
}
return result
}
// Create the state identical to this one, but we are no longer in
// Binary Shift mode.
func (s *state) endBinaryShift(index int) *state {
if s.bShiftByteCount == 0 {
return s
}
tokens := newShiftToken(s.tokens, index-s.bShiftByteCount, s.bShiftByteCount)
return &state{
mode: s.mode,
tokens: tokens,
bShiftByteCount: 0,
bitCount: s.bitCount,
}
}
// Returns true if "this" state is better (or equal) to be in than "that"
// state under all possible circumstances.
func (this *state) isBetterThanOrEqualTo(other *state) bool {
mySize := this.bitCount + (latchTable[this.mode][other.mode] >> 16)
if other.bShiftByteCount > 0 && (this.bShiftByteCount == 0 || this.bShiftByteCount > other.bShiftByteCount) {
mySize += 10 // Cost of entering Binary Shift mode.
}
return mySize <= other.bitCount
}
func (s *state) toBitList(text []byte) *utils.BitList {
tokens := make([]token, 0)
se := s.endBinaryShift(len(text))
for t := se.tokens; t != nil; t = t.prev() {
tokens = append(tokens, t)
}
res := new(utils.BitList)
for i := len(tokens) - 1; i >= 0; i-- {
tokens[i].appendTo(res, text)
}
return res
}
func (s *state) String() string {
tokens := make([]token, 0)
for t := s.tokens; t != nil; t = t.prev() {
tokens = append([]token{t}, tokens...)
}
return fmt.Sprintf("M:%d bits=%d bytes=%d: %v", s.mode, s.bitCount, s.bShiftByteCount, tokens)
}

75
vendor/github.com/boombuler/barcode/aztec/token.go generated vendored Normal file
View file

@ -0,0 +1,75 @@
package aztec
import (
"fmt"
"github.com/boombuler/barcode/utils"
)
type token interface {
fmt.Stringer
prev() token
appendTo(bits *utils.BitList, text []byte)
}
type simpleToken struct {
token
value int
bitCount byte
}
type binaryShiftToken struct {
token
bShiftStart int
bShiftByteCnt int
}
func newSimpleToken(prev token, value int, bitCount byte) token {
return &simpleToken{prev, value, bitCount}
}
func newShiftToken(prev token, bShiftStart int, bShiftCnt int) token {
return &binaryShiftToken{prev, bShiftStart, bShiftCnt}
}
func (st *simpleToken) prev() token {
return st.token
}
func (st *simpleToken) appendTo(bits *utils.BitList, text []byte) {
bits.AddBits(st.value, st.bitCount)
}
func (st *simpleToken) String() string {
value := st.value & ((1 << st.bitCount) - 1)
value |= 1 << st.bitCount
return "<" + fmt.Sprintf("%b", value)[1:] + ">"
}
func (bst *binaryShiftToken) prev() token {
return bst.token
}
func (bst *binaryShiftToken) appendTo(bits *utils.BitList, text []byte) {
for i := 0; i < bst.bShiftByteCnt; i++ {
if i == 0 || (i == 31 && bst.bShiftByteCnt <= 62) {
// We need a header before the first character, and before
// character 31 when the total byte code is <= 62
bits.AddBits(31, 5) // BINARY_SHIFT
if bst.bShiftByteCnt > 62 {
bits.AddBits(bst.bShiftByteCnt-31, 16)
} else if i == 0 {
// 1 <= binaryShiftByteCode <= 62
if bst.bShiftByteCnt < 31 {
bits.AddBits(bst.bShiftByteCnt, 5)
} else {
bits.AddBits(31, 5)
}
} else {
// 32 <= binaryShiftCount <= 62 and i == 31
bits.AddBits(bst.bShiftByteCnt-31, 5)
}
}
bits.AddByte(text[bst.bShiftStart+i])
}
}
func (bst *binaryShiftToken) String() string {
return fmt.Sprintf("<%d::%d>", bst.bShiftStart, (bst.bShiftStart + bst.bShiftByteCnt - 1))
}

42
vendor/github.com/boombuler/barcode/barcode.go generated vendored Normal file
View file

@ -0,0 +1,42 @@
package barcode
import "image"
const (
TypeAztec = "Aztec"
TypeCodabar = "Codabar"
TypeCode128 = "Code 128"
TypeCode39 = "Code 39"
TypeCode93 = "Code 93"
TypeDataMatrix = "DataMatrix"
TypeEAN8 = "EAN 8"
TypeEAN13 = "EAN 13"
TypePDF = "PDF417"
TypeQR = "QR Code"
Type2of5 = "2 of 5"
Type2of5Interleaved = "2 of 5 (interleaved)"
)
// Contains some meta information about a barcode
type Metadata struct {
// the name of the barcode kind
CodeKind string
// contains 1 for 1D barcodes or 2 for 2D barcodes
Dimensions byte
}
// a rendered and encoded barcode
type Barcode interface {
image.Image
// returns some meta information about the barcode
Metadata() Metadata
// the data that was encoded in this barcode
Content() string
}
// Additional interface that some barcodes might implement to provide
// the value of its checksum.
type BarcodeIntCS interface {
Barcode
CheckSum() int
}

49
vendor/github.com/boombuler/barcode/codabar/encoder.go generated vendored Normal file
View file

@ -0,0 +1,49 @@
// Package codabar can create Codabar barcodes
package codabar
import (
"fmt"
"regexp"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
var encodingTable = map[rune][]bool{
'0': []bool{true, false, true, false, true, false, false, true, true},
'1': []bool{true, false, true, false, true, true, false, false, true},
'2': []bool{true, false, true, false, false, true, false, true, true},
'3': []bool{true, true, false, false, true, false, true, false, true},
'4': []bool{true, false, true, true, false, true, false, false, true},
'5': []bool{true, true, false, true, false, true, false, false, true},
'6': []bool{true, false, false, true, false, true, false, true, true},
'7': []bool{true, false, false, true, false, true, true, false, true},
'8': []bool{true, false, false, true, true, false, true, false, true},
'9': []bool{true, true, false, true, false, false, true, false, true},
'-': []bool{true, false, true, false, false, true, true, false, true},
'$': []bool{true, false, true, true, false, false, true, false, true},
':': []bool{true, true, false, true, false, true, true, false, true, true},
'/': []bool{true, true, false, true, true, false, true, false, true, true},
'.': []bool{true, true, false, true, true, false, true, true, false, true},
'+': []bool{true, false, true, true, false, false, true, true, false, false, true, true},
'A': []bool{true, false, true, true, false, false, true, false, false, true},
'B': []bool{true, false, true, false, false, true, false, false, true, true},
'C': []bool{true, false, false, true, false, false, true, false, true, true},
'D': []bool{true, false, true, false, false, true, true, false, false, true},
}
// Encode creates a codabar barcode for the given content
func Encode(content string) (barcode.Barcode, error) {
checkValid, _ := regexp.Compile(`[ABCD][0123456789\-\$\:/\.\+]*[ABCD]$`)
if content == "!" || checkValid.ReplaceAllString(content, "!") != "!" {
return nil, fmt.Errorf("can not encode \"%s\"", content)
}
resBits := new(utils.BitList)
for i, r := range content {
if i > 0 {
resBits.AddBit(false)
}
resBits.AddBit(encodingTable[r]...)
}
return utils.New1DCode(barcode.TypeCodabar, content, resBits), nil
}

View file

@ -0,0 +1,32 @@
package codabar
import (
"image/color"
"testing"
)
func Test_Encode(t *testing.T) {
_, err := Encode("FOOBAR")
if err == nil {
t.Error("\"FOOBAR\" should not be encodable")
}
testEncode := func(txt, testResult string) {
code, err := Encode(txt)
if err != nil || code == nil {
t.Fail()
} else {
if code.Bounds().Max.X != len(testResult) {
t.Errorf("%v: length missmatch", txt)
} else {
for i, r := range testResult {
if (code.At(i, 0) == color.Black) != (r == '1') {
t.Errorf("%v: code missmatch on position %d", txt, i)
}
}
}
}
}
testEncode("A40156B", "10110010010101101001010101001101010110010110101001010010101101010010011")
}

203
vendor/github.com/boombuler/barcode/code128/encode.go generated vendored Normal file
View file

@ -0,0 +1,203 @@
// Package code128 can create Code128 barcodes
package code128
import (
"fmt"
"strings"
"unicode/utf8"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
func strToRunes(str string) []rune {
result := make([]rune, utf8.RuneCountInString(str))
i := 0
for _, r := range str {
result[i] = r
i++
}
return result
}
func shouldUseCTable(nextRunes []rune, curEncoding byte) bool {
requiredDigits := 4
if curEncoding == startCSymbol {
requiredDigits = 2
}
if len(nextRunes) < requiredDigits {
return false
}
for i := 0; i < requiredDigits; i++ {
if i%2 == 0 && nextRunes[i] == FNC1 {
requiredDigits++
if len(nextRunes) < requiredDigits {
return false
}
continue
}
if nextRunes[i] < '0' || nextRunes[i] > '9' {
return false
}
}
return true
}
func tableContainsRune(table string, r rune) bool {
return strings.ContainsRune(table, r) || r == FNC1 || r == FNC2 || r == FNC3 || r == FNC4
}
func shouldUseATable(nextRunes []rune, curEncoding byte) bool {
nextRune := nextRunes[0]
if !tableContainsRune(bTable, nextRune) || curEncoding == startASymbol {
return tableContainsRune(aTable, nextRune)
}
if curEncoding == 0 {
for _, r := range nextRunes {
if tableContainsRune(abTable, r) {
continue
}
if strings.ContainsRune(aOnlyTable, r) {
return true
}
break
}
}
return false
}
func getCodeIndexList(content []rune) *utils.BitList {
result := new(utils.BitList)
curEncoding := byte(0)
for i := 0; i < len(content); i++ {
if shouldUseCTable(content[i:], curEncoding) {
if curEncoding != startCSymbol {
if curEncoding == byte(0) {
result.AddByte(startCSymbol)
} else {
result.AddByte(codeCSymbol)
}
curEncoding = startCSymbol
}
if content[i] == FNC1 {
result.AddByte(102)
} else {
idx := (content[i] - '0') * 10
i++
idx = idx + (content[i] - '0')
result.AddByte(byte(idx))
}
} else if shouldUseATable(content[i:], curEncoding) {
if curEncoding != startASymbol {
if curEncoding == byte(0) {
result.AddByte(startASymbol)
} else {
result.AddByte(codeASymbol)
}
curEncoding = startASymbol
}
var idx int
switch content[i] {
case FNC1:
idx = 102
break
case FNC2:
idx = 97
break
case FNC3:
idx = 96
break
case FNC4:
idx = 101
break
default:
idx = strings.IndexRune(aTable, content[i])
break
}
if idx < 0 {
return nil
}
result.AddByte(byte(idx))
} else {
if curEncoding != startBSymbol {
if curEncoding == byte(0) {
result.AddByte(startBSymbol)
} else {
result.AddByte(codeBSymbol)
}
curEncoding = startBSymbol
}
var idx int
switch content[i] {
case FNC1:
idx = 102
break
case FNC2:
idx = 97
break
case FNC3:
idx = 96
break
case FNC4:
idx = 100
break
default:
idx = strings.IndexRune(bTable, content[i])
break
}
if idx < 0 {
return nil
}
result.AddByte(byte(idx))
}
}
return result
}
// Encode creates a Code 128 barcode for the given content
func Encode(content string) (barcode.BarcodeIntCS, error) {
contentRunes := strToRunes(content)
if len(contentRunes) <= 0 || len(contentRunes) > 80 {
return nil, fmt.Errorf("content length should be between 1 and 80 runes but got %d", len(contentRunes))
}
idxList := getCodeIndexList(contentRunes)
if idxList == nil {
return nil, fmt.Errorf("\"%s\" could not be encoded", content)
}
result := new(utils.BitList)
sum := 0
for i, idx := range idxList.GetBytes() {
if i == 0 {
sum = int(idx)
} else {
sum += i * int(idx)
}
result.AddBit(encodingTable[idx]...)
}
sum = sum % 103
result.AddBit(encodingTable[sum]...)
result.AddBit(encodingTable[stopSymbol]...)
return utils.New1DCodeIntCheckSum(barcode.TypeCode128, content, result, sum), nil
}
func EncodeWithoutChecksum(content string) (barcode.Barcode, error) {
contentRunes := strToRunes(content)
if len(contentRunes) <= 0 || len(contentRunes) > 80 {
return nil, fmt.Errorf("content length should be between 1 and 80 runes but got %d", len(contentRunes))
}
idxList := getCodeIndexList(contentRunes)
if idxList == nil {
return nil, fmt.Errorf("\"%s\" could not be encoded", content)
}
result := new(utils.BitList)
for _, idx := range idxList.GetBytes() {
result.AddBit(encodingTable[idx]...)
}
result.AddBit(encodingTable[stopSymbol]...)
return utils.New1DCode(barcode.TypeCode128, content, result), nil
}

View file

@ -0,0 +1,131 @@
package code128
import (
"image/color"
"testing"
)
func testEncode(t *testing.T, txt, testResult string) {
code, err := Encode(txt)
if err != nil || code == nil {
t.Error(err)
} else {
if code.Bounds().Max.X != len(testResult) {
t.Errorf("%v: length missmatch. Got %d expected %d", txt, code.Bounds().Max.X, len(testResult))
} else {
encoded := ""
failed := false
for i, r := range testResult {
if code.At(i, 0) == color.Black {
encoded += "1"
} else {
encoded += "0"
}
if (code.At(i, 0) == color.Black) != (r == '1') {
failed = true
t.Errorf("%v: code missmatch on position %d", txt, i)
}
}
if failed {
t.Log("Encoded: ", encoded)
}
}
}
}
func Test_EncodeFunctionChars(t *testing.T) {
encFNC1 := "11110101110"
encFNC2 := "11110101000"
encFNC3 := "10111100010"
encFNC4 := "10111101110"
encStartB := "11010010000"
encStop := "1100011101011"
// Special Case FC1 can also be encoded to C Table therefor using 123 as suffix might have unexpected results.
testEncode(t, string(FNC1)+"A23", encStartB+encFNC1+"10100011000"+"11001110010"+"11001011100"+"10100011110"+encStop)
testEncode(t, string(FNC2)+"123", encStartB+encFNC2+"10011100110"+"11001110010"+"11001011100"+"11100010110"+encStop)
testEncode(t, string(FNC3)+"123", encStartB+encFNC3+"10011100110"+"11001110010"+"11001011100"+"11101000110"+encStop)
testEncode(t, string(FNC4)+"123", encStartB+encFNC4+"10011100110"+"11001110010"+"11001011100"+"11100011010"+encStop)
}
func Test_Unencodable(t *testing.T) {
if _, err := Encode(""); err == nil {
t.Fail()
}
if _, err := Encode("ä"); err == nil {
t.Fail()
}
}
func Test_EncodeCTable(t *testing.T) {
testEncode(t, "HI345678H", "110100100001100010100011000100010101110111101000101100011100010110110000101001011110111011000101000111011000101100011101011")
testEncode(t, "334455", "11010011100101000110001000110111011101000110100100111101100011101011")
testEncode(t, string(FNC1)+"1234",
"11010011100"+ // Start C
"11110101110"+ // FNC1
"10110011100"+ // 12
"10001011000"+ // 34
"11101001100"+ // CheckSum == 24
"1100011101011") // Stop
}
func Test_shouldUseCTable(t *testing.T) {
if !shouldUseCTable([]rune{FNC1, '1', '2'}, startCSymbol) {
t.Error("[FNC1]12 failed")
}
if shouldUseCTable([]rune{FNC1, '1'}, startCSymbol) {
t.Error("[FNC1]1 failed")
}
if shouldUseCTable([]rune{'0', FNC1, '1'}, startCSymbol) {
t.Error("0[FNC1]1 failed")
}
if !shouldUseCTable([]rune{'0', '1', FNC1, '2', '3'}, startBSymbol) {
t.Error("01[FNC1]23 failed")
}
if shouldUseCTable([]rune{'0', '1', FNC1}, startBSymbol) {
t.Error("01[FNC1] failed")
}
}
func Test_Issue16(t *testing.T) {
if !shouldUseATable([]rune{'\r', 'A'}, 0) {
t.Error("Code should start with A-Table if the text start with \\r")
}
if !shouldUseATable([]rune{FNC1, '\r'}, 0) {
t.Error("Code should start with A-Table if the text start with <FNC1>\\r")
}
if shouldUseATable([]rune{FNC1, '1', '2', '3'}, 0) {
t.Error("Code should not start with A-Table if the text start with <FNC1>123")
}
testEncode(t, string(FNC3)+"$P\rI", "110100001001011110001010010001100111011101101111011101011000100010110001010001100011101011")
}
func Test_Datalogic(t *testing.T) {
// <Start A><FNC3>$P\r<checksum><STOP>
testEncode(t, string(FNC3)+"$P\r",
"11010000100"+ // <Start A>
"10111100010"+ // <FNC3>
"10010001100"+ // $
"11101110110"+ // P
"11110111010"+ // CR
"11000100010"+ // checksum = 'I'
"1100011101011") // STOP
// <Start B><FNC3>$P,Ae,P<CR><checksum><STOP>
testEncode(t, string(FNC3)+"$P,Ae,P\r",
"11010010000"+ // <Start B>
"10111100010"+ // <FNC3>
"10010001100"+ // $
"11101110110"+ // P
"10110011100"+ // ,
"10100011000"+ // A
"10110010000"+ // e
"10110011100"+ // ,
"11101110110"+ // P
"11101011110"+ // <Code A>
"11110111010"+ // <CR>
"10110001000"+ // checksum = 'D'
"1100011101011") // STOP
}

View file

@ -0,0 +1,143 @@
package code128
var encodingTable = [107][]bool{
[]bool{true, true, false, true, true, false, false, true, true, false, false},
[]bool{true, true, false, false, true, true, false, true, true, false, false},
[]bool{true, true, false, false, true, true, false, false, true, true, false},
[]bool{true, false, false, true, false, false, true, true, false, false, false},
[]bool{true, false, false, true, false, false, false, true, true, false, false},
[]bool{true, false, false, false, true, false, false, true, true, false, false},
[]bool{true, false, false, true, true, false, false, true, false, false, false},
[]bool{true, false, false, true, true, false, false, false, true, false, false},
[]bool{true, false, false, false, true, true, false, false, true, false, false},
[]bool{true, true, false, false, true, false, false, true, false, false, false},
[]bool{true, true, false, false, true, false, false, false, true, false, false},
[]bool{true, true, false, false, false, true, false, false, true, false, false},
[]bool{true, false, true, true, false, false, true, true, true, false, false},
[]bool{true, false, false, true, true, false, true, true, true, false, false},
[]bool{true, false, false, true, true, false, false, true, true, true, false},
[]bool{true, false, true, true, true, false, false, true, true, false, false},
[]bool{true, false, false, true, true, true, false, true, true, false, false},
[]bool{true, false, false, true, true, true, false, false, true, true, false},
[]bool{true, true, false, false, true, true, true, false, false, true, false},
[]bool{true, true, false, false, true, false, true, true, true, false, false},
[]bool{true, true, false, false, true, false, false, true, true, true, false},
[]bool{true, true, false, true, true, true, false, false, true, false, false},
[]bool{true, true, false, false, true, true, true, false, true, false, false},
[]bool{true, true, true, false, true, true, false, true, true, true, false},
[]bool{true, true, true, false, true, false, false, true, true, false, false},
[]bool{true, true, true, false, false, true, false, true, true, false, false},
[]bool{true, true, true, false, false, true, false, false, true, true, false},
[]bool{true, true, true, false, true, true, false, false, true, false, false},
[]bool{true, true, true, false, false, true, true, false, true, false, false},
[]bool{true, true, true, false, false, true, true, false, false, true, false},
[]bool{true, true, false, true, true, false, true, true, false, false, false},
[]bool{true, true, false, true, true, false, false, false, true, true, false},
[]bool{true, true, false, false, false, true, true, false, true, true, false},
[]bool{true, false, true, false, false, false, true, true, false, false, false},
[]bool{true, false, false, false, true, false, true, true, false, false, false},
[]bool{true, false, false, false, true, false, false, false, true, true, false},
[]bool{true, false, true, true, false, false, false, true, false, false, false},
[]bool{true, false, false, false, true, true, false, true, false, false, false},
[]bool{true, false, false, false, true, true, false, false, false, true, false},
[]bool{true, true, false, true, false, false, false, true, false, false, false},
[]bool{true, true, false, false, false, true, false, true, false, false, false},
[]bool{true, true, false, false, false, true, false, false, false, true, false},
[]bool{true, false, true, true, false, true, true, true, false, false, false},
[]bool{true, false, true, true, false, false, false, true, true, true, false},
[]bool{true, false, false, false, true, true, false, true, true, true, false},
[]bool{true, false, true, true, true, false, true, true, false, false, false},
[]bool{true, false, true, true, true, false, false, false, true, true, false},
[]bool{true, false, false, false, true, true, true, false, true, true, false},
[]bool{true, true, true, false, true, true, true, false, true, true, false},
[]bool{true, true, false, true, false, false, false, true, true, true, false},
[]bool{true, true, false, false, false, true, false, true, true, true, false},
[]bool{true, true, false, true, true, true, false, true, false, false, false},
[]bool{true, true, false, true, true, true, false, false, false, true, false},
[]bool{true, true, false, true, true, true, false, true, true, true, false},
[]bool{true, true, true, false, true, false, true, true, false, false, false},
[]bool{true, true, true, false, true, false, false, false, true, true, false},
[]bool{true, true, true, false, false, false, true, false, true, true, false},
[]bool{true, true, true, false, true, true, false, true, false, false, false},
[]bool{true, true, true, false, true, true, false, false, false, true, false},
[]bool{true, true, true, false, false, false, true, true, false, true, false},
[]bool{true, true, true, false, true, true, true, true, false, true, false},
[]bool{true, true, false, false, true, false, false, false, false, true, false},
[]bool{true, true, true, true, false, false, false, true, false, true, false},
[]bool{true, false, true, false, false, true, true, false, false, false, false},
[]bool{true, false, true, false, false, false, false, true, true, false, false},
[]bool{true, false, false, true, false, true, true, false, false, false, false},
[]bool{true, false, false, true, false, false, false, false, true, true, false},
[]bool{true, false, false, false, false, true, false, true, true, false, false},
[]bool{true, false, false, false, false, true, false, false, true, true, false},
[]bool{true, false, true, true, false, false, true, false, false, false, false},
[]bool{true, false, true, true, false, false, false, false, true, false, false},
[]bool{true, false, false, true, true, false, true, false, false, false, false},
[]bool{true, false, false, true, true, false, false, false, false, true, false},
[]bool{true, false, false, false, false, true, true, false, true, false, false},
[]bool{true, false, false, false, false, true, true, false, false, true, false},
[]bool{true, true, false, false, false, false, true, false, false, true, false},
[]bool{true, true, false, false, true, false, true, false, false, false, false},
[]bool{true, true, true, true, false, true, true, true, false, true, false},
[]bool{true, true, false, false, false, false, true, false, true, false, false},
[]bool{true, false, false, false, true, true, true, true, false, true, false},
[]bool{true, false, true, false, false, true, true, true, true, false, false},
[]bool{true, false, false, true, false, true, true, true, true, false, false},
[]bool{true, false, false, true, false, false, true, true, true, true, false},
[]bool{true, false, true, true, true, true, false, false, true, false, false},
[]bool{true, false, false, true, true, true, true, false, true, false, false},
[]bool{true, false, false, true, true, true, true, false, false, true, false},
[]bool{true, true, true, true, false, true, false, false, true, false, false},
[]bool{true, true, true, true, false, false, true, false, true, false, false},
[]bool{true, true, true, true, false, false, true, false, false, true, false},
[]bool{true, true, false, true, true, false, true, true, true, true, false},
[]bool{true, true, false, true, true, true, true, false, true, true, false},
[]bool{true, true, true, true, false, true, true, false, true, true, false},
[]bool{true, false, true, false, true, true, true, true, false, false, false},
[]bool{true, false, true, false, false, false, true, true, true, true, false},
[]bool{true, false, false, false, true, false, true, true, true, true, false},
[]bool{true, false, true, true, true, true, false, true, false, false, false},
[]bool{true, false, true, true, true, true, false, false, false, true, false},
[]bool{true, true, true, true, false, true, false, true, false, false, false},
[]bool{true, true, true, true, false, true, false, false, false, true, false},
[]bool{true, false, true, true, true, false, true, true, true, true, false},
[]bool{true, false, true, true, true, true, false, true, true, true, false},
[]bool{true, true, true, false, true, false, true, true, true, true, false},
[]bool{true, true, true, true, false, true, false, true, true, true, false},
[]bool{true, true, false, true, false, false, false, false, true, false, false},
[]bool{true, true, false, true, false, false, true, false, false, false, false},
[]bool{true, true, false, true, false, false, true, true, true, false, false},
[]bool{true, true, false, false, false, true, true, true, false, true, false, true, true},
}
const startASymbol byte = 103
const startBSymbol byte = 104
const startCSymbol byte = 105
const codeASymbol byte = 101
const codeBSymbol byte = 100
const codeCSymbol byte = 99
const stopSymbol byte = 106
const (
// FNC1 - Special Function 1
FNC1 = '\u00f1'
// FNC2 - Special Function 2
FNC2 = '\u00f2'
// FNC3 - Special Function 3
FNC3 = '\u00f3'
// FNC4 - Special Function 4
FNC4 = '\u00f4'
)
const abTable = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
const bTable = abTable + "`abcdefghijklmnopqrstuvwxyz{|}~\u007F"
const aOnlyTable = "\u0000\u0001\u0002\u0003\u0004" + // NUL, SOH, STX, ETX, EOT
"\u0005\u0006\u0007\u0008\u0009" + // ENQ, ACK, BEL, BS, HT
"\u000A\u000B\u000C\u000D\u000E" + // LF, VT, FF, CR, SO
"\u000F\u0010\u0011\u0012\u0013" + // SI, DLE, DC1, DC2, DC3
"\u0014\u0015\u0016\u0017\u0018" + // DC4, NAK, SYN, ETB, CAN
"\u0019\u001A\u001B\u001C\u001D" + // EM, SUB, ESC, FS, GS
"\u001E\u001F" // RS, US
const aTable = abTable + aOnlyTable

152
vendor/github.com/boombuler/barcode/code39/encoder.go generated vendored Normal file
View file

@ -0,0 +1,152 @@
// Package code39 can create Code39 barcodes
package code39
import (
"errors"
"strconv"
"strings"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type encodeInfo struct {
value int
data []bool
}
var encodeTable = map[rune]encodeInfo{
'0': encodeInfo{0, []bool{true, false, true, false, false, true, true, false, true, true, false, true}},
'1': encodeInfo{1, []bool{true, true, false, true, false, false, true, false, true, false, true, true}},
'2': encodeInfo{2, []bool{true, false, true, true, false, false, true, false, true, false, true, true}},
'3': encodeInfo{3, []bool{true, true, false, true, true, false, false, true, false, true, false, true}},
'4': encodeInfo{4, []bool{true, false, true, false, false, true, true, false, true, false, true, true}},
'5': encodeInfo{5, []bool{true, true, false, true, false, false, true, true, false, true, false, true}},
'6': encodeInfo{6, []bool{true, false, true, true, false, false, true, true, false, true, false, true}},
'7': encodeInfo{7, []bool{true, false, true, false, false, true, false, true, true, false, true, true}},
'8': encodeInfo{8, []bool{true, true, false, true, false, false, true, false, true, true, false, true}},
'9': encodeInfo{9, []bool{true, false, true, true, false, false, true, false, true, true, false, true}},
'A': encodeInfo{10, []bool{true, true, false, true, false, true, false, false, true, false, true, true}},
'B': encodeInfo{11, []bool{true, false, true, true, false, true, false, false, true, false, true, true}},
'C': encodeInfo{12, []bool{true, true, false, true, true, false, true, false, false, true, false, true}},
'D': encodeInfo{13, []bool{true, false, true, false, true, true, false, false, true, false, true, true}},
'E': encodeInfo{14, []bool{true, true, false, true, false, true, true, false, false, true, false, true}},
'F': encodeInfo{15, []bool{true, false, true, true, false, true, true, false, false, true, false, true}},
'G': encodeInfo{16, []bool{true, false, true, false, true, false, false, true, true, false, true, true}},
'H': encodeInfo{17, []bool{true, true, false, true, false, true, false, false, true, true, false, true}},
'I': encodeInfo{18, []bool{true, false, true, true, false, true, false, false, true, true, false, true}},
'J': encodeInfo{19, []bool{true, false, true, false, true, true, false, false, true, true, false, true}},
'K': encodeInfo{20, []bool{true, true, false, true, false, true, false, true, false, false, true, true}},
'L': encodeInfo{21, []bool{true, false, true, true, false, true, false, true, false, false, true, true}},
'M': encodeInfo{22, []bool{true, true, false, true, true, false, true, false, true, false, false, true}},
'N': encodeInfo{23, []bool{true, false, true, false, true, true, false, true, false, false, true, true}},
'O': encodeInfo{24, []bool{true, true, false, true, false, true, true, false, true, false, false, true}},
'P': encodeInfo{25, []bool{true, false, true, true, false, true, true, false, true, false, false, true}},
'Q': encodeInfo{26, []bool{true, false, true, false, true, false, true, true, false, false, true, true}},
'R': encodeInfo{27, []bool{true, true, false, true, false, true, false, true, true, false, false, true}},
'S': encodeInfo{28, []bool{true, false, true, true, false, true, false, true, true, false, false, true}},
'T': encodeInfo{29, []bool{true, false, true, false, true, true, false, true, true, false, false, true}},
'U': encodeInfo{30, []bool{true, true, false, false, true, false, true, false, true, false, true, true}},
'V': encodeInfo{31, []bool{true, false, false, true, true, false, true, false, true, false, true, true}},
'W': encodeInfo{32, []bool{true, true, false, false, true, true, false, true, false, true, false, true}},
'X': encodeInfo{33, []bool{true, false, false, true, false, true, true, false, true, false, true, true}},
'Y': encodeInfo{34, []bool{true, true, false, false, true, false, true, true, false, true, false, true}},
'Z': encodeInfo{35, []bool{true, false, false, true, true, false, true, true, false, true, false, true}},
'-': encodeInfo{36, []bool{true, false, false, true, false, true, false, true, true, false, true, true}},
'.': encodeInfo{37, []bool{true, true, false, false, true, false, true, false, true, true, false, true}},
' ': encodeInfo{38, []bool{true, false, false, true, true, false, true, false, true, true, false, true}},
'$': encodeInfo{39, []bool{true, false, false, true, false, false, true, false, false, true, false, true}},
'/': encodeInfo{40, []bool{true, false, false, true, false, false, true, false, true, false, false, true}},
'+': encodeInfo{41, []bool{true, false, false, true, false, true, false, false, true, false, false, true}},
'%': encodeInfo{42, []bool{true, false, true, false, false, true, false, false, true, false, false, true}},
'*': encodeInfo{-1, []bool{true, false, false, true, false, true, true, false, true, true, false, true}},
}
var extendedTable = map[rune]string{
0: `%U`, 1: `$A`, 2: `$B`, 3: `$C`, 4: `$D`, 5: `$E`, 6: `$F`, 7: `$G`, 8: `$H`, 9: `$I`, 10: `$J`,
11: `$K`, 12: `$L`, 13: `$M`, 14: `$N`, 15: `$O`, 16: `$P`, 17: `$Q`, 18: `$R`, 19: `$S`, 20: `$T`,
21: `$U`, 22: `$V`, 23: `$W`, 24: `$X`, 25: `$Y`, 26: `$Z`, 27: `%A`, 28: `%B`, 29: `%C`, 30: `%D`,
31: `%E`, 33: `/A`, 34: `/B`, 35: `/C`, 36: `/D`, 37: `/E`, 38: `/F`, 39: `/G`, 40: `/H`, 41: `/I`,
42: `/J`, 43: `/K`, 44: `/L`, 47: `/O`, 58: `/Z`, 59: `%F`, 60: `%G`, 61: `%H`, 62: `%I`, 63: `%J`,
64: `%V`, 91: `%K`, 92: `%L`, 93: `%M`, 94: `%N`, 95: `%O`, 96: `%W`, 97: `+A`, 98: `+B`, 99: `+C`,
100: `+D`, 101: `+E`, 102: `+F`, 103: `+G`, 104: `+H`, 105: `+I`, 106: `+J`, 107: `+K`, 108: `+L`,
109: `+M`, 110: `+N`, 111: `+O`, 112: `+P`, 113: `+Q`, 114: `+R`, 115: `+S`, 116: `+T`, 117: `+U`,
118: `+V`, 119: `+W`, 120: `+X`, 121: `+Y`, 122: `+Z`, 123: `%P`, 124: `%Q`, 125: `%R`, 126: `%S`,
127: `%T`,
}
func getChecksum(content string) string {
sum := 0
for _, r := range content {
info, ok := encodeTable[r]
if !ok || info.value < 0 {
return "#"
}
sum += info.value
}
sum = sum % 43
for r, v := range encodeTable {
if v.value == sum {
return string(r)
}
}
return "#"
}
func prepare(content string) (string, error) {
result := ""
for _, r := range content {
if r > 127 {
return "", errors.New("Only ASCII strings can be encoded")
}
val, ok := extendedTable[r]
if ok {
result += val
} else {
result += string([]rune{r})
}
}
return result, nil
}
// Encode returns a code39 barcode for the given content
// if includeChecksum is set to true, a checksum character is calculated and added to the content
func Encode(content string, includeChecksum bool, fullASCIIMode bool) (barcode.BarcodeIntCS, error) {
if fullASCIIMode {
var err error
content, err = prepare(content)
if err != nil {
return nil, err
}
} else if strings.ContainsRune(content, '*') {
return nil, errors.New("invalid data! try full ascii mode")
}
data := "*" + content
if includeChecksum {
data += getChecksum(content)
}
data += "*"
result := new(utils.BitList)
for i, r := range data {
if i != 0 {
result.AddBit(false)
}
info, ok := encodeTable[r]
if !ok {
return nil, errors.New("invalid data! try full ascii mode")
}
result.AddBit(info.data...)
}
checkSum, err := strconv.ParseInt(getChecksum(content), 10, 64)
if err != nil {
checkSum = 0
}
return utils.New1DCodeIntCheckSum(barcode.TypeCode39, content, result, int(checkSum)), nil
}

View file

@ -0,0 +1,31 @@
package code39
import (
"image/color"
"testing"
)
func doTest(t *testing.T, addCS, fullASCII bool, data, testResult string) {
code, err := Encode(data, addCS, fullASCII)
if err != nil {
t.Error(err)
}
if len(testResult) != code.Bounds().Max.X {
t.Errorf("Invalid code size. Expected %d got %d", len(testResult), code.Bounds().Max.X)
}
for i, r := range testResult {
if (code.At(i, 0) == color.Black) != (r == '1') {
t.Errorf("Failed at position %d", i)
}
}
}
func Test_Encode(t *testing.T) {
doTest(t, false, false, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
"1001011011010110101001011010110100101101101101001010101011001011011010110010101"+
"011011001010101010011011011010100110101011010011010101011001101011010101001101011010"+
"100110110110101001010101101001101101011010010101101101001010101011001101101010110010"+
"101101011001010101101100101100101010110100110101011011001101010101001011010110110010"+
"110101010011011010101010011011010110100101011010110010101101101100101010101001101011"+
"011010011010101011001101010101001011011011010010110101011001011010100101101101")
}

131
vendor/github.com/boombuler/barcode/code93/encoder.go generated vendored Normal file
View file

@ -0,0 +1,131 @@
// Package code93 can create Code93 barcodes
package code93
import (
"errors"
"strings"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type encodeInfo struct {
value int
data int
}
const (
// Special Function 1 ($)
FNC1 = '\u00f1'
// Special Function 2 (%)
FNC2 = '\u00f2'
// Special Function 3 (/)
FNC3 = '\u00f3'
// Special Function 4 (+)
FNC4 = '\u00f4'
)
var encodeTable = map[rune]encodeInfo{
'0': encodeInfo{0, 0x114}, '1': encodeInfo{1, 0x148}, '2': encodeInfo{2, 0x144},
'3': encodeInfo{3, 0x142}, '4': encodeInfo{4, 0x128}, '5': encodeInfo{5, 0x124},
'6': encodeInfo{6, 0x122}, '7': encodeInfo{7, 0x150}, '8': encodeInfo{8, 0x112},
'9': encodeInfo{9, 0x10A}, 'A': encodeInfo{10, 0x1A8}, 'B': encodeInfo{11, 0x1A4},
'C': encodeInfo{12, 0x1A2}, 'D': encodeInfo{13, 0x194}, 'E': encodeInfo{14, 0x192},
'F': encodeInfo{15, 0x18A}, 'G': encodeInfo{16, 0x168}, 'H': encodeInfo{17, 0x164},
'I': encodeInfo{18, 0x162}, 'J': encodeInfo{19, 0x134}, 'K': encodeInfo{20, 0x11A},
'L': encodeInfo{21, 0x158}, 'M': encodeInfo{22, 0x14C}, 'N': encodeInfo{23, 0x146},
'O': encodeInfo{24, 0x12C}, 'P': encodeInfo{25, 0x116}, 'Q': encodeInfo{26, 0x1B4},
'R': encodeInfo{27, 0x1B2}, 'S': encodeInfo{28, 0x1AC}, 'T': encodeInfo{29, 0x1A6},
'U': encodeInfo{30, 0x196}, 'V': encodeInfo{31, 0x19A}, 'W': encodeInfo{32, 0x16C},
'X': encodeInfo{33, 0x166}, 'Y': encodeInfo{34, 0x136}, 'Z': encodeInfo{35, 0x13A},
'-': encodeInfo{36, 0x12E}, '.': encodeInfo{37, 0x1D4}, ' ': encodeInfo{38, 0x1D2},
'$': encodeInfo{39, 0x1CA}, '/': encodeInfo{40, 0x16E}, '+': encodeInfo{41, 0x176},
'%': encodeInfo{42, 0x1AE}, FNC1: encodeInfo{43, 0x126}, FNC2: encodeInfo{44, 0x1DA},
FNC3: encodeInfo{45, 0x1D6}, FNC4: encodeInfo{46, 0x132}, '*': encodeInfo{47, 0x15E},
}
var extendedTable = []string{
"\u00f2U", "\u00f1A", "\u00f1B", "\u00f1C", "\u00f1D", "\u00f1E", "\u00f1F", "\u00f1G",
"\u00f1H", "\u00f1I", "\u00f1J", "\u00f1K", "\u00f1L", "\u00f1M", "\u00f1N", "\u00f1O",
"\u00f1P", "\u00f1Q", "\u00f1R", "\u00f1S", "\u00f1T", "\u00f1U", "\u00f1V", "\u00f1W",
"\u00f1X", "\u00f1Y", "\u00f1Z", "\u00f2A", "\u00f2B", "\u00f2C", "\u00f2D", "\u00f2E",
" ", "\u00f3A", "\u00f3B", "\u00f3C", "\u00f3D", "\u00f3E", "\u00f3F", "\u00f3G",
"\u00f3H", "\u00f3I", "\u00f3J", "\u00f3K", "\u00f3L", "-", ".", "\u00f3O",
"0", "1", "2", "3", "4", "5", "6", "7",
"8", "9", "\u00f3Z", "\u00f2F", "\u00f2G", "\u00f2H", "\u00f2I", "\u00f2J",
"\u00f2V", "A", "B", "C", "D", "E", "F", "G",
"H", "I", "J", "K", "L", "M", "N", "O",
"P", "Q", "R", "S", "T", "U", "V", "W",
"X", "Y", "Z", "\u00f2K", "\u00f2L", "\u00f2M", "\u00f2N", "\u00f2O",
"\u00f2W", "\u00f4A", "\u00f4B", "\u00f4C", "\u00f4D", "\u00f4E", "\u00f4F", "\u00f4G",
"\u00f4H", "\u00f4I", "\u00f4J", "\u00f4K", "\u00f4L", "\u00f4M", "\u00f4N", "\u00f4O",
"\u00f4P", "\u00f4Q", "\u00f4R", "\u00f4S", "\u00f4T", "\u00f4U", "\u00f4V", "\u00f4W",
"\u00f4X", "\u00f4Y", "\u00f4Z", "\u00f2P", "\u00f2Q", "\u00f2R", "\u00f2S", "\u00f2T",
}
func prepare(content string) (string, error) {
result := ""
for _, r := range content {
if r > 127 {
return "", errors.New("Only ASCII strings can be encoded")
}
result += extendedTable[int(r)]
}
return result, nil
}
// Encode returns a code93 barcode for the given content
// if includeChecksum is set to true, two checksum characters are calculated and added to the content
func Encode(content string, includeChecksum bool, fullASCIIMode bool) (barcode.Barcode, error) {
if fullASCIIMode {
var err error
content, err = prepare(content)
if err != nil {
return nil, err
}
} else if strings.ContainsRune(content, '*') {
return nil, errors.New("invalid data! content may not contain '*'")
}
data := content + string(getChecksum(content, 20))
data += string(getChecksum(data, 15))
data = "*" + data + "*"
result := new(utils.BitList)
for _, r := range data {
info, ok := encodeTable[r]
if !ok {
return nil, errors.New("invalid data!")
}
result.AddBits(info.data, 9)
}
result.AddBit(true)
return utils.New1DCode(barcode.TypeCode93, content, result), nil
}
func getChecksum(content string, maxWeight int) rune {
weight := 1
total := 0
data := []rune(content)
for i := len(data) - 1; i >= 0; i-- {
r := data[i]
info, ok := encodeTable[r]
if !ok {
return ' '
}
total += info.value * weight
if weight++; weight > maxWeight {
weight = 1
}
}
total = total % 47
for r, info := range encodeTable {
if info.value == total {
return r
}
}
return ' '
}

View file

@ -0,0 +1,39 @@
package code93
import (
"image/color"
"testing"
)
func doTest(t *testing.T, data, testResult string) {
code, err := Encode(data, true, false)
if err != nil {
t.Error(err)
}
if len(testResult) != code.Bounds().Max.X {
t.Errorf("Invalid code size. Expected %d got %d", len(testResult), code.Bounds().Max.X)
}
for i, r := range testResult {
if (code.At(i, 0) == color.Black) != (r == '1') {
t.Errorf("Failed at position %d", i)
}
}
}
func Test_CheckSum(t *testing.T) {
if r := getChecksum("TEST93", 20); r != '+' {
t.Errorf("Checksum C-Failed. Got %s", string(r))
}
if r := getChecksum("TEST93+", 15); r != '6' {
t.Errorf("Checksum K-Failed. Got %s", string(r))
}
}
func Test_Encode(t *testing.T) {
doTest(t, "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",
"1010111101101010001101001001101000101100101001100100101100010101011010001011001"+
"001011000101001101001000110101010110001010011001010001101001011001000101101101101001"+
"101100101101011001101001101100101101100110101011011001011001101001101101001110101000"+
"101001010010001010001001010000101001010001001001001001000101010100001000100101000010"+
"101001110101010000101010111101")
}

View file

@ -0,0 +1,213 @@
package datamatrix
import (
"github.com/boombuler/barcode/utils"
"strconv"
)
type setValFunc func(byte)
type codeLayout struct {
matrix *utils.BitList
occupy *utils.BitList
size *dmCodeSize
}
func newCodeLayout(size *dmCodeSize) *codeLayout {
result := new(codeLayout)
result.matrix = utils.NewBitList(size.MatrixColumns() * size.MatrixRows())
result.occupy = utils.NewBitList(size.MatrixColumns() * size.MatrixRows())
result.size = size
return result
}
func (l *codeLayout) Occupied(row, col int) bool {
return l.occupy.GetBit(col + row*l.size.MatrixColumns())
}
func (l *codeLayout) Set(row, col int, value, bitNum byte) {
val := ((value >> (7 - bitNum)) & 1) == 1
if row < 0 {
row += l.size.MatrixRows()
col += 4 - ((l.size.MatrixRows() + 4) % 8)
}
if col < 0 {
col += l.size.MatrixColumns()
row += 4 - ((l.size.MatrixColumns() + 4) % 8)
}
if l.Occupied(row, col) {
panic("Field already occupied row: " + strconv.Itoa(row) + " col: " + strconv.Itoa(col))
}
l.occupy.SetBit(col+row*l.size.MatrixColumns(), true)
l.matrix.SetBit(col+row*l.size.MatrixColumns(), val)
}
func (l *codeLayout) SetSimple(row, col int, value byte) {
l.Set(row-2, col-2, value, 0)
l.Set(row-2, col-1, value, 1)
l.Set(row-1, col-2, value, 2)
l.Set(row-1, col-1, value, 3)
l.Set(row-1, col-0, value, 4)
l.Set(row-0, col-2, value, 5)
l.Set(row-0, col-1, value, 6)
l.Set(row-0, col-0, value, 7)
}
func (l *codeLayout) Corner1(value byte) {
l.Set(l.size.MatrixRows()-1, 0, value, 0)
l.Set(l.size.MatrixRows()-1, 1, value, 1)
l.Set(l.size.MatrixRows()-1, 2, value, 2)
l.Set(0, l.size.MatrixColumns()-2, value, 3)
l.Set(0, l.size.MatrixColumns()-1, value, 4)
l.Set(1, l.size.MatrixColumns()-1, value, 5)
l.Set(2, l.size.MatrixColumns()-1, value, 6)
l.Set(3, l.size.MatrixColumns()-1, value, 7)
}
func (l *codeLayout) Corner2(value byte) {
l.Set(l.size.MatrixRows()-3, 0, value, 0)
l.Set(l.size.MatrixRows()-2, 0, value, 1)
l.Set(l.size.MatrixRows()-1, 0, value, 2)
l.Set(0, l.size.MatrixColumns()-4, value, 3)
l.Set(0, l.size.MatrixColumns()-3, value, 4)
l.Set(0, l.size.MatrixColumns()-2, value, 5)
l.Set(0, l.size.MatrixColumns()-1, value, 6)
l.Set(1, l.size.MatrixColumns()-1, value, 7)
}
func (l *codeLayout) Corner3(value byte) {
l.Set(l.size.MatrixRows()-3, 0, value, 0)
l.Set(l.size.MatrixRows()-2, 0, value, 1)
l.Set(l.size.MatrixRows()-1, 0, value, 2)
l.Set(0, l.size.MatrixColumns()-2, value, 3)
l.Set(0, l.size.MatrixColumns()-1, value, 4)
l.Set(1, l.size.MatrixColumns()-1, value, 5)
l.Set(2, l.size.MatrixColumns()-1, value, 6)
l.Set(3, l.size.MatrixColumns()-1, value, 7)
}
func (l *codeLayout) Corner4(value byte) {
l.Set(l.size.MatrixRows()-1, 0, value, 0)
l.Set(l.size.MatrixRows()-1, l.size.MatrixColumns()-1, value, 1)
l.Set(0, l.size.MatrixColumns()-3, value, 2)
l.Set(0, l.size.MatrixColumns()-2, value, 3)
l.Set(0, l.size.MatrixColumns()-1, value, 4)
l.Set(1, l.size.MatrixColumns()-3, value, 5)
l.Set(1, l.size.MatrixColumns()-2, value, 6)
l.Set(1, l.size.MatrixColumns()-1, value, 7)
}
func (l *codeLayout) SetValues(data []byte) {
idx := 0
row := 4
col := 0
for (row < l.size.MatrixRows()) || (col < l.size.MatrixColumns()) {
if (row == l.size.MatrixRows()) && (col == 0) {
l.Corner1(data[idx])
idx++
}
if (row == l.size.MatrixRows()-2) && (col == 0) && (l.size.MatrixColumns()%4 != 0) {
l.Corner2(data[idx])
idx++
}
if (row == l.size.MatrixRows()-2) && (col == 0) && (l.size.MatrixColumns()%8 == 4) {
l.Corner3(data[idx])
idx++
}
if (row == l.size.MatrixRows()+4) && (col == 2) && (l.size.MatrixColumns()%8 == 0) {
l.Corner4(data[idx])
idx++
}
for true {
if (row < l.size.MatrixRows()) && (col >= 0) && !l.Occupied(row, col) {
l.SetSimple(row, col, data[idx])
idx++
}
row -= 2
col += 2
if (row < 0) || (col >= l.size.MatrixColumns()) {
break
}
}
row += 1
col += 3
for true {
if (row >= 0) && (col < l.size.MatrixColumns()) && !l.Occupied(row, col) {
l.SetSimple(row, col, data[idx])
idx++
}
row += 2
col -= 2
if (row >= l.size.MatrixRows()) || (col < 0) {
break
}
}
row += 3
col += 1
}
if !l.Occupied(l.size.MatrixRows()-1, l.size.MatrixColumns()-1) {
l.Set(l.size.MatrixRows()-1, l.size.MatrixColumns()-1, 255, 0)
l.Set(l.size.MatrixRows()-2, l.size.MatrixColumns()-2, 255, 0)
}
}
func (l *codeLayout) Merge() *datamatrixCode {
result := newDataMatrixCode(l.size)
//dotted horizontal lines
for r := 0; r < l.size.Rows; r += (l.size.RegionRows() + 2) {
for c := 0; c < l.size.Columns; c += 2 {
result.set(c, r, true)
}
}
//solid horizontal line
for r := l.size.RegionRows() + 1; r < l.size.Rows; r += (l.size.RegionRows() + 2) {
for c := 0; c < l.size.Columns; c++ {
result.set(c, r, true)
}
}
//dotted vertical lines
for c := l.size.RegionColumns() + 1; c < l.size.Columns; c += (l.size.RegionColumns() + 2) {
for r := 1; r < l.size.Rows; r += 2 {
result.set(c, r, true)
}
}
//solid vertical line
for c := 0; c < l.size.Columns; c += (l.size.RegionColumns() + 2) {
for r := 0; r < l.size.Rows; r++ {
result.set(c, r, true)
}
}
count := 0
for hRegion := 0; hRegion < l.size.RegionCountHorizontal; hRegion++ {
for vRegion := 0; vRegion < l.size.RegionCountVertical; vRegion++ {
for x := 0; x < l.size.RegionColumns(); x++ {
colMatrix := (l.size.RegionColumns() * hRegion) + x
colResult := ((2 + l.size.RegionColumns()) * hRegion) + x + 1
for y := 0; y < l.size.RegionRows(); y++ {
rowMatrix := (l.size.RegionRows() * vRegion) + y
rowResult := ((2 + l.size.RegionRows()) * vRegion) + y + 1
val := l.matrix.GetBit(colMatrix + rowMatrix*l.size.MatrixColumns())
if val {
count++
}
result.set(colResult, rowResult, val)
}
}
}
}
return result
}

View file

@ -0,0 +1,73 @@
package datamatrix
type dmCodeSize struct {
Rows int
Columns int
RegionCountHorizontal int
RegionCountVertical int
ECCCount int
BlockCount int
}
func (s *dmCodeSize) RegionRows() int {
return (s.Rows - (s.RegionCountHorizontal * 2)) / s.RegionCountHorizontal
}
func (s *dmCodeSize) RegionColumns() int {
return (s.Columns - (s.RegionCountVertical * 2)) / s.RegionCountVertical
}
func (s *dmCodeSize) MatrixRows() int {
return s.RegionRows() * s.RegionCountHorizontal
}
func (s *dmCodeSize) MatrixColumns() int {
return s.RegionColumns() * s.RegionCountVertical
}
func (s *dmCodeSize) DataCodewords() int {
return ((s.MatrixColumns() * s.MatrixRows()) / 8) - s.ECCCount
}
func (s *dmCodeSize) DataCodewordsForBlock(idx int) int {
if s.Rows == 144 && s.Columns == 144 {
// Special Case...
if idx < 8 {
return 156
} else {
return 155
}
}
return s.DataCodewords() / s.BlockCount
}
func (s *dmCodeSize) ErrorCorrectionCodewordsPerBlock() int {
return s.ECCCount / s.BlockCount
}
var codeSizes []*dmCodeSize = []*dmCodeSize{
&dmCodeSize{10, 10, 1, 1, 5, 1},
&dmCodeSize{12, 12, 1, 1, 7, 1},
&dmCodeSize{14, 14, 1, 1, 10, 1},
&dmCodeSize{16, 16, 1, 1, 12, 1},
&dmCodeSize{18, 18, 1, 1, 14, 1},
&dmCodeSize{20, 20, 1, 1, 18, 1},
&dmCodeSize{22, 22, 1, 1, 20, 1},
&dmCodeSize{24, 24, 1, 1, 24, 1},
&dmCodeSize{26, 26, 1, 1, 28, 1},
&dmCodeSize{32, 32, 2, 2, 36, 1},
&dmCodeSize{36, 36, 2, 2, 42, 1},
&dmCodeSize{40, 40, 2, 2, 48, 1},
&dmCodeSize{44, 44, 2, 2, 56, 1},
&dmCodeSize{48, 48, 2, 2, 68, 1},
&dmCodeSize{52, 52, 2, 2, 84, 2},
&dmCodeSize{64, 64, 4, 4, 112, 2},
&dmCodeSize{72, 72, 4, 4, 144, 4},
&dmCodeSize{80, 80, 4, 4, 192, 4},
&dmCodeSize{88, 88, 4, 4, 224, 4},
&dmCodeSize{96, 96, 4, 4, 272, 4},
&dmCodeSize{104, 104, 4, 4, 336, 6},
&dmCodeSize{120, 120, 6, 6, 408, 6},
&dmCodeSize{132, 132, 6, 6, 496, 8},
&dmCodeSize{144, 144, 6, 6, 620, 10},
}

View file

@ -0,0 +1,102 @@
package datamatrix
import (
"bytes"
"testing"
)
func codeFromStr(str string, size *dmCodeSize) *datamatrixCode {
code := newDataMatrixCode(size)
idx := 0
for _, r := range str {
x := idx % size.Columns
y := idx / size.Columns
switch r {
case '#':
code.set(x, y, true)
case '.':
code.set(x, y, false)
default:
continue
}
idx++
}
return code
}
func Test_Issue12(t *testing.T) {
data := `{"po":12,"batchAction":"start_end"}`
realData := addPadding(encodeText(data), 36)
wantedData := []byte{124, 35, 113, 112, 35, 59, 142, 45, 35, 99, 98, 117, 100, 105, 66, 100, 117, 106, 112, 111, 35, 59, 35, 116, 117, 98, 115, 117, 96, 102, 111, 101, 35, 126, 129, 181}
if bytes.Compare(realData, wantedData) != 0 {
t.Error("Data Encoding failed")
return
}
var codeSize *dmCodeSize
for _, s := range codeSizes {
if s.DataCodewords() >= len(wantedData) {
codeSize = s
break
}
}
realECC := ec.calcECC(realData, codeSize)[len(realData):]
wantedECC := []byte{196, 53, 147, 192, 151, 213, 107, 61, 98, 251, 50, 71, 186, 15, 43, 111, 165, 243, 209, 79, 128, 109, 251, 4}
if bytes.Compare(realECC, wantedECC) != 0 {
t.Errorf("Error correction calculation failed\nGot: %v", realECC)
return
}
barcode := `
#.#.#.#.#.#.#.#.#.#.#.#.
#....###..#..#....#...##
##.......#...#.#.#....#.
#.###...##..#...##.##..#
##...####..##..#.#.#.##.
#.###.##.###..#######.##
#..###...##.##..#.##.##.
#.#.#.#.#.#.###....#.#.#
##.#...#.#.#..#...#####.
#...####..#...##..#.#..#
##...#...##.###.#.....#.
#.###.#.##.#.....###..##
##..#####...#..##...###.
###...#.####.##.#.#.#..#
#..###..#.#.####.#.###..
###.#.#..#..#.###.#.##.#
#####.##.###..#.####.#..
#.##.#......#.#..#.#.###
###.#....######.#...##..
##...#..##.###..#...####
#.######.###.##..#...##.
#..#..#.##.#..####...#.#
###.###..#..##.#.##...#.
########################`
bc, err := Encode(data)
if err != nil {
t.Error(err)
return
}
realResult := bc.(*datamatrixCode)
if realResult.Columns != 24 || realResult.Rows != 24 {
t.Errorf("Got wrong barcode size %dx%d", realResult.Columns, realResult.Rows)
return
}
wantedResult := codeFromStr(barcode, realResult.dmCodeSize)
for x := 0; x < wantedResult.Columns; x++ {
for y := 0; y < wantedResult.Rows; y++ {
r := realResult.get(x, y)
w := wantedResult.get(x, y)
if w != r {
t.Errorf("Failed at: c%d/r%d", x, y)
}
}
}
}

View file

@ -0,0 +1,50 @@
package datamatrix
import (
"image"
"image/color"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type datamatrixCode struct {
*utils.BitList
*dmCodeSize
content string
}
func newDataMatrixCode(size *dmCodeSize) *datamatrixCode {
return &datamatrixCode{utils.NewBitList(size.Rows * size.Columns), size, ""}
}
func (c *datamatrixCode) Content() string {
return c.content
}
func (c *datamatrixCode) Metadata() barcode.Metadata {
return barcode.Metadata{barcode.TypeDataMatrix, 2}
}
func (c *datamatrixCode) ColorModel() color.Model {
return color.Gray16Model
}
func (c *datamatrixCode) Bounds() image.Rectangle {
return image.Rect(0, 0, c.Columns, c.Rows)
}
func (c *datamatrixCode) At(x, y int) color.Color {
if c.get(x, y) {
return color.Black
}
return color.White
}
func (c *datamatrixCode) get(x, y int) bool {
return c.GetBit(x*c.Rows + y)
}
func (c *datamatrixCode) set(x, y int, value bool) {
c.SetBit(x*c.Rows+y, value)
}

View file

@ -0,0 +1,75 @@
// Package datamatrix can create Datamatrix barcodes
package datamatrix
import (
"errors"
"github.com/boombuler/barcode"
)
// Encode returns a Datamatrix barcode for the given content
func Encode(content string) (barcode.Barcode, error) {
data := encodeText(content)
var size *dmCodeSize
for _, s := range codeSizes {
if s.DataCodewords() >= len(data) {
size = s
break
}
}
if size == nil {
return nil, errors.New("to much data to encode")
}
data = addPadding(data, size.DataCodewords())
data = ec.calcECC(data, size)
code := render(data, size)
if code != nil {
code.content = content
return code, nil
}
return nil, errors.New("unable to render barcode")
}
func render(data []byte, size *dmCodeSize) *datamatrixCode {
cl := newCodeLayout(size)
cl.SetValues(data)
return cl.Merge()
}
func encodeText(content string) []byte {
var result []byte
input := []byte(content)
for i := 0; i < len(input); {
c := input[i]
i++
if c >= '0' && c <= '9' && i < len(input) && input[i] >= '0' && input[i] <= '9' {
// two numbers...
c2 := input[i]
i++
cw := byte(((c-'0')*10 + (c2 - '0')) + 130)
result = append(result, cw)
} else if c > 127 {
// not correct... needs to be redone later...
result = append(result, 235, c-127)
} else {
result = append(result, c+1)
}
}
return result
}
func addPadding(data []byte, toCount int) []byte {
if len(data) < toCount {
data = append(data, 129)
}
for len(data) < toCount {
R := ((149 * (len(data) + 1)) % 253) + 1
data = append(data, byte((129+R)%254))
}
return data
}

View file

@ -0,0 +1,45 @@
package datamatrix
import (
"github.com/boombuler/barcode/utils"
)
type errorCorrection struct {
rs *utils.ReedSolomonEncoder
}
var ec *errorCorrection = newErrorCorrection()
func newErrorCorrection() *errorCorrection {
gf := utils.NewGaloisField(301, 256, 1)
return &errorCorrection{utils.NewReedSolomonEncoder(gf)}
}
func (ec *errorCorrection) calcECC(data []byte, size *dmCodeSize) []byte {
dataSize := len(data)
// make some space for error correction codes
data = append(data, make([]byte, size.ECCCount)...)
for block := 0; block < size.BlockCount; block++ {
dataCnt := size.DataCodewordsForBlock(block)
buff := make([]int, dataCnt)
// copy the data for the current block to buff
j := 0
for i := block; i < dataSize; i += size.BlockCount {
buff[j] = int(data[i])
j++
}
// calc the error correction codes
ecc := ec.rs.Encode(buff, size.ErrorCorrectionCodewordsPerBlock())
// and append them to the result
j = 0
for i := block; i < size.ErrorCorrectionCodewordsPerBlock()*size.BlockCount; i += size.BlockCount {
data[dataSize+i] = byte(ecc[j])
j++
}
}
return data
}

View file

@ -0,0 +1,28 @@
package datamatrix
import (
"bytes"
"testing"
)
func Test_CalcECC(t *testing.T) {
data := []byte{142, 164, 186}
var size *dmCodeSize = nil
for _, s := range codeSizes {
if s.DataCodewords() >= len(data) {
size = s
break
}
}
if size == nil {
t.Error("size not found")
}
if bytes.Compare(ec.calcECC(data, size), []byte{142, 164, 186, 114, 25, 5, 88, 102}) != 0 {
t.Error("ECC Test 1 failed")
}
data = []byte{66, 129, 70}
if bytes.Compare(ec.calcECC(data, size), []byte{66, 129, 70, 138, 234, 82, 82, 95}) != 0 {
t.Error("ECC Test 2 failed")
}
}

187
vendor/github.com/boombuler/barcode/ean/encoder.go generated vendored Normal file
View file

@ -0,0 +1,187 @@
// Package ean can create EAN 8 and EAN 13 barcodes.
package ean
import (
"errors"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type encodedNumber struct {
LeftOdd []bool
LeftEven []bool
Right []bool
CheckSum []bool
}
var encoderTable = map[rune]encodedNumber{
'0': encodedNumber{
[]bool{false, false, false, true, true, false, true},
[]bool{false, true, false, false, true, true, true},
[]bool{true, true, true, false, false, true, false},
[]bool{false, false, false, false, false, false},
},
'1': encodedNumber{
[]bool{false, false, true, true, false, false, true},
[]bool{false, true, true, false, false, true, true},
[]bool{true, true, false, false, true, true, false},
[]bool{false, false, true, false, true, true},
},
'2': encodedNumber{
[]bool{false, false, true, false, false, true, true},
[]bool{false, false, true, true, false, true, true},
[]bool{true, true, false, true, true, false, false},
[]bool{false, false, true, true, false, true},
},
'3': encodedNumber{
[]bool{false, true, true, true, true, false, true},
[]bool{false, true, false, false, false, false, true},
[]bool{true, false, false, false, false, true, false},
[]bool{false, false, true, true, true, false},
},
'4': encodedNumber{
[]bool{false, true, false, false, false, true, true},
[]bool{false, false, true, true, true, false, true},
[]bool{true, false, true, true, true, false, false},
[]bool{false, true, false, false, true, true},
},
'5': encodedNumber{
[]bool{false, true, true, false, false, false, true},
[]bool{false, true, true, true, false, false, true},
[]bool{true, false, false, true, true, true, false},
[]bool{false, true, true, false, false, true},
},
'6': encodedNumber{
[]bool{false, true, false, true, true, true, true},
[]bool{false, false, false, false, true, false, true},
[]bool{true, false, true, false, false, false, false},
[]bool{false, true, true, true, false, false},
},
'7': encodedNumber{
[]bool{false, true, true, true, false, true, true},
[]bool{false, false, true, false, false, false, true},
[]bool{true, false, false, false, true, false, false},
[]bool{false, true, false, true, false, true},
},
'8': encodedNumber{
[]bool{false, true, true, false, true, true, true},
[]bool{false, false, false, true, false, false, true},
[]bool{true, false, false, true, false, false, false},
[]bool{false, true, false, true, true, false},
},
'9': encodedNumber{
[]bool{false, false, false, true, false, true, true},
[]bool{false, false, true, false, true, true, true},
[]bool{true, true, true, false, true, false, false},
[]bool{false, true, true, false, true, false},
},
}
func calcCheckNum(code string) rune {
x3 := len(code) == 7
sum := 0
for _, r := range code {
curNum := utils.RuneToInt(r)
if curNum < 0 || curNum > 9 {
return 'B'
}
if x3 {
curNum = curNum * 3
}
x3 = !x3
sum += curNum
}
return utils.IntToRune((10 - (sum % 10)) % 10)
}
func encodeEAN8(code string) *utils.BitList {
result := new(utils.BitList)
result.AddBit(true, false, true)
for cpos, r := range code {
num, ok := encoderTable[r]
if !ok {
return nil
}
var data []bool
if cpos < 4 {
data = num.LeftOdd
} else {
data = num.Right
}
if cpos == 4 {
result.AddBit(false, true, false, true, false)
}
result.AddBit(data...)
}
result.AddBit(true, false, true)
return result
}
func encodeEAN13(code string) *utils.BitList {
result := new(utils.BitList)
result.AddBit(true, false, true)
var firstNum []bool
for cpos, r := range code {
num, ok := encoderTable[r]
if !ok {
return nil
}
if cpos == 0 {
firstNum = num.CheckSum
continue
}
var data []bool
if cpos < 7 { // Left
if firstNum[cpos-1] {
data = num.LeftEven
} else {
data = num.LeftOdd
}
} else {
data = num.Right
}
if cpos == 7 {
result.AddBit(false, true, false, true, false)
}
result.AddBit(data...)
}
result.AddBit(true, false, true)
return result
}
// Encode returns a EAN 8 or EAN 13 barcode for the given code
func Encode(code string) (barcode.BarcodeIntCS, error) {
var checkSum int
if len(code) == 7 || len(code) == 12 {
code += string(calcCheckNum(code))
checkSum = utils.RuneToInt(calcCheckNum(code))
} else if len(code) == 8 || len(code) == 13 {
check := code[0 : len(code)-1]
check += string(calcCheckNum(check))
if check != code {
return nil, errors.New("checksum missmatch")
}
checkSum = utils.RuneToInt(rune(code[len(code)-1]))
}
if len(code) == 8 {
result := encodeEAN8(code)
if result != nil {
return utils.New1DCodeIntCheckSum(barcode.TypeEAN8, code, result, checkSum), nil
}
} else if len(code) == 13 {
result := encodeEAN13(code)
if result != nil {
return utils.New1DCodeIntCheckSum(barcode.TypeEAN13, code, result, checkSum), nil
}
}
return nil, errors.New("invalid ean code data")
}

View file

@ -0,0 +1,46 @@
package ean
import (
"image/color"
"testing"
)
func testHelper(t *testing.T, testCode, testResult, kind string, checkMetadata bool) {
code, err := Encode(testCode)
if err != nil {
t.Error(err)
}
if checkMetadata && (code.Metadata().Dimensions != 1 || code.Content() != testCode || code.Metadata().CodeKind != kind) {
t.Error("Metadata missmatch")
}
if len(testResult) != code.Bounds().Max.X {
t.Fail()
}
for i, r := range testResult {
if (code.At(i, 0) == color.Black) != (r == '1') {
t.Fail()
}
}
}
func Test_EncodeEAN(t *testing.T) {
testHelper(t, "5901234123457", "10100010110100111011001100100110111101001110101010110011011011001000010101110010011101000100101", "EAN 13", true)
testHelper(t, "55123457", "1010110001011000100110010010011010101000010101110010011101000100101", "EAN 8", true)
testHelper(t, "5512345", "1010110001011000100110010010011010101000010101110010011101000100101", "EAN 8", false)
_, err := Encode("55123458") //<-- Invalid checksum
if err == nil {
t.Error("Invalid checksum not detected")
}
_, err = Encode("invalid")
if err == nil {
t.Error("\"invalid\" should not be encodable")
}
_, err = Encode("invalid")
if err == nil {
t.Error("\"invalid\" should not be encodable")
}
bits := encodeEAN13("invalid error")
if bits != nil {
t.Error("\"invalid error\" should not be encodable")
}
}

416
vendor/github.com/boombuler/barcode/pdf417/codewords.go generated vendored Normal file
View file

@ -0,0 +1,416 @@
package pdf417
const start_word = 0x1fea8
const stop_word = 0x3fa29
var codewords = [][]int{
[]int{
0x1d5c0, 0x1eaf0, 0x1f57c, 0x1d4e0, 0x1ea78, 0x1f53e, 0x1a8c0,
0x1d470, 0x1a860, 0x15040, 0x1a830, 0x15020, 0x1adc0, 0x1d6f0,
0x1eb7c, 0x1ace0, 0x1d678, 0x1eb3e, 0x158c0, 0x1ac70, 0x15860,
0x15dc0, 0x1aef0, 0x1d77c, 0x15ce0, 0x1ae78, 0x1d73e, 0x15c70,
0x1ae3c, 0x15ef0, 0x1af7c, 0x15e78, 0x1af3e, 0x15f7c, 0x1f5fa,
0x1d2e0, 0x1e978, 0x1f4be, 0x1a4c0, 0x1d270, 0x1e93c, 0x1a460,
0x1d238, 0x14840, 0x1a430, 0x1d21c, 0x14820, 0x1a418, 0x14810,
0x1a6e0, 0x1d378, 0x1e9be, 0x14cc0, 0x1a670, 0x1d33c, 0x14c60,
0x1a638, 0x1d31e, 0x14c30, 0x1a61c, 0x14ee0, 0x1a778, 0x1d3be,
0x14e70, 0x1a73c, 0x14e38, 0x1a71e, 0x14f78, 0x1a7be, 0x14f3c,
0x14f1e, 0x1a2c0, 0x1d170, 0x1e8bc, 0x1a260, 0x1d138, 0x1e89e,
0x14440, 0x1a230, 0x1d11c, 0x14420, 0x1a218, 0x14410, 0x14408,
0x146c0, 0x1a370, 0x1d1bc, 0x14660, 0x1a338, 0x1d19e, 0x14630,
0x1a31c, 0x14618, 0x1460c, 0x14770, 0x1a3bc, 0x14738, 0x1a39e,
0x1471c, 0x147bc, 0x1a160, 0x1d0b8, 0x1e85e, 0x14240, 0x1a130,
0x1d09c, 0x14220, 0x1a118, 0x1d08e, 0x14210, 0x1a10c, 0x14208,
0x1a106, 0x14360, 0x1a1b8, 0x1d0de, 0x14330, 0x1a19c, 0x14318,
0x1a18e, 0x1430c, 0x14306, 0x1a1de, 0x1438e, 0x14140, 0x1a0b0,
0x1d05c, 0x14120, 0x1a098, 0x1d04e, 0x14110, 0x1a08c, 0x14108,
0x1a086, 0x14104, 0x141b0, 0x14198, 0x1418c, 0x140a0, 0x1d02e,
0x1a04c, 0x1a046, 0x14082, 0x1cae0, 0x1e578, 0x1f2be, 0x194c0,
0x1ca70, 0x1e53c, 0x19460, 0x1ca38, 0x1e51e, 0x12840, 0x19430,
0x12820, 0x196e0, 0x1cb78, 0x1e5be, 0x12cc0, 0x19670, 0x1cb3c,
0x12c60, 0x19638, 0x12c30, 0x12c18, 0x12ee0, 0x19778, 0x1cbbe,
0x12e70, 0x1973c, 0x12e38, 0x12e1c, 0x12f78, 0x197be, 0x12f3c,
0x12fbe, 0x1dac0, 0x1ed70, 0x1f6bc, 0x1da60, 0x1ed38, 0x1f69e,
0x1b440, 0x1da30, 0x1ed1c, 0x1b420, 0x1da18, 0x1ed0e, 0x1b410,
0x1da0c, 0x192c0, 0x1c970, 0x1e4bc, 0x1b6c0, 0x19260, 0x1c938,
0x1e49e, 0x1b660, 0x1db38, 0x1ed9e, 0x16c40, 0x12420, 0x19218,
0x1c90e, 0x16c20, 0x1b618, 0x16c10, 0x126c0, 0x19370, 0x1c9bc,
0x16ec0, 0x12660, 0x19338, 0x1c99e, 0x16e60, 0x1b738, 0x1db9e,
0x16e30, 0x12618, 0x16e18, 0x12770, 0x193bc, 0x16f70, 0x12738,
0x1939e, 0x16f38, 0x1b79e, 0x16f1c, 0x127bc, 0x16fbc, 0x1279e,
0x16f9e, 0x1d960, 0x1ecb8, 0x1f65e, 0x1b240, 0x1d930, 0x1ec9c,
0x1b220, 0x1d918, 0x1ec8e, 0x1b210, 0x1d90c, 0x1b208, 0x1b204,
0x19160, 0x1c8b8, 0x1e45e, 0x1b360, 0x19130, 0x1c89c, 0x16640,
0x12220, 0x1d99c, 0x1c88e, 0x16620, 0x12210, 0x1910c, 0x16610,
0x1b30c, 0x19106, 0x12204, 0x12360, 0x191b8, 0x1c8de, 0x16760,
0x12330, 0x1919c, 0x16730, 0x1b39c, 0x1918e, 0x16718, 0x1230c,
0x12306, 0x123b8, 0x191de, 0x167b8, 0x1239c, 0x1679c, 0x1238e,
0x1678e, 0x167de, 0x1b140, 0x1d8b0, 0x1ec5c, 0x1b120, 0x1d898,
0x1ec4e, 0x1b110, 0x1d88c, 0x1b108, 0x1d886, 0x1b104, 0x1b102,
0x12140, 0x190b0, 0x1c85c, 0x16340, 0x12120, 0x19098, 0x1c84e,
0x16320, 0x1b198, 0x1d8ce, 0x16310, 0x12108, 0x19086, 0x16308,
0x1b186, 0x16304, 0x121b0, 0x190dc, 0x163b0, 0x12198, 0x190ce,
0x16398, 0x1b1ce, 0x1638c, 0x12186, 0x16386, 0x163dc, 0x163ce,
0x1b0a0, 0x1d858, 0x1ec2e, 0x1b090, 0x1d84c, 0x1b088, 0x1d846,
0x1b084, 0x1b082, 0x120a0, 0x19058, 0x1c82e, 0x161a0, 0x12090,
0x1904c, 0x16190, 0x1b0cc, 0x19046, 0x16188, 0x12084, 0x16184,
0x12082, 0x120d8, 0x161d8, 0x161cc, 0x161c6, 0x1d82c, 0x1d826,
0x1b042, 0x1902c, 0x12048, 0x160c8, 0x160c4, 0x160c2, 0x18ac0,
0x1c570, 0x1e2bc, 0x18a60, 0x1c538, 0x11440, 0x18a30, 0x1c51c,
0x11420, 0x18a18, 0x11410, 0x11408, 0x116c0, 0x18b70, 0x1c5bc,
0x11660, 0x18b38, 0x1c59e, 0x11630, 0x18b1c, 0x11618, 0x1160c,
0x11770, 0x18bbc, 0x11738, 0x18b9e, 0x1171c, 0x117bc, 0x1179e,
0x1cd60, 0x1e6b8, 0x1f35e, 0x19a40, 0x1cd30, 0x1e69c, 0x19a20,
0x1cd18, 0x1e68e, 0x19a10, 0x1cd0c, 0x19a08, 0x1cd06, 0x18960,
0x1c4b8, 0x1e25e, 0x19b60, 0x18930, 0x1c49c, 0x13640, 0x11220,
0x1cd9c, 0x1c48e, 0x13620, 0x19b18, 0x1890c, 0x13610, 0x11208,
0x13608, 0x11360, 0x189b8, 0x1c4de, 0x13760, 0x11330, 0x1cdde,
0x13730, 0x19b9c, 0x1898e, 0x13718, 0x1130c, 0x1370c, 0x113b8,
0x189de, 0x137b8, 0x1139c, 0x1379c, 0x1138e, 0x113de, 0x137de,
0x1dd40, 0x1eeb0, 0x1f75c, 0x1dd20, 0x1ee98, 0x1f74e, 0x1dd10,
0x1ee8c, 0x1dd08, 0x1ee86, 0x1dd04, 0x19940, 0x1ccb0, 0x1e65c,
0x1bb40, 0x19920, 0x1eedc, 0x1e64e, 0x1bb20, 0x1dd98, 0x1eece,
0x1bb10, 0x19908, 0x1cc86, 0x1bb08, 0x1dd86, 0x19902, 0x11140,
0x188b0, 0x1c45c, 0x13340, 0x11120, 0x18898, 0x1c44e, 0x17740,
0x13320, 0x19998, 0x1ccce, 0x17720, 0x1bb98, 0x1ddce, 0x18886,
0x17710, 0x13308, 0x19986, 0x17708, 0x11102, 0x111b0, 0x188dc,
0x133b0, 0x11198, 0x188ce, 0x177b0, 0x13398, 0x199ce, 0x17798,
0x1bbce, 0x11186, 0x13386, 0x111dc, 0x133dc, 0x111ce, 0x177dc,
0x133ce, 0x1dca0, 0x1ee58, 0x1f72e, 0x1dc90, 0x1ee4c, 0x1dc88,
0x1ee46, 0x1dc84, 0x1dc82, 0x198a0, 0x1cc58, 0x1e62e, 0x1b9a0,
0x19890, 0x1ee6e, 0x1b990, 0x1dccc, 0x1cc46, 0x1b988, 0x19884,
0x1b984, 0x19882, 0x1b982, 0x110a0, 0x18858, 0x1c42e, 0x131a0,
0x11090, 0x1884c, 0x173a0, 0x13190, 0x198cc, 0x18846, 0x17390,
0x1b9cc, 0x11084, 0x17388, 0x13184, 0x11082, 0x13182, 0x110d8,
0x1886e, 0x131d8, 0x110cc, 0x173d8, 0x131cc, 0x110c6, 0x173cc,
0x131c6, 0x110ee, 0x173ee, 0x1dc50, 0x1ee2c, 0x1dc48, 0x1ee26,
0x1dc44, 0x1dc42, 0x19850, 0x1cc2c, 0x1b8d0, 0x19848, 0x1cc26,
0x1b8c8, 0x1dc66, 0x1b8c4, 0x19842, 0x1b8c2, 0x11050, 0x1882c,
0x130d0, 0x11048, 0x18826, 0x171d0, 0x130c8, 0x19866, 0x171c8,
0x1b8e6, 0x11042, 0x171c4, 0x130c2, 0x171c2, 0x130ec, 0x171ec,
0x171e6, 0x1ee16, 0x1dc22, 0x1cc16, 0x19824, 0x19822, 0x11028,
0x13068, 0x170e8, 0x11022, 0x13062, 0x18560, 0x10a40, 0x18530,
0x10a20, 0x18518, 0x1c28e, 0x10a10, 0x1850c, 0x10a08, 0x18506,
0x10b60, 0x185b8, 0x1c2de, 0x10b30, 0x1859c, 0x10b18, 0x1858e,
0x10b0c, 0x10b06, 0x10bb8, 0x185de, 0x10b9c, 0x10b8e, 0x10bde,
0x18d40, 0x1c6b0, 0x1e35c, 0x18d20, 0x1c698, 0x18d10, 0x1c68c,
0x18d08, 0x1c686, 0x18d04, 0x10940, 0x184b0, 0x1c25c, 0x11b40,
0x10920, 0x1c6dc, 0x1c24e, 0x11b20, 0x18d98, 0x1c6ce, 0x11b10,
0x10908, 0x18486, 0x11b08, 0x18d86, 0x10902, 0x109b0, 0x184dc,
0x11bb0, 0x10998, 0x184ce, 0x11b98, 0x18dce, 0x11b8c, 0x10986,
0x109dc, 0x11bdc, 0x109ce, 0x11bce, 0x1cea0, 0x1e758, 0x1f3ae,
0x1ce90, 0x1e74c, 0x1ce88, 0x1e746, 0x1ce84, 0x1ce82, 0x18ca0,
0x1c658, 0x19da0, 0x18c90, 0x1c64c, 0x19d90, 0x1cecc, 0x1c646,
0x19d88, 0x18c84, 0x19d84, 0x18c82, 0x19d82, 0x108a0, 0x18458,
0x119a0, 0x10890, 0x1c66e, 0x13ba0, 0x11990, 0x18ccc, 0x18446,
0x13b90, 0x19dcc, 0x10884, 0x13b88, 0x11984, 0x10882, 0x11982,
0x108d8, 0x1846e, 0x119d8, 0x108cc, 0x13bd8, 0x119cc, 0x108c6,
0x13bcc, 0x119c6, 0x108ee, 0x119ee, 0x13bee, 0x1ef50, 0x1f7ac,
0x1ef48, 0x1f7a6, 0x1ef44, 0x1ef42, 0x1ce50, 0x1e72c, 0x1ded0,
0x1ef6c, 0x1e726, 0x1dec8, 0x1ef66, 0x1dec4, 0x1ce42, 0x1dec2,
0x18c50, 0x1c62c, 0x19cd0, 0x18c48, 0x1c626, 0x1bdd0, 0x19cc8,
0x1ce66, 0x1bdc8, 0x1dee6, 0x18c42, 0x1bdc4, 0x19cc2, 0x1bdc2,
0x10850, 0x1842c, 0x118d0, 0x10848, 0x18426, 0x139d0, 0x118c8,
0x18c66, 0x17bd0, 0x139c8, 0x19ce6, 0x10842, 0x17bc8, 0x1bde6,
0x118c2, 0x17bc4, 0x1086c, 0x118ec, 0x10866, 0x139ec, 0x118e6,
0x17bec, 0x139e6, 0x17be6, 0x1ef28, 0x1f796, 0x1ef24, 0x1ef22,
0x1ce28, 0x1e716, 0x1de68, 0x1ef36, 0x1de64, 0x1ce22, 0x1de62,
0x18c28, 0x1c616, 0x19c68, 0x18c24, 0x1bce8, 0x19c64, 0x18c22,
0x1bce4, 0x19c62, 0x1bce2, 0x10828, 0x18416, 0x11868, 0x18c36,
0x138e8, 0x11864, 0x10822, 0x179e8, 0x138e4, 0x11862, 0x179e4,
0x138e2, 0x179e2, 0x11876, 0x179f6, 0x1ef12, 0x1de34, 0x1de32,
0x19c34, 0x1bc74, 0x1bc72, 0x11834, 0x13874, 0x178f4, 0x178f2,
0x10540, 0x10520, 0x18298, 0x10510, 0x10508, 0x10504, 0x105b0,
0x10598, 0x1058c, 0x10586, 0x105dc, 0x105ce, 0x186a0, 0x18690,
0x1c34c, 0x18688, 0x1c346, 0x18684, 0x18682, 0x104a0, 0x18258,
0x10da0, 0x186d8, 0x1824c, 0x10d90, 0x186cc, 0x10d88, 0x186c6,
0x10d84, 0x10482, 0x10d82, 0x104d8, 0x1826e, 0x10dd8, 0x186ee,
0x10dcc, 0x104c6, 0x10dc6, 0x104ee, 0x10dee, 0x1c750, 0x1c748,
0x1c744, 0x1c742, 0x18650, 0x18ed0, 0x1c76c, 0x1c326, 0x18ec8,
0x1c766, 0x18ec4, 0x18642, 0x18ec2, 0x10450, 0x10cd0, 0x10448,
0x18226, 0x11dd0, 0x10cc8, 0x10444, 0x11dc8, 0x10cc4, 0x10442,
0x11dc4, 0x10cc2, 0x1046c, 0x10cec, 0x10466, 0x11dec, 0x10ce6,
0x11de6, 0x1e7a8, 0x1e7a4, 0x1e7a2, 0x1c728, 0x1cf68, 0x1e7b6,
0x1cf64, 0x1c722, 0x1cf62, 0x18628, 0x1c316, 0x18e68, 0x1c736,
0x19ee8, 0x18e64, 0x18622, 0x19ee4, 0x18e62, 0x19ee2, 0x10428,
0x18216, 0x10c68, 0x18636, 0x11ce8, 0x10c64, 0x10422, 0x13de8,
0x11ce4, 0x10c62, 0x13de4, 0x11ce2, 0x10436, 0x10c76, 0x11cf6,
0x13df6, 0x1f7d4, 0x1f7d2, 0x1e794, 0x1efb4, 0x1e792, 0x1efb2,
0x1c714, 0x1cf34, 0x1c712, 0x1df74, 0x1cf32, 0x1df72, 0x18614,
0x18e34, 0x18612, 0x19e74, 0x18e32, 0x1bef4,
},
[]int{
0x1f560, 0x1fab8, 0x1ea40, 0x1f530, 0x1fa9c, 0x1ea20, 0x1f518,
0x1fa8e, 0x1ea10, 0x1f50c, 0x1ea08, 0x1f506, 0x1ea04, 0x1eb60,
0x1f5b8, 0x1fade, 0x1d640, 0x1eb30, 0x1f59c, 0x1d620, 0x1eb18,
0x1f58e, 0x1d610, 0x1eb0c, 0x1d608, 0x1eb06, 0x1d604, 0x1d760,
0x1ebb8, 0x1f5de, 0x1ae40, 0x1d730, 0x1eb9c, 0x1ae20, 0x1d718,
0x1eb8e, 0x1ae10, 0x1d70c, 0x1ae08, 0x1d706, 0x1ae04, 0x1af60,
0x1d7b8, 0x1ebde, 0x15e40, 0x1af30, 0x1d79c, 0x15e20, 0x1af18,
0x1d78e, 0x15e10, 0x1af0c, 0x15e08, 0x1af06, 0x15f60, 0x1afb8,
0x1d7de, 0x15f30, 0x1af9c, 0x15f18, 0x1af8e, 0x15f0c, 0x15fb8,
0x1afde, 0x15f9c, 0x15f8e, 0x1e940, 0x1f4b0, 0x1fa5c, 0x1e920,
0x1f498, 0x1fa4e, 0x1e910, 0x1f48c, 0x1e908, 0x1f486, 0x1e904,
0x1e902, 0x1d340, 0x1e9b0, 0x1f4dc, 0x1d320, 0x1e998, 0x1f4ce,
0x1d310, 0x1e98c, 0x1d308, 0x1e986, 0x1d304, 0x1d302, 0x1a740,
0x1d3b0, 0x1e9dc, 0x1a720, 0x1d398, 0x1e9ce, 0x1a710, 0x1d38c,
0x1a708, 0x1d386, 0x1a704, 0x1a702, 0x14f40, 0x1a7b0, 0x1d3dc,
0x14f20, 0x1a798, 0x1d3ce, 0x14f10, 0x1a78c, 0x14f08, 0x1a786,
0x14f04, 0x14fb0, 0x1a7dc, 0x14f98, 0x1a7ce, 0x14f8c, 0x14f86,
0x14fdc, 0x14fce, 0x1e8a0, 0x1f458, 0x1fa2e, 0x1e890, 0x1f44c,
0x1e888, 0x1f446, 0x1e884, 0x1e882, 0x1d1a0, 0x1e8d8, 0x1f46e,
0x1d190, 0x1e8cc, 0x1d188, 0x1e8c6, 0x1d184, 0x1d182, 0x1a3a0,
0x1d1d8, 0x1e8ee, 0x1a390, 0x1d1cc, 0x1a388, 0x1d1c6, 0x1a384,
0x1a382, 0x147a0, 0x1a3d8, 0x1d1ee, 0x14790, 0x1a3cc, 0x14788,
0x1a3c6, 0x14784, 0x14782, 0x147d8, 0x1a3ee, 0x147cc, 0x147c6,
0x147ee, 0x1e850, 0x1f42c, 0x1e848, 0x1f426, 0x1e844, 0x1e842,
0x1d0d0, 0x1e86c, 0x1d0c8, 0x1e866, 0x1d0c4, 0x1d0c2, 0x1a1d0,
0x1d0ec, 0x1a1c8, 0x1d0e6, 0x1a1c4, 0x1a1c2, 0x143d0, 0x1a1ec,
0x143c8, 0x1a1e6, 0x143c4, 0x143c2, 0x143ec, 0x143e6, 0x1e828,
0x1f416, 0x1e824, 0x1e822, 0x1d068, 0x1e836, 0x1d064, 0x1d062,
0x1a0e8, 0x1d076, 0x1a0e4, 0x1a0e2, 0x141e8, 0x1a0f6, 0x141e4,
0x141e2, 0x1e814, 0x1e812, 0x1d034, 0x1d032, 0x1a074, 0x1a072,
0x1e540, 0x1f2b0, 0x1f95c, 0x1e520, 0x1f298, 0x1f94e, 0x1e510,
0x1f28c, 0x1e508, 0x1f286, 0x1e504, 0x1e502, 0x1cb40, 0x1e5b0,
0x1f2dc, 0x1cb20, 0x1e598, 0x1f2ce, 0x1cb10, 0x1e58c, 0x1cb08,
0x1e586, 0x1cb04, 0x1cb02, 0x19740, 0x1cbb0, 0x1e5dc, 0x19720,
0x1cb98, 0x1e5ce, 0x19710, 0x1cb8c, 0x19708, 0x1cb86, 0x19704,
0x19702, 0x12f40, 0x197b0, 0x1cbdc, 0x12f20, 0x19798, 0x1cbce,
0x12f10, 0x1978c, 0x12f08, 0x19786, 0x12f04, 0x12fb0, 0x197dc,
0x12f98, 0x197ce, 0x12f8c, 0x12f86, 0x12fdc, 0x12fce, 0x1f6a0,
0x1fb58, 0x16bf0, 0x1f690, 0x1fb4c, 0x169f8, 0x1f688, 0x1fb46,
0x168fc, 0x1f684, 0x1f682, 0x1e4a0, 0x1f258, 0x1f92e, 0x1eda0,
0x1e490, 0x1fb6e, 0x1ed90, 0x1f6cc, 0x1f246, 0x1ed88, 0x1e484,
0x1ed84, 0x1e482, 0x1ed82, 0x1c9a0, 0x1e4d8, 0x1f26e, 0x1dba0,
0x1c990, 0x1e4cc, 0x1db90, 0x1edcc, 0x1e4c6, 0x1db88, 0x1c984,
0x1db84, 0x1c982, 0x1db82, 0x193a0, 0x1c9d8, 0x1e4ee, 0x1b7a0,
0x19390, 0x1c9cc, 0x1b790, 0x1dbcc, 0x1c9c6, 0x1b788, 0x19384,
0x1b784, 0x19382, 0x1b782, 0x127a0, 0x193d8, 0x1c9ee, 0x16fa0,
0x12790, 0x193cc, 0x16f90, 0x1b7cc, 0x193c6, 0x16f88, 0x12784,
0x16f84, 0x12782, 0x127d8, 0x193ee, 0x16fd8, 0x127cc, 0x16fcc,
0x127c6, 0x16fc6, 0x127ee, 0x1f650, 0x1fb2c, 0x165f8, 0x1f648,
0x1fb26, 0x164fc, 0x1f644, 0x1647e, 0x1f642, 0x1e450, 0x1f22c,
0x1ecd0, 0x1e448, 0x1f226, 0x1ecc8, 0x1f666, 0x1ecc4, 0x1e442,
0x1ecc2, 0x1c8d0, 0x1e46c, 0x1d9d0, 0x1c8c8, 0x1e466, 0x1d9c8,
0x1ece6, 0x1d9c4, 0x1c8c2, 0x1d9c2, 0x191d0, 0x1c8ec, 0x1b3d0,
0x191c8, 0x1c8e6, 0x1b3c8, 0x1d9e6, 0x1b3c4, 0x191c2, 0x1b3c2,
0x123d0, 0x191ec, 0x167d0, 0x123c8, 0x191e6, 0x167c8, 0x1b3e6,
0x167c4, 0x123c2, 0x167c2, 0x123ec, 0x167ec, 0x123e6, 0x167e6,
0x1f628, 0x1fb16, 0x162fc, 0x1f624, 0x1627e, 0x1f622, 0x1e428,
0x1f216, 0x1ec68, 0x1f636, 0x1ec64, 0x1e422, 0x1ec62, 0x1c868,
0x1e436, 0x1d8e8, 0x1c864, 0x1d8e4, 0x1c862, 0x1d8e2, 0x190e8,
0x1c876, 0x1b1e8, 0x1d8f6, 0x1b1e4, 0x190e2, 0x1b1e2, 0x121e8,
0x190f6, 0x163e8, 0x121e4, 0x163e4, 0x121e2, 0x163e2, 0x121f6,
0x163f6, 0x1f614, 0x1617e, 0x1f612, 0x1e414, 0x1ec34, 0x1e412,
0x1ec32, 0x1c834, 0x1d874, 0x1c832, 0x1d872, 0x19074, 0x1b0f4,
0x19072, 0x1b0f2, 0x120f4, 0x161f4, 0x120f2, 0x161f2, 0x1f60a,
0x1e40a, 0x1ec1a, 0x1c81a, 0x1d83a, 0x1903a, 0x1b07a, 0x1e2a0,
0x1f158, 0x1f8ae, 0x1e290, 0x1f14c, 0x1e288, 0x1f146, 0x1e284,
0x1e282, 0x1c5a0, 0x1e2d8, 0x1f16e, 0x1c590, 0x1e2cc, 0x1c588,
0x1e2c6, 0x1c584, 0x1c582, 0x18ba0, 0x1c5d8, 0x1e2ee, 0x18b90,
0x1c5cc, 0x18b88, 0x1c5c6, 0x18b84, 0x18b82, 0x117a0, 0x18bd8,
0x1c5ee, 0x11790, 0x18bcc, 0x11788, 0x18bc6, 0x11784, 0x11782,
0x117d8, 0x18bee, 0x117cc, 0x117c6, 0x117ee, 0x1f350, 0x1f9ac,
0x135f8, 0x1f348, 0x1f9a6, 0x134fc, 0x1f344, 0x1347e, 0x1f342,
0x1e250, 0x1f12c, 0x1e6d0, 0x1e248, 0x1f126, 0x1e6c8, 0x1f366,
0x1e6c4, 0x1e242, 0x1e6c2, 0x1c4d0, 0x1e26c, 0x1cdd0, 0x1c4c8,
0x1e266, 0x1cdc8, 0x1e6e6, 0x1cdc4, 0x1c4c2, 0x1cdc2, 0x189d0,
0x1c4ec, 0x19bd0, 0x189c8, 0x1c4e6, 0x19bc8, 0x1cde6, 0x19bc4,
0x189c2, 0x19bc2, 0x113d0, 0x189ec, 0x137d0, 0x113c8, 0x189e6,
0x137c8, 0x19be6, 0x137c4, 0x113c2, 0x137c2, 0x113ec, 0x137ec,
0x113e6, 0x137e6, 0x1fba8, 0x175f0, 0x1bafc, 0x1fba4, 0x174f8,
0x1ba7e, 0x1fba2, 0x1747c, 0x1743e, 0x1f328, 0x1f996, 0x132fc,
0x1f768, 0x1fbb6, 0x176fc, 0x1327e, 0x1f764, 0x1f322, 0x1767e,
0x1f762, 0x1e228, 0x1f116, 0x1e668, 0x1e224, 0x1eee8, 0x1f776,
0x1e222, 0x1eee4, 0x1e662, 0x1eee2, 0x1c468, 0x1e236, 0x1cce8,
0x1c464, 0x1dde8, 0x1cce4, 0x1c462, 0x1dde4, 0x1cce2, 0x1dde2,
0x188e8, 0x1c476, 0x199e8, 0x188e4, 0x1bbe8, 0x199e4, 0x188e2,
0x1bbe4, 0x199e2, 0x1bbe2, 0x111e8, 0x188f6, 0x133e8, 0x111e4,
0x177e8, 0x133e4, 0x111e2, 0x177e4, 0x133e2, 0x177e2, 0x111f6,
0x133f6, 0x1fb94, 0x172f8, 0x1b97e, 0x1fb92, 0x1727c, 0x1723e,
0x1f314, 0x1317e, 0x1f734, 0x1f312, 0x1737e, 0x1f732, 0x1e214,
0x1e634, 0x1e212, 0x1ee74, 0x1e632, 0x1ee72, 0x1c434, 0x1cc74,
0x1c432, 0x1dcf4, 0x1cc72, 0x1dcf2, 0x18874, 0x198f4, 0x18872,
0x1b9f4, 0x198f2, 0x1b9f2, 0x110f4, 0x131f4, 0x110f2, 0x173f4,
0x131f2, 0x173f2, 0x1fb8a, 0x1717c, 0x1713e, 0x1f30a, 0x1f71a,
0x1e20a, 0x1e61a, 0x1ee3a, 0x1c41a, 0x1cc3a, 0x1dc7a, 0x1883a,
0x1987a, 0x1b8fa, 0x1107a, 0x130fa, 0x171fa, 0x170be, 0x1e150,
0x1f0ac, 0x1e148, 0x1f0a6, 0x1e144, 0x1e142, 0x1c2d0, 0x1e16c,
0x1c2c8, 0x1e166, 0x1c2c4, 0x1c2c2, 0x185d0, 0x1c2ec, 0x185c8,
0x1c2e6, 0x185c4, 0x185c2, 0x10bd0, 0x185ec, 0x10bc8, 0x185e6,
0x10bc4, 0x10bc2, 0x10bec, 0x10be6, 0x1f1a8, 0x1f8d6, 0x11afc,
0x1f1a4, 0x11a7e, 0x1f1a2, 0x1e128, 0x1f096, 0x1e368, 0x1e124,
0x1e364, 0x1e122, 0x1e362, 0x1c268, 0x1e136, 0x1c6e8, 0x1c264,
0x1c6e4, 0x1c262, 0x1c6e2, 0x184e8, 0x1c276, 0x18de8, 0x184e4,
0x18de4, 0x184e2, 0x18de2, 0x109e8, 0x184f6, 0x11be8, 0x109e4,
0x11be4, 0x109e2, 0x11be2, 0x109f6, 0x11bf6, 0x1f9d4, 0x13af8,
0x19d7e, 0x1f9d2, 0x13a7c, 0x13a3e, 0x1f194, 0x1197e, 0x1f3b4,
0x1f192, 0x13b7e, 0x1f3b2, 0x1e114, 0x1e334, 0x1e112, 0x1e774,
0x1e332, 0x1e772, 0x1c234, 0x1c674, 0x1c232, 0x1cef4, 0x1c672,
0x1cef2, 0x18474, 0x18cf4, 0x18472, 0x19df4, 0x18cf2, 0x19df2,
0x108f4, 0x119f4, 0x108f2, 0x13bf4, 0x119f2, 0x13bf2, 0x17af0,
0x1bd7c, 0x17a78, 0x1bd3e, 0x17a3c, 0x17a1e, 0x1f9ca, 0x1397c,
0x1fbda, 0x17b7c, 0x1393e, 0x17b3e, 0x1f18a, 0x1f39a, 0x1f7ba,
0x1e10a, 0x1e31a, 0x1e73a, 0x1ef7a, 0x1c21a, 0x1c63a, 0x1ce7a,
0x1defa, 0x1843a, 0x18c7a, 0x19cfa, 0x1bdfa, 0x1087a, 0x118fa,
0x139fa, 0x17978, 0x1bcbe, 0x1793c, 0x1791e, 0x138be, 0x179be,
0x178bc, 0x1789e, 0x1785e, 0x1e0a8, 0x1e0a4, 0x1e0a2, 0x1c168,
0x1e0b6, 0x1c164, 0x1c162, 0x182e8, 0x1c176, 0x182e4, 0x182e2,
0x105e8, 0x182f6, 0x105e4, 0x105e2, 0x105f6, 0x1f0d4, 0x10d7e,
0x1f0d2, 0x1e094, 0x1e1b4, 0x1e092, 0x1e1b2, 0x1c134, 0x1c374,
0x1c132, 0x1c372, 0x18274, 0x186f4, 0x18272, 0x186f2, 0x104f4,
0x10df4, 0x104f2, 0x10df2, 0x1f8ea, 0x11d7c, 0x11d3e, 0x1f0ca,
0x1f1da, 0x1e08a, 0x1e19a, 0x1e3ba, 0x1c11a, 0x1c33a, 0x1c77a,
0x1823a, 0x1867a, 0x18efa, 0x1047a, 0x10cfa, 0x11dfa, 0x13d78,
0x19ebe, 0x13d3c, 0x13d1e, 0x11cbe, 0x13dbe, 0x17d70, 0x1bebc,
0x17d38, 0x1be9e, 0x17d1c, 0x17d0e, 0x13cbc, 0x17dbc, 0x13c9e,
0x17d9e, 0x17cb8, 0x1be5e, 0x17c9c, 0x17c8e, 0x13c5e, 0x17cde,
0x17c5c, 0x17c4e, 0x17c2e, 0x1c0b4, 0x1c0b2, 0x18174, 0x18172,
0x102f4, 0x102f2, 0x1e0da, 0x1c09a, 0x1c1ba, 0x1813a, 0x1837a,
0x1027a, 0x106fa, 0x10ebe, 0x11ebc, 0x11e9e, 0x13eb8, 0x19f5e,
0x13e9c, 0x13e8e, 0x11e5e, 0x13ede, 0x17eb0, 0x1bf5c, 0x17e98,
0x1bf4e, 0x17e8c, 0x17e86, 0x13e5c, 0x17edc, 0x13e4e, 0x17ece,
0x17e58, 0x1bf2e, 0x17e4c, 0x17e46, 0x13e2e, 0x17e6e, 0x17e2c,
0x17e26, 0x10f5e, 0x11f5c, 0x11f4e, 0x13f58, 0x19fae, 0x13f4c,
0x13f46, 0x11f2e, 0x13f6e, 0x13f2c, 0x13f26,
},
[]int{
0x1abe0, 0x1d5f8, 0x153c0, 0x1a9f0, 0x1d4fc, 0x151e0, 0x1a8f8,
0x1d47e, 0x150f0, 0x1a87c, 0x15078, 0x1fad0, 0x15be0, 0x1adf8,
0x1fac8, 0x159f0, 0x1acfc, 0x1fac4, 0x158f8, 0x1ac7e, 0x1fac2,
0x1587c, 0x1f5d0, 0x1faec, 0x15df8, 0x1f5c8, 0x1fae6, 0x15cfc,
0x1f5c4, 0x15c7e, 0x1f5c2, 0x1ebd0, 0x1f5ec, 0x1ebc8, 0x1f5e6,
0x1ebc4, 0x1ebc2, 0x1d7d0, 0x1ebec, 0x1d7c8, 0x1ebe6, 0x1d7c4,
0x1d7c2, 0x1afd0, 0x1d7ec, 0x1afc8, 0x1d7e6, 0x1afc4, 0x14bc0,
0x1a5f0, 0x1d2fc, 0x149e0, 0x1a4f8, 0x1d27e, 0x148f0, 0x1a47c,
0x14878, 0x1a43e, 0x1483c, 0x1fa68, 0x14df0, 0x1a6fc, 0x1fa64,
0x14cf8, 0x1a67e, 0x1fa62, 0x14c7c, 0x14c3e, 0x1f4e8, 0x1fa76,
0x14efc, 0x1f4e4, 0x14e7e, 0x1f4e2, 0x1e9e8, 0x1f4f6, 0x1e9e4,
0x1e9e2, 0x1d3e8, 0x1e9f6, 0x1d3e4, 0x1d3e2, 0x1a7e8, 0x1d3f6,
0x1a7e4, 0x1a7e2, 0x145e0, 0x1a2f8, 0x1d17e, 0x144f0, 0x1a27c,
0x14478, 0x1a23e, 0x1443c, 0x1441e, 0x1fa34, 0x146f8, 0x1a37e,
0x1fa32, 0x1467c, 0x1463e, 0x1f474, 0x1477e, 0x1f472, 0x1e8f4,
0x1e8f2, 0x1d1f4, 0x1d1f2, 0x1a3f4, 0x1a3f2, 0x142f0, 0x1a17c,
0x14278, 0x1a13e, 0x1423c, 0x1421e, 0x1fa1a, 0x1437c, 0x1433e,
0x1f43a, 0x1e87a, 0x1d0fa, 0x14178, 0x1a0be, 0x1413c, 0x1411e,
0x141be, 0x140bc, 0x1409e, 0x12bc0, 0x195f0, 0x1cafc, 0x129e0,
0x194f8, 0x1ca7e, 0x128f0, 0x1947c, 0x12878, 0x1943e, 0x1283c,
0x1f968, 0x12df0, 0x196fc, 0x1f964, 0x12cf8, 0x1967e, 0x1f962,
0x12c7c, 0x12c3e, 0x1f2e8, 0x1f976, 0x12efc, 0x1f2e4, 0x12e7e,
0x1f2e2, 0x1e5e8, 0x1f2f6, 0x1e5e4, 0x1e5e2, 0x1cbe8, 0x1e5f6,
0x1cbe4, 0x1cbe2, 0x197e8, 0x1cbf6, 0x197e4, 0x197e2, 0x1b5e0,
0x1daf8, 0x1ed7e, 0x169c0, 0x1b4f0, 0x1da7c, 0x168e0, 0x1b478,
0x1da3e, 0x16870, 0x1b43c, 0x16838, 0x1b41e, 0x1681c, 0x125e0,
0x192f8, 0x1c97e, 0x16de0, 0x124f0, 0x1927c, 0x16cf0, 0x1b67c,
0x1923e, 0x16c78, 0x1243c, 0x16c3c, 0x1241e, 0x16c1e, 0x1f934,
0x126f8, 0x1937e, 0x1fb74, 0x1f932, 0x16ef8, 0x1267c, 0x1fb72,
0x16e7c, 0x1263e, 0x16e3e, 0x1f274, 0x1277e, 0x1f6f4, 0x1f272,
0x16f7e, 0x1f6f2, 0x1e4f4, 0x1edf4, 0x1e4f2, 0x1edf2, 0x1c9f4,
0x1dbf4, 0x1c9f2, 0x1dbf2, 0x193f4, 0x193f2, 0x165c0, 0x1b2f0,
0x1d97c, 0x164e0, 0x1b278, 0x1d93e, 0x16470, 0x1b23c, 0x16438,
0x1b21e, 0x1641c, 0x1640e, 0x122f0, 0x1917c, 0x166f0, 0x12278,
0x1913e, 0x16678, 0x1b33e, 0x1663c, 0x1221e, 0x1661e, 0x1f91a,
0x1237c, 0x1fb3a, 0x1677c, 0x1233e, 0x1673e, 0x1f23a, 0x1f67a,
0x1e47a, 0x1ecfa, 0x1c8fa, 0x1d9fa, 0x191fa, 0x162e0, 0x1b178,
0x1d8be, 0x16270, 0x1b13c, 0x16238, 0x1b11e, 0x1621c, 0x1620e,
0x12178, 0x190be, 0x16378, 0x1213c, 0x1633c, 0x1211e, 0x1631e,
0x121be, 0x163be, 0x16170, 0x1b0bc, 0x16138, 0x1b09e, 0x1611c,
0x1610e, 0x120bc, 0x161bc, 0x1209e, 0x1619e, 0x160b8, 0x1b05e,
0x1609c, 0x1608e, 0x1205e, 0x160de, 0x1605c, 0x1604e, 0x115e0,
0x18af8, 0x1c57e, 0x114f0, 0x18a7c, 0x11478, 0x18a3e, 0x1143c,
0x1141e, 0x1f8b4, 0x116f8, 0x18b7e, 0x1f8b2, 0x1167c, 0x1163e,
0x1f174, 0x1177e, 0x1f172, 0x1e2f4, 0x1e2f2, 0x1c5f4, 0x1c5f2,
0x18bf4, 0x18bf2, 0x135c0, 0x19af0, 0x1cd7c, 0x134e0, 0x19a78,
0x1cd3e, 0x13470, 0x19a3c, 0x13438, 0x19a1e, 0x1341c, 0x1340e,
0x112f0, 0x1897c, 0x136f0, 0x11278, 0x1893e, 0x13678, 0x19b3e,
0x1363c, 0x1121e, 0x1361e, 0x1f89a, 0x1137c, 0x1f9ba, 0x1377c,
0x1133e, 0x1373e, 0x1f13a, 0x1f37a, 0x1e27a, 0x1e6fa, 0x1c4fa,
0x1cdfa, 0x189fa, 0x1bae0, 0x1dd78, 0x1eebe, 0x174c0, 0x1ba70,
0x1dd3c, 0x17460, 0x1ba38, 0x1dd1e, 0x17430, 0x1ba1c, 0x17418,
0x1ba0e, 0x1740c, 0x132e0, 0x19978, 0x1ccbe, 0x176e0, 0x13270,
0x1993c, 0x17670, 0x1bb3c, 0x1991e, 0x17638, 0x1321c, 0x1761c,
0x1320e, 0x1760e, 0x11178, 0x188be, 0x13378, 0x1113c, 0x17778,
0x1333c, 0x1111e, 0x1773c, 0x1331e, 0x1771e, 0x111be, 0x133be,
0x177be, 0x172c0, 0x1b970, 0x1dcbc, 0x17260, 0x1b938, 0x1dc9e,
0x17230, 0x1b91c, 0x17218, 0x1b90e, 0x1720c, 0x17206, 0x13170,
0x198bc, 0x17370, 0x13138, 0x1989e, 0x17338, 0x1b99e, 0x1731c,
0x1310e, 0x1730e, 0x110bc, 0x131bc, 0x1109e, 0x173bc, 0x1319e,
0x1739e, 0x17160, 0x1b8b8, 0x1dc5e, 0x17130, 0x1b89c, 0x17118,
0x1b88e, 0x1710c, 0x17106, 0x130b8, 0x1985e, 0x171b8, 0x1309c,
0x1719c, 0x1308e, 0x1718e, 0x1105e, 0x130de, 0x171de, 0x170b0,
0x1b85c, 0x17098, 0x1b84e, 0x1708c, 0x17086, 0x1305c, 0x170dc,
0x1304e, 0x170ce, 0x17058, 0x1b82e, 0x1704c, 0x17046, 0x1302e,
0x1706e, 0x1702c, 0x17026, 0x10af0, 0x1857c, 0x10a78, 0x1853e,
0x10a3c, 0x10a1e, 0x10b7c, 0x10b3e, 0x1f0ba, 0x1e17a, 0x1c2fa,
0x185fa, 0x11ae0, 0x18d78, 0x1c6be, 0x11a70, 0x18d3c, 0x11a38,
0x18d1e, 0x11a1c, 0x11a0e, 0x10978, 0x184be, 0x11b78, 0x1093c,
0x11b3c, 0x1091e, 0x11b1e, 0x109be, 0x11bbe, 0x13ac0, 0x19d70,
0x1cebc, 0x13a60, 0x19d38, 0x1ce9e, 0x13a30, 0x19d1c, 0x13a18,
0x19d0e, 0x13a0c, 0x13a06, 0x11970, 0x18cbc, 0x13b70, 0x11938,
0x18c9e, 0x13b38, 0x1191c, 0x13b1c, 0x1190e, 0x13b0e, 0x108bc,
0x119bc, 0x1089e, 0x13bbc, 0x1199e, 0x13b9e, 0x1bd60, 0x1deb8,
0x1ef5e, 0x17a40, 0x1bd30, 0x1de9c, 0x17a20, 0x1bd18, 0x1de8e,
0x17a10, 0x1bd0c, 0x17a08, 0x1bd06, 0x17a04, 0x13960, 0x19cb8,
0x1ce5e, 0x17b60, 0x13930, 0x19c9c, 0x17b30, 0x1bd9c, 0x19c8e,
0x17b18, 0x1390c, 0x17b0c, 0x13906, 0x17b06, 0x118b8, 0x18c5e,
0x139b8, 0x1189c, 0x17bb8, 0x1399c, 0x1188e, 0x17b9c, 0x1398e,
0x17b8e, 0x1085e, 0x118de, 0x139de, 0x17bde, 0x17940, 0x1bcb0,
0x1de5c, 0x17920, 0x1bc98, 0x1de4e, 0x17910, 0x1bc8c, 0x17908,
0x1bc86, 0x17904, 0x17902, 0x138b0, 0x19c5c, 0x179b0, 0x13898,
0x19c4e, 0x17998, 0x1bcce, 0x1798c, 0x13886, 0x17986, 0x1185c,
0x138dc, 0x1184e, 0x179dc, 0x138ce, 0x179ce, 0x178a0, 0x1bc58,
0x1de2e, 0x17890, 0x1bc4c, 0x17888, 0x1bc46, 0x17884, 0x17882,
0x13858, 0x19c2e, 0x178d8, 0x1384c, 0x178cc, 0x13846, 0x178c6,
0x1182e, 0x1386e, 0x178ee, 0x17850, 0x1bc2c, 0x17848, 0x1bc26,
0x17844, 0x17842, 0x1382c, 0x1786c, 0x13826, 0x17866, 0x17828,
0x1bc16, 0x17824, 0x17822, 0x13816, 0x17836, 0x10578, 0x182be,
0x1053c, 0x1051e, 0x105be, 0x10d70, 0x186bc, 0x10d38, 0x1869e,
0x10d1c, 0x10d0e, 0x104bc, 0x10dbc, 0x1049e, 0x10d9e, 0x11d60,
0x18eb8, 0x1c75e, 0x11d30, 0x18e9c, 0x11d18, 0x18e8e, 0x11d0c,
0x11d06, 0x10cb8, 0x1865e, 0x11db8, 0x10c9c, 0x11d9c, 0x10c8e,
0x11d8e, 0x1045e, 0x10cde, 0x11dde, 0x13d40, 0x19eb0, 0x1cf5c,
0x13d20, 0x19e98, 0x1cf4e, 0x13d10, 0x19e8c, 0x13d08, 0x19e86,
0x13d04, 0x13d02, 0x11cb0, 0x18e5c, 0x13db0, 0x11c98, 0x18e4e,
0x13d98, 0x19ece, 0x13d8c, 0x11c86, 0x13d86, 0x10c5c, 0x11cdc,
0x10c4e, 0x13ddc, 0x11cce, 0x13dce, 0x1bea0, 0x1df58, 0x1efae,
0x1be90, 0x1df4c, 0x1be88, 0x1df46, 0x1be84, 0x1be82, 0x13ca0,
0x19e58, 0x1cf2e, 0x17da0, 0x13c90, 0x19e4c, 0x17d90, 0x1becc,
0x19e46, 0x17d88, 0x13c84, 0x17d84, 0x13c82, 0x17d82, 0x11c58,
0x18e2e, 0x13cd8, 0x11c4c, 0x17dd8, 0x13ccc, 0x11c46, 0x17dcc,
0x13cc6, 0x17dc6, 0x10c2e, 0x11c6e, 0x13cee, 0x17dee, 0x1be50,
0x1df2c, 0x1be48, 0x1df26, 0x1be44, 0x1be42, 0x13c50, 0x19e2c,
0x17cd0, 0x13c48, 0x19e26, 0x17cc8, 0x1be66, 0x17cc4, 0x13c42,
0x17cc2, 0x11c2c, 0x13c6c, 0x11c26, 0x17cec, 0x13c66, 0x17ce6,
0x1be28, 0x1df16, 0x1be24, 0x1be22, 0x13c28, 0x19e16, 0x17c68,
0x13c24, 0x17c64, 0x13c22, 0x17c62, 0x11c16, 0x13c36, 0x17c76,
0x1be14, 0x1be12, 0x13c14, 0x17c34, 0x13c12, 0x17c32, 0x102bc,
0x1029e, 0x106b8, 0x1835e, 0x1069c, 0x1068e, 0x1025e, 0x106de,
0x10eb0, 0x1875c, 0x10e98, 0x1874e, 0x10e8c, 0x10e86, 0x1065c,
0x10edc, 0x1064e, 0x10ece, 0x11ea0, 0x18f58, 0x1c7ae, 0x11e90,
0x18f4c, 0x11e88, 0x18f46, 0x11e84, 0x11e82, 0x10e58, 0x1872e,
0x11ed8, 0x18f6e, 0x11ecc, 0x10e46, 0x11ec6, 0x1062e, 0x10e6e,
0x11eee, 0x19f50, 0x1cfac, 0x19f48, 0x1cfa6, 0x19f44, 0x19f42,
0x11e50, 0x18f2c, 0x13ed0, 0x19f6c, 0x18f26, 0x13ec8, 0x11e44,
0x13ec4, 0x11e42, 0x13ec2, 0x10e2c, 0x11e6c, 0x10e26, 0x13eec,
0x11e66, 0x13ee6, 0x1dfa8, 0x1efd6, 0x1dfa4, 0x1dfa2, 0x19f28,
0x1cf96, 0x1bf68, 0x19f24, 0x1bf64, 0x19f22, 0x1bf62, 0x11e28,
0x18f16, 0x13e68, 0x11e24, 0x17ee8, 0x13e64, 0x11e22, 0x17ee4,
0x13e62, 0x17ee2, 0x10e16, 0x11e36, 0x13e76, 0x17ef6, 0x1df94,
0x1df92, 0x19f14, 0x1bf34, 0x19f12, 0x1bf32, 0x11e14, 0x13e34,
0x11e12, 0x17e74, 0x13e32, 0x17e72, 0x1df8a, 0x19f0a, 0x1bf1a,
0x11e0a, 0x13e1a, 0x17e3a, 0x1035c, 0x1034e, 0x10758, 0x183ae,
0x1074c, 0x10746, 0x1032e, 0x1076e, 0x10f50, 0x187ac, 0x10f48,
0x187a6, 0x10f44, 0x10f42, 0x1072c, 0x10f6c, 0x10726, 0x10f66,
0x18fa8, 0x1c7d6, 0x18fa4, 0x18fa2, 0x10f28, 0x18796, 0x11f68,
0x18fb6, 0x11f64, 0x10f22, 0x11f62, 0x10716, 0x10f36, 0x11f76,
0x1cfd4, 0x1cfd2, 0x18f94, 0x19fb4, 0x18f92, 0x19fb2, 0x10f14,
0x11f34, 0x10f12, 0x13f74, 0x11f32, 0x13f72, 0x1cfca, 0x18f8a,
0x19f9a, 0x10f0a, 0x11f1a, 0x13f3a, 0x103ac, 0x103a6, 0x107a8,
0x183d6, 0x107a4, 0x107a2, 0x10396, 0x107b6, 0x187d4, 0x187d2,
0x10794, 0x10fb4, 0x10792, 0x10fb2, 0x1c7ea,
},
}
func getCodeword(tableId int, word int) int {
return codewords[tableId][word]
}

View file

@ -0,0 +1,57 @@
package pdf417
import "math"
const (
minCols = 2
maxCols = 30
maxRows = 30
minRows = 2
moduleHeight = 2
preferred_ratio = 3.0
)
func calculateNumberOfRows(m, k, c int) int {
r := ((m + 1 + k) / c) + 1
if c*r >= (m + 1 + k + c) {
r--
}
return r
}
func calcDimensions(dataWords, eccWords int) (cols, rows int) {
ratio := 0.0
cols = 0
rows = 0
for c := minCols; c <= maxCols; c++ {
r := calculateNumberOfRows(dataWords, eccWords, c)
if r < minRows {
break
}
if r > maxRows {
continue
}
newRatio := float64(17*cols+69) / float64(rows*moduleHeight)
if rows != 0 && math.Abs(newRatio-preferred_ratio) > math.Abs(ratio-preferred_ratio) {
continue
}
ratio = newRatio
cols = c
rows = r
}
if rows == 0 {
r := calculateNumberOfRows(dataWords, eccWords, minCols)
if r < minRows {
rows = minRows
cols = minCols
}
}
return
}

162
vendor/github.com/boombuler/barcode/pdf417/encoder.go generated vendored Normal file
View file

@ -0,0 +1,162 @@
// Package pdf417 can create PDF-417 barcodes
package pdf417
import (
"fmt"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
const (
padding_codeword = 900
)
// Encodes the given data as PDF417 barcode.
// securityLevel should be between 0 and 8. The higher the number, the more
// additional error-correction codes are added.
func Encode(data string, securityLevel byte) (barcode.Barcode, error) {
if securityLevel >= 9 {
return nil, fmt.Errorf("Invalid security level %d", securityLevel)
}
sl := securitylevel(securityLevel)
dataWords, err := highlevelEncode(data)
if err != nil {
return nil, err
}
columns, rows := calcDimensions(len(dataWords), sl.ErrorCorrectionWordCount())
if columns < minCols || columns > maxCols || rows < minRows || rows > maxRows {
return nil, fmt.Errorf("Unable to fit data in barcode")
}
barcode := new(pdfBarcode)
barcode.data = data
codeWords, err := encodeData(dataWords, columns, sl)
if err != nil {
return nil, err
}
grid := [][]int{}
for i := 0; i < len(codeWords); i += columns {
grid = append(grid, codeWords[i:min(i+columns, len(codeWords))])
}
codes := [][]int{}
for rowNum, row := range grid {
table := rowNum % 3
rowCodes := make([]int, 0, columns+4)
rowCodes = append(rowCodes, start_word)
rowCodes = append(rowCodes, getCodeword(table, getLeftCodeWord(rowNum, rows, columns, securityLevel)))
for _, word := range row {
rowCodes = append(rowCodes, getCodeword(table, word))
}
rowCodes = append(rowCodes, getCodeword(table, getRightCodeWord(rowNum, rows, columns, securityLevel)))
rowCodes = append(rowCodes, stop_word)
codes = append(codes, rowCodes)
}
barcode.code = renderBarcode(codes)
barcode.width = (columns+4)*17 + 1
return barcode, nil
}
func encodeData(dataWords []int, columns int, sl securitylevel) ([]int, error) {
dataCount := len(dataWords)
ecCount := sl.ErrorCorrectionWordCount()
padWords := getPadding(dataCount, ecCount, columns)
dataWords = append(dataWords, padWords...)
length := len(dataWords) + 1
dataWords = append([]int{length}, dataWords...)
ecWords := sl.Compute(dataWords)
return append(dataWords, ecWords...), nil
}
func getLeftCodeWord(rowNum int, rows int, columns int, securityLevel byte) int {
tableId := rowNum % 3
var x int
switch tableId {
case 0:
x = (rows - 3) / 3
case 1:
x = int(securityLevel) * 3
x += (rows - 1) % 3
case 2:
x = columns - 1
}
return 30*(rowNum/3) + x
}
func getRightCodeWord(rowNum int, rows int, columns int, securityLevel byte) int {
tableId := rowNum % 3
var x int
switch tableId {
case 0:
x = columns - 1
case 1:
x = (rows - 1) / 3
case 2:
x = int(securityLevel) * 3
x += (rows - 1) % 3
}
return 30*(rowNum/3) + x
}
func min(a, b int) int {
if a <= b {
return a
}
return b
}
func getPadding(dataCount int, ecCount int, columns int) []int {
totalCount := dataCount + ecCount + 1
mod := totalCount % columns
padding := []int{}
if mod > 0 {
padCount := columns - mod
padding = make([]int, padCount)
for i := 0; i < padCount; i++ {
padding[i] = padding_codeword
}
}
return padding
}
func renderBarcode(codes [][]int) *utils.BitList {
bl := new(utils.BitList)
for _, row := range codes {
lastIdx := len(row) - 1
for i, col := range row {
if i == lastIdx {
bl.AddBits(col, 18)
} else {
bl.AddBits(col, 17)
}
}
}
return bl
}

View file

@ -0,0 +1,151 @@
package pdf417
type securitylevel byte
func (level securitylevel) ErrorCorrectionWordCount() int {
return 1 << (uint(level) + 1)
}
var correctionFactors = [][]int{
// Level 0
[]int{27, 917},
// Level 1
[]int{522, 568, 723, 809},
// Level 2
[]int{237, 308, 436, 284, 646, 653, 428, 379},
// Level 3
[]int{
274, 562, 232, 755, 599, 524, 801, 132, 295, 116, 442, 428, 295, 42,
176, 65,
},
// Level 4
[]int{
361, 575, 922, 525, 176, 586, 640, 321, 536, 742, 677, 742, 687,
284, 193, 517, 273, 494, 263, 147, 593, 800, 571, 320, 803, 133,
231, 390, 685, 330, 63, 410,
},
// Level 5
[]int{
539, 422, 6, 93, 862, 771, 453, 106, 610, 287, 107, 505, 733, 877,
381, 612, 723, 476, 462, 172, 430, 609, 858, 822, 543, 376, 511,
400, 672, 762, 283, 184, 440, 35, 519, 31, 460, 594, 225, 535, 517,
352, 605, 158, 651, 201, 488, 502, 648, 733, 717, 83, 404, 97, 280,
771, 840, 629, 4, 381, 843, 623, 264, 543,
},
// Level 6
[]int{
521, 310, 864, 547, 858, 580, 296, 379, 53, 779, 897, 444, 400, 925,
749, 415, 822, 93, 217, 208, 928, 244, 583, 620, 246, 148, 447, 631,
292, 908, 490, 704, 516, 258, 457, 907, 594, 723, 674, 292, 272, 96,
684, 432, 686, 606, 860, 569, 193, 219, 129, 186, 236, 287, 192,
775, 278, 173, 40, 379, 712, 463, 646, 776, 171, 491, 297, 763, 156,
732, 95, 270, 447, 90, 507, 48, 228, 821, 808, 898, 784, 663, 627,
378, 382, 262, 380, 602, 754, 336, 89, 614, 87, 432, 670, 616, 157,
374, 242, 726, 600, 269, 375, 898, 845, 454, 354, 130, 814, 587,
804, 34, 211, 330, 539, 297, 827, 865, 37, 517, 834, 315, 550, 86,
801, 4, 108, 539,
},
// Level 7
[]int{
524, 894, 75, 766, 882, 857, 74, 204, 82, 586, 708, 250, 905, 786,
138, 720, 858, 194, 311, 913, 275, 190, 375, 850, 438, 733, 194,
280, 201, 280, 828, 757, 710, 814, 919, 89, 68, 569, 11, 204, 796,
605, 540, 913, 801, 700, 799, 137, 439, 418, 592, 668, 353, 859,
370, 694, 325, 240, 216, 257, 284, 549, 209, 884, 315, 70, 329, 793,
490, 274, 877, 162, 749, 812, 684, 461, 334, 376, 849, 521, 307,
291, 803, 712, 19, 358, 399, 908, 103, 511, 51, 8, 517, 225, 289,
470, 637, 731, 66, 255, 917, 269, 463, 830, 730, 433, 848, 585, 136,
538, 906, 90, 2, 290, 743, 199, 655, 903, 329, 49, 802, 580, 355,
588, 188, 462, 10, 134, 628, 320, 479, 130, 739, 71, 263, 318, 374,
601, 192, 605, 142, 673, 687, 234, 722, 384, 177, 752, 607, 640,
455, 193, 689, 707, 805, 641, 48, 60, 732, 621, 895, 544, 261, 852,
655, 309, 697, 755, 756, 60, 231, 773, 434, 421, 726, 528, 503, 118,
49, 795, 32, 144, 500, 238, 836, 394, 280, 566, 319, 9, 647, 550,
73, 914, 342, 126, 32, 681, 331, 792, 620, 60, 609, 441, 180, 791,
893, 754, 605, 383, 228, 749, 760, 213, 54, 297, 134, 54, 834, 299,
922, 191, 910, 532, 609, 829, 189, 20, 167, 29, 872, 449, 83, 402,
41, 656, 505, 579, 481, 173, 404, 251, 688, 95, 497, 555, 642, 543,
307, 159, 924, 558, 648, 55, 497, 10,
},
// Level 8
[]int{
352, 77, 373, 504, 35, 599, 428, 207, 409, 574, 118, 498, 285, 380,
350, 492, 197, 265, 920, 155, 914, 299, 229, 643, 294, 871, 306, 88,
87, 193, 352, 781, 846, 75, 327, 520, 435, 543, 203, 666, 249, 346,
781, 621, 640, 268, 794, 534, 539, 781, 408, 390, 644, 102, 476,
499, 290, 632, 545, 37, 858, 916, 552, 41, 542, 289, 122, 272, 383,
800, 485, 98, 752, 472, 761, 107, 784, 860, 658, 741, 290, 204, 681,
407, 855, 85, 99, 62, 482, 180, 20, 297, 451, 593, 913, 142, 808,
684, 287, 536, 561, 76, 653, 899, 729, 567, 744, 390, 513, 192, 516,
258, 240, 518, 794, 395, 768, 848, 51, 610, 384, 168, 190, 826, 328,
596, 786, 303, 570, 381, 415, 641, 156, 237, 151, 429, 531, 207,
676, 710, 89, 168, 304, 402, 40, 708, 575, 162, 864, 229, 65, 861,
841, 512, 164, 477, 221, 92, 358, 785, 288, 357, 850, 836, 827, 736,
707, 94, 8, 494, 114, 521, 2, 499, 851, 543, 152, 729, 771, 95, 248,
361, 578, 323, 856, 797, 289, 51, 684, 466, 533, 820, 669, 45, 902,
452, 167, 342, 244, 173, 35, 463, 651, 51, 699, 591, 452, 578, 37,
124, 298, 332, 552, 43, 427, 119, 662, 777, 475, 850, 764, 364, 578,
911, 283, 711, 472, 420, 245, 288, 594, 394, 511, 327, 589, 777,
699, 688, 43, 408, 842, 383, 721, 521, 560, 644, 714, 559, 62, 145,
873, 663, 713, 159, 672, 729, 624, 59, 193, 417, 158, 209, 563, 564,
343, 693, 109, 608, 563, 365, 181, 772, 677, 310, 248, 353, 708,
410, 579, 870, 617, 841, 632, 860, 289, 536, 35, 777, 618, 586, 424,
833, 77, 597, 346, 269, 757, 632, 695, 751, 331, 247, 184, 45, 787,
680, 18, 66, 407, 369, 54, 492, 228, 613, 830, 922, 437, 519, 644,
905, 789, 420, 305, 441, 207, 300, 892, 827, 141, 537, 381, 662,
513, 56, 252, 341, 242, 797, 838, 837, 720, 224, 307, 631, 61, 87,
560, 310, 756, 665, 397, 808, 851, 309, 473, 795, 378, 31, 647, 915,
459, 806, 590, 731, 425, 216, 548, 249, 321, 881, 699, 535, 673,
782, 210, 815, 905, 303, 843, 922, 281, 73, 469, 791, 660, 162, 498,
308, 155, 422, 907, 817, 187, 62, 16, 425, 535, 336, 286, 437, 375,
273, 610, 296, 183, 923, 116, 667, 751, 353, 62, 366, 691, 379, 687,
842, 37, 357, 720, 742, 330, 5, 39, 923, 311, 424, 242, 749, 321,
54, 669, 316, 342, 299, 534, 105, 667, 488, 640, 672, 576, 540, 316,
486, 721, 610, 46, 656, 447, 171, 616, 464, 190, 531, 297, 321, 762,
752, 533, 175, 134, 14, 381, 433, 717, 45, 111, 20, 596, 284, 736,
138, 646, 411, 877, 669, 141, 919, 45, 780, 407, 164, 332, 899, 165,
726, 600, 325, 498, 655, 357, 752, 768, 223, 849, 647, 63, 310, 863,
251, 366, 304, 282, 738, 675, 410, 389, 244, 31, 121, 303, 263,
},
}
func (level securitylevel) Compute(data []int) []int {
// Correction factors for the given level
factors := correctionFactors[int(level)]
// Number of correction code words
count := level.ErrorCorrectionWordCount()
// Correction code words array, prepopulated with zeros
ecWords := make([]int, count)
for _, value := range data {
temp := (value + ecWords[0]) % 929
for i := count - 1; i >= 0; i-- {
add := 0
if i > 0 {
add = ecWords[count-i]
}
ecWords[count-1-i] = (add + 929 - (temp*factors[i])%929) % 929
}
}
for key, word := range ecWords {
if word > 0 {
ecWords[key] = 929 - word
}
}
return ecWords
}

View file

@ -0,0 +1,61 @@
package pdf417
import (
"testing"
)
var inputData = []int{16, 902, 1, 278, 827, 900, 295, 902, 2, 326, 823, 544, 900, 149, 900, 900}
func TestReedSolomonComputeLevel0(t *testing.T) {
var level securitylevel = 0
expected := []int{156, 765}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 0")
}
func TestReedSolomonComputeLevel1(t *testing.T) {
var level securitylevel = 1
expected := []int{168, 875, 63, 355}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 1")
}
func TestReedSolomonComputeLevel2(t *testing.T) {
var level securitylevel = 2
expected := []int{628, 715, 393, 299, 863, 601, 169, 708}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 2")
}
func TestReedSolomonComputeLevel3(t *testing.T) {
var level securitylevel = 3
expected := []int{232, 176, 793, 616, 476, 406, 855, 445, 84, 518, 522, 721, 607, 2, 42, 578}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 3")
}
func TestReedSolomonComputeLevel4(t *testing.T) {
var level securitylevel = 4
expected := []int{281, 156, 276, 668, 44, 252, 877, 30, 549, 856, 773, 639, 420, 330, 693, 329, 283, 723, 480, 482, 102, 925, 535, 892, 374, 472, 837, 331, 343, 608, 390, 364}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 4")
}
func TestReedSolomonComputeLevel5(t *testing.T) {
var level securitylevel = 5
expected := []int{31, 850, 18, 870, 53, 477, 837, 130, 533, 186, 266, 450, 39, 492, 542, 653, 499, 887, 618, 103, 364, 313, 906, 396, 270, 735, 593, 81, 557, 712, 810, 48, 167, 533, 205, 577, 503, 126, 449, 189, 859, 471, 493, 849, 554, 76, 878, 893, 168, 497, 251, 704, 311, 650, 283, 268, 462, 223, 659, 763, 176, 34, 544, 304}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 5")
}
func TestReedSolomonComputeLevel6(t *testing.T) {
var level securitylevel = 6
expected := []int{345, 775, 909, 489, 650, 568, 869, 577, 574, 349, 885, 317, 492, 222, 783, 451, 647, 385, 168, 366, 118, 655, 643, 551, 179, 880, 880, 752, 132, 206, 765, 862, 727, 240, 32, 266, 911, 287, 813, 437, 868, 201, 681, 867, 567, 398, 508, 564, 504, 676, 785, 554, 831, 566, 424, 93, 515, 275, 61, 544, 272, 621, 374, 922, 779, 663, 789, 295, 631, 536, 755, 465, 485, 416, 76, 412, 76, 431, 28, 614, 767, 419, 600, 779, 94, 584, 647, 846, 121, 97, 790, 205, 424, 793, 263, 271, 694, 522, 437, 817, 382, 164, 113, 849, 178, 602, 554, 261, 415, 737, 401, 675, 203, 271, 649, 120, 765, 209, 522, 687, 420, 32, 60, 266, 270, 228, 304, 270}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 6")
}
func TestReedSolomonComputeLevel7(t *testing.T) {
var level securitylevel = 7
expected := []int{142, 203, 799, 4, 105, 137, 793, 914, 225, 636, 60, 171, 490, 180, 414, 141, 399, 599, 829, 288, 108, 268, 444, 481, 795, 146, 655, 778, 189, 32, 597, 206, 208, 711, 845, 608, 642, 636, 540, 795, 845, 466, 492, 659, 138, 800, 912, 171, 92, 438, 225, 301, 777, 449, 230, 448, 326, 182, 892, 681, 543, 582, 732, 758, 162, 587, 685, 378, 646, 356, 354, 25, 839, 839, 556, 253, 501, 771, 745, 616, 473, 293, 669, 822, 613, 684, 229, 265, 110, 438, 144, 727, 317, 605, 414, 497, 82, 278, 267, 323, 43, 894, 624, 282, 790, 579, 430, 255, 802, 553, 922, 604, 68, 692, 809, 909, 663, 589, 735, 670, 298, 158, 201, 68, 124, 64, 67, 338, 694, 373, 225, 579, 309, 699, 920, 432, 717, 72, 126, 819, 142, 755, 473, 630, 331, 758, 730, 65, 359, 451, 236, 16, 56, 31, 87, 587, 125, 385, 384, 197, 352, 383, 173, 271, 38, 558, 810, 260, 521, 680, 7, 319, 650, 334, 695, 708, 0, 562, 365, 204, 114, 185, 560, 746, 767, 449, 797, 688, 63, 135, 818, 805, 3, 536, 908, 532, 400, 698, 49, 212, 630, 93, 157, 275, 3, 20, 611, 179, 302, 282, 876, 665, 241, 206, 474, 80, 217, 460, 462, 751, 719, 571, 536, 794, 522, 385, 598, 756, 162, 212, 758, 662, 361, 223, 587, 857, 503, 382, 615, 86, 283, 541, 847, 518, 406, 736, 486, 408, 226, 342, 784, 772, 211, 888, 234, 335}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 7")
}
func TestReedSolomonComputeLevel8(t *testing.T) {
var level securitylevel = 8
expected := []int{538, 446, 840, 510, 163, 708, 177, 666, 423, 600, 707, 913, 770, 571, 156, 683, 676, 697, 898, 776, 128, 851, 163, 854, 135, 661, 880, 279, 92, 324, 397, 207, 379, 223, 574, 9, 70, 858, 878, 579, 61, 551, 261, 388, 315, 856, 266, 865, 923, 38, 313, 62, 381, 198, 265, 256, 385, 878, 347, 532, 821, 53, 855, 225, 697, 826, 263, 334, 207, 565, 460, 496, 705, 599, 383, 289, 178, 168, 401, 268, 555, 190, 922, 284, 180, 810, 891, 832, 636, 813, 894, 495, 701, 484, 204, 793, 129, 164, 444, 228, 636, 98, 809, 57, 736, 697, 727, 534, 889, 480, 898, 773, 234, 851, 880, 843, 714, 443, 412, 489, 578, 468, 367, 663, 11, 686, 319, 352, 345, 670, 106, 106, 219, 466, 439, 350, 538, 66, 852, 175, 465, 731, 332, 110, 926, 491, 18, 422, 736, 797, 624, 376, 728, 526, 735, 200, 502, 923, 789, 529, 923, 706, 384, 869, 172, 548, 520, 463, 813, 384, 793, 231, 190, 653, 864, 351, 400, 525, 487, 828, 654, 307, 141, 638, 770, 775, 282, 54, 758, 197, 492, 320, 86, 790, 275, 237, 923, 25, 591, 605, 61, 824, 79, 631, 532, 337, 867, 423, 340, 597, 682, 923, 287, 408, 503, 361, 881, 196, 468, 759, 746, 389, 124, 784, 198, 865, 538, 451, 178, 772, 653, 121, 497, 598, 711, 716, 241, 159, 429, 88, 799, 761, 639, 105, 54, 807, 351, 435, 793, 873, 360, 8, 881, 479, 693, 576, 849, 875, 771, 621, 134, 863, 8, 171, 799, 924, 103, 63, 491, 538, 597, 855, 697, 499, 7, 886, 286, 85, 107, 220, 319, 124, 197, 150, 729, 899, 585, 540, 676, 414, 256, 856, 596, 259, 882, 436, 26, 273, 753, 127, 679, 390, 654, 42, 276, 420, 247, 629, 116, 803, 131, 25, 403, 645, 462, 897, 151, 622, 108, 167, 227, 831, 887, 662, 739, 263, 829, 56, 624, 317, 908, 378, 39, 393, 861, 338, 202, 179, 907, 109, 360, 736, 554, 342, 594, 125, 433, 394, 195, 698, 844, 912, 530, 842, 337, 294, 528, 231, 735, 93, 8, 579, 42, 148, 609, 233, 782, 887, 888, 915, 620, 78, 137, 161, 282, 217, 775, 564, 33, 195, 36, 584, 679, 775, 476, 309, 230, 303, 708, 143, 679, 502, 814, 193, 508, 532, 542, 580, 603, 641, 338, 361, 542, 537, 810, 394, 764, 136, 167, 611, 881, 775, 267, 433, 142, 202, 828, 363, 101, 728, 660, 583, 483, 786, 717, 190, 809, 422, 567, 741, 695, 310, 120, 177, 47, 494, 345, 508, 16, 639, 402, 625, 286, 298, 358, 54, 705, 916, 291, 424, 375, 883, 655, 675, 498, 498, 884, 862, 365, 310, 805, 763, 855, 354, 777, 543, 53, 773, 120, 408, 234, 728, 438, 914, 3, 670, 546, 465, 449, 923, 51, 546, 709, 648, 96, 320, 682, 326, 848, 234, 855, 791, 20, 97, 901, 351, 317, 764, 767, 312, 206, 139, 610, 578, 646, 264, 389, 238, 675, 595, 430, 88}
compareIntSlice(t, expected, level.Compute(inputData), "SecLvl 8")
}

354
vendor/github.com/boombuler/barcode/pdf417/highlevel.go generated vendored Normal file
View file

@ -0,0 +1,354 @@
package pdf417
import (
"errors"
"math/big"
"github.com/boombuler/barcode/utils"
)
type encodingMode byte
type subMode byte
const (
encText encodingMode = iota
encNumeric
encBinary
subUpper subMode = iota
subLower
subMixed
subPunct
latch_to_text = 900
latch_to_byte_padded = 901
latch_to_numeric = 902
latch_to_byte = 924
shift_to_byte = 913
min_numeric_count = 13
)
var (
mixedMap map[rune]int
punctMap map[rune]int
)
func init() {
mixedMap = make(map[rune]int)
mixedRaw := []rune{
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 38, 13, 9, 44, 58,
35, 45, 46, 36, 47, 43, 37, 42, 61, 94, 0, 32, 0, 0, 0,
}
for idx, ch := range mixedRaw {
if ch > 0 {
mixedMap[ch] = idx
}
}
punctMap = make(map[rune]int)
punctRaw := []rune{
59, 60, 62, 64, 91, 92, 93, 95, 96, 126, 33, 13, 9, 44, 58,
10, 45, 46, 36, 47, 34, 124, 42, 40, 41, 63, 123, 125, 39, 0,
}
for idx, ch := range punctRaw {
if ch > 0 {
punctMap[ch] = idx
}
}
}
func determineConsecutiveDigitCount(data []rune) int {
cnt := 0
for _, r := range data {
if utils.RuneToInt(r) == -1 {
break
}
cnt++
}
return cnt
}
func encodeNumeric(digits []rune) ([]int, error) {
digitCount := len(digits)
chunkCount := digitCount / 44
if digitCount%44 != 0 {
chunkCount++
}
codeWords := []int{}
for i := 0; i < chunkCount; i++ {
start := i * 44
end := start + 44
if end > digitCount {
end = digitCount
}
chunk := digits[start:end]
chunkNum := big.NewInt(0)
_, ok := chunkNum.SetString("1"+string(chunk), 10)
if !ok {
return nil, errors.New("Failed converting: " + string(chunk))
}
cws := []int{}
for chunkNum.Cmp(big.NewInt(0)) > 0 {
newChunk, cw := chunkNum.DivMod(chunkNum, big.NewInt(900), big.NewInt(0))
chunkNum = newChunk
cws = append([]int{int(cw.Int64())}, cws...)
}
codeWords = append(codeWords, cws...)
}
return codeWords, nil
}
func determineConsecutiveTextCount(msg []rune) int {
result := 0
isText := func(ch rune) bool {
return ch == '\t' || ch == '\n' || ch == '\r' || (ch >= 32 && ch <= 126)
}
for i, ch := range msg {
numericCount := determineConsecutiveDigitCount(msg[i:])
if numericCount >= min_numeric_count || (numericCount == 0 && !isText(ch)) {
break
}
result++
}
return result
}
func encodeText(text []rune, submode subMode) (subMode, []int) {
isAlphaUpper := func(ch rune) bool {
return ch == ' ' || (ch >= 'A' && ch <= 'Z')
}
isAlphaLower := func(ch rune) bool {
return ch == ' ' || (ch >= 'a' && ch <= 'z')
}
isMixed := func(ch rune) bool {
_, ok := mixedMap[ch]
return ok
}
isPunctuation := func(ch rune) bool {
_, ok := punctMap[ch]
return ok
}
idx := 0
var tmp []int
for idx < len(text) {
ch := text[idx]
switch submode {
case subUpper:
if isAlphaUpper(ch) {
if ch == ' ' {
tmp = append(tmp, 26) //space
} else {
tmp = append(tmp, int(ch-'A'))
}
} else {
if isAlphaLower(ch) {
submode = subLower
tmp = append(tmp, 27) // lower latch
continue
} else if isMixed(ch) {
submode = subMixed
tmp = append(tmp, 28) // mixed latch
continue
} else {
tmp = append(tmp, 29) // punctuation switch
tmp = append(tmp, punctMap[ch])
break
}
}
break
case subLower:
if isAlphaLower(ch) {
if ch == ' ' {
tmp = append(tmp, 26) //space
} else {
tmp = append(tmp, int(ch-'a'))
}
} else {
if isAlphaUpper(ch) {
tmp = append(tmp, 27) //upper switch
tmp = append(tmp, int(ch-'A'))
break
} else if isMixed(ch) {
submode = subMixed
tmp = append(tmp, 28) //mixed latch
continue
} else {
tmp = append(tmp, 29) //punctuation switch
tmp = append(tmp, punctMap[ch])
break
}
}
break
case subMixed:
if isMixed(ch) {
tmp = append(tmp, mixedMap[ch])
} else {
if isAlphaUpper(ch) {
submode = subUpper
tmp = append(tmp, 28) //upper latch
continue
} else if isAlphaLower(ch) {
submode = subLower
tmp = append(tmp, 27) //lower latch
continue
} else {
if idx+1 < len(text) {
next := text[idx+1]
if isPunctuation(next) {
submode = subPunct
tmp = append(tmp, 25) //punctuation latch
continue
}
}
tmp = append(tmp, 29) //punctuation switch
tmp = append(tmp, punctMap[ch])
}
}
break
default: //subPunct
if isPunctuation(ch) {
tmp = append(tmp, punctMap[ch])
} else {
submode = subUpper
tmp = append(tmp, 29) //upper latch
continue
}
}
idx++
}
h := 0
result := []int{}
for i, val := range tmp {
if i%2 != 0 {
h = (h * 30) + val
result = append(result, h)
} else {
h = val
}
}
if len(tmp)%2 != 0 {
result = append(result, (h*30)+29)
}
return submode, result
}
func determineConsecutiveBinaryCount(msg []byte) int {
result := 0
for i, _ := range msg {
numericCount := determineConsecutiveDigitCount([]rune(string(msg[i:])))
if numericCount >= min_numeric_count {
break
}
textCount := determineConsecutiveTextCount([]rune(string(msg[i:])))
if textCount > 5 {
break
}
result++
}
return result
}
func encodeBinary(data []byte, startmode encodingMode) []int {
result := []int{}
count := len(data)
if count == 1 && startmode == encText {
result = append(result, shift_to_byte)
} else if (count % 6) == 0 {
result = append(result, latch_to_byte)
} else {
result = append(result, latch_to_byte_padded)
}
idx := 0
// Encode sixpacks
if count >= 6 {
words := make([]int, 5)
for (count - idx) >= 6 {
var t int64 = 0
for i := 0; i < 6; i++ {
t = t << 8
t += int64(data[idx+i])
}
for i := 0; i < 5; i++ {
words[4-i] = int(t % 900)
t = t / 900
}
result = append(result, words...)
idx += 6
}
}
//Encode rest (remaining n<5 bytes if any)
for i := idx; i < count; i++ {
result = append(result, int(data[i]&0xff))
}
return result
}
func highlevelEncode(dataStr string) ([]int, error) {
encodingMode := encText
textSubMode := subUpper
result := []int{}
data := []byte(dataStr)
for len(data) > 0 {
numericCount := determineConsecutiveDigitCount([]rune(string(data)))
if numericCount >= min_numeric_count || numericCount == len(data) {
result = append(result, latch_to_numeric)
encodingMode = encNumeric
textSubMode = subUpper
numData, err := encodeNumeric([]rune(string(data[:numericCount])))
if err != nil {
return nil, err
}
result = append(result, numData...)
data = data[numericCount:]
} else {
textCount := determineConsecutiveTextCount([]rune(string(data)))
if textCount >= 5 || textCount == len(data) {
if encodingMode != encText {
result = append(result, latch_to_text)
encodingMode = encText
textSubMode = subUpper
}
var txtData []int
textSubMode, txtData = encodeText([]rune(string(data[:textCount])), textSubMode)
result = append(result, txtData...)
data = data[textCount:]
} else {
binaryCount := determineConsecutiveBinaryCount(data)
if binaryCount == 0 {
binaryCount = 1
}
bytes := data[:binaryCount]
if len(bytes) != 1 || encodingMode != encText {
encodingMode = encBinary
textSubMode = subUpper
}
byteData := encodeBinary(bytes, encodingMode)
result = append(result, byteData...)
data = data[binaryCount:]
}
}
}
return result, nil
}

View file

@ -0,0 +1,41 @@
package pdf417
import "testing"
func compareIntSlice(t *testing.T, expected, actual []int, testStr string) {
if len(actual) != len(expected) {
t.Errorf("Invalid slice size. Expected %d got %d while encoding %q", len(expected), len(actual), testStr)
return
}
for i, a := range actual {
if e := expected[i]; e != a {
t.Errorf("Unexpected value at position %d. Expected %d got %d while encoding %q", i, e, a, testStr)
}
}
}
func TestHighlevelEncode(t *testing.T) {
runTest := func(msg string, expected ...int) {
if codes, err := highlevelEncode(msg); err != nil {
t.Error(err)
} else {
compareIntSlice(t, expected, codes, msg)
}
}
runTest("01234", 902, 112, 434)
runTest("Super !", 567, 615, 137, 809, 329)
runTest("Super ", 567, 615, 137, 809)
runTest("ABC123", 1, 88, 32, 119)
runTest("123ABC", 841, 63, 840, 32)
}
func TestBinaryEncoder(t *testing.T) {
runTest := func(msg string, expected ...int) {
codes := encodeBinary([]byte(msg), encText)
compareIntSlice(t, expected, codes, msg)
}
runTest("alcool", 924, 163, 238, 432, 766, 244)
runTest("alcoolique", 901, 163, 238, 432, 766, 244, 105, 113, 117, 101)
}

40
vendor/github.com/boombuler/barcode/pdf417/pdfcode.go generated vendored Normal file
View file

@ -0,0 +1,40 @@
package pdf417
import (
"image"
"image/color"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type pdfBarcode struct {
data string
width int
code *utils.BitList
}
func (c *pdfBarcode) Metadata() barcode.Metadata {
return barcode.Metadata{barcode.TypePDF, 2}
}
func (c *pdfBarcode) Content() string {
return c.data
}
func (c *pdfBarcode) ColorModel() color.Model {
return color.Gray16Model
}
func (c *pdfBarcode) Bounds() image.Rectangle {
height := c.code.Len() / c.width
return image.Rect(0, 0, c.width, height*moduleHeight)
}
func (c *pdfBarcode) At(x, y int) color.Color {
if c.code.GetBit((y/moduleHeight)*c.width + x) {
return color.Black
}
return color.White
}

66
vendor/github.com/boombuler/barcode/qr/alphanumeric.go generated vendored Normal file
View file

@ -0,0 +1,66 @@
package qr
import (
"errors"
"fmt"
"strings"
"github.com/boombuler/barcode/utils"
)
const charSet string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:"
func stringToAlphaIdx(content string) <-chan int {
result := make(chan int)
go func() {
for _, r := range content {
idx := strings.IndexRune(charSet, r)
result <- idx
if idx < 0 {
break
}
}
close(result)
}()
return result
}
func encodeAlphaNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
contentLenIsOdd := len(content)%2 == 1
contentBitCount := (len(content) / 2) * 11
if contentLenIsOdd {
contentBitCount += 6
}
vi := findSmallestVersionInfo(ecl, alphaNumericMode, contentBitCount)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
res := new(utils.BitList)
res.AddBits(int(alphaNumericMode), 4)
res.AddBits(len(content), vi.charCountBits(alphaNumericMode))
encoder := stringToAlphaIdx(content)
for idx := 0; idx < len(content)/2; idx++ {
c1 := <-encoder
c2 := <-encoder
if c1 < 0 || c2 < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
}
res.AddBits(c1*45+c2, 11)
}
if contentLenIsOdd {
c := <-encoder
if c < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, AlphaNumeric)
}
res.AddBits(c, 6)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}

View file

@ -0,0 +1,44 @@
package qr
import (
"bytes"
"testing"
)
func makeString(length int, content string) string {
res := ""
for i := 0; i < length; i++ {
res += content
}
return res
}
func Test_AlphaNumericEncoding(t *testing.T) {
encode := AlphaNumeric.getEncoder()
x, vi, err := encode("HELLO WORLD", M)
if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{32, 91, 11, 120, 209, 114, 220, 77, 67, 64, 236, 17, 236, 17, 236, 17}) != 0 {
t.Errorf("\"HELLO WORLD\" failed to encode: %s", err)
}
x, vi, err = encode(makeString(4296, "A"), L)
if x == nil || vi == nil || err != nil {
t.Fail()
}
x, vi, err = encode(makeString(4297, "A"), L)
if x != nil || vi != nil || err == nil {
t.Fail()
}
x, vi, err = encode("ABc", L)
if x != nil || vi != nil || err == nil {
t.Fail()
}
x, vi, err = encode("hello world", M)
if x != nil || vi != nil || err == nil {
t.Error("\"hello world\" should not be encodable in alphanumeric mode")
}
}

23
vendor/github.com/boombuler/barcode/qr/automatic.go generated vendored Normal file
View file

@ -0,0 +1,23 @@
package qr
import (
"fmt"
"github.com/boombuler/barcode/utils"
)
func encodeAuto(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
bits, vi, _ := Numeric.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
bits, vi, _ = AlphaNumeric.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
bits, vi, _ = Unicode.getEncoder()(content, ecl)
if bits != nil && vi != nil {
return bits, vi, nil
}
return nil, nil, fmt.Errorf("No encoding found to encode \"%s\"", content)
}

View file

@ -0,0 +1,30 @@
package qr
import (
"bytes"
"testing"
)
func Test_AutomaticEncoding(t *testing.T) {
tests := map[string]encodeFn{
"0123456789": Numeric.getEncoder(),
"ALPHA NUMERIC": AlphaNumeric.getEncoder(),
"unicode encoing": Unicode.getEncoder(),
"very long unicode encoding" + makeString(3000, "A"): nil,
}
for str, enc := range tests {
testValue, _, _ := Auto.getEncoder()(str, M)
if enc != nil {
correctValue, _, _ := enc(str, M)
if testValue == nil || bytes.Compare(correctValue.GetBytes(), testValue.GetBytes()) != 0 {
t.Errorf("wrong encoding used for '%s'", str)
}
} else {
if testValue != nil {
t.Errorf("wrong encoding used for '%s'", str)
}
}
}
}

59
vendor/github.com/boombuler/barcode/qr/blocks.go generated vendored Normal file
View file

@ -0,0 +1,59 @@
package qr
type block struct {
data []byte
ecc []byte
}
type blockList []*block
func splitToBlocks(data <-chan byte, vi *versionInfo) blockList {
result := make(blockList, vi.NumberOfBlocksInGroup1+vi.NumberOfBlocksInGroup2)
for b := 0; b < int(vi.NumberOfBlocksInGroup1); b++ {
blk := new(block)
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup1)
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup1); cw++ {
blk.data[cw] = <-data
}
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
result[b] = blk
}
for b := 0; b < int(vi.NumberOfBlocksInGroup2); b++ {
blk := new(block)
blk.data = make([]byte, vi.DataCodeWordsPerBlockInGroup2)
for cw := 0; cw < int(vi.DataCodeWordsPerBlockInGroup2); cw++ {
blk.data[cw] = <-data
}
blk.ecc = ec.calcECC(blk.data, vi.ErrorCorrectionCodewordsPerBlock)
result[int(vi.NumberOfBlocksInGroup1)+b] = blk
}
return result
}
func (bl blockList) interleave(vi *versionInfo) []byte {
var maxCodewordCount int
if vi.DataCodeWordsPerBlockInGroup1 > vi.DataCodeWordsPerBlockInGroup2 {
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup1)
} else {
maxCodewordCount = int(vi.DataCodeWordsPerBlockInGroup2)
}
resultLen := (vi.DataCodeWordsPerBlockInGroup1+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup1 +
(vi.DataCodeWordsPerBlockInGroup2+vi.ErrorCorrectionCodewordsPerBlock)*vi.NumberOfBlocksInGroup2
result := make([]byte, 0, resultLen)
for i := 0; i < maxCodewordCount; i++ {
for b := 0; b < len(bl); b++ {
if len(bl[b].data) > i {
result = append(result, bl[b].data[i])
}
}
}
for i := 0; i < int(vi.ErrorCorrectionCodewordsPerBlock); i++ {
for b := 0; b < len(bl); b++ {
result = append(result, bl[b].ecc[i])
}
}
return result
}

36
vendor/github.com/boombuler/barcode/qr/blocks_test.go generated vendored Normal file
View file

@ -0,0 +1,36 @@
package qr
import (
"bytes"
"testing"
)
func Test_Blocks(t *testing.T) {
byteIt := make(chan byte)
go func() {
for _, b := range []byte{67, 85, 70, 134, 87, 38, 85, 194, 119, 50, 6, 18, 6, 103, 38, 246, 246, 66, 7, 118, 134, 242, 7, 38, 86, 22, 198, 199, 146, 6, 182, 230, 247, 119, 50, 7, 118, 134, 87, 38, 82, 6, 134, 151, 50, 7, 70, 247, 118, 86, 194, 6, 151, 50, 16, 236, 17, 236, 17, 236, 17, 236} {
byteIt <- b
}
close(byteIt)
}()
vi := &versionInfo{5, Q, 18, 2, 15, 2, 16}
data := splitToBlocks(byteIt, vi).interleave(vi)
if bytes.Compare(data, []byte{67, 246, 182, 70, 85, 246, 230, 247, 70, 66, 247, 118, 134, 7, 119, 86, 87, 118, 50, 194, 38, 134, 7, 6, 85, 242, 118, 151, 194, 7, 134, 50, 119, 38, 87, 16, 50, 86, 38, 236, 6, 22, 82, 17, 18, 198, 6, 236, 6, 199, 134, 17, 103, 146, 151, 236, 38, 6, 50, 17, 7, 236, 213, 87, 148, 235, 199, 204, 116, 159, 11, 96, 177, 5, 45, 60, 212, 173, 115, 202, 76, 24, 247, 182, 133, 147, 241, 124, 75, 59, 223, 157, 242, 33, 229, 200, 238, 106, 248, 134, 76, 40, 154, 27, 195, 255, 117, 129, 230, 172, 154, 209, 189, 82, 111, 17, 10, 2, 86, 163, 108, 131, 161, 163, 240, 32, 111, 120, 192, 178, 39, 133, 141, 236}) != 0 {
t.Fail()
}
byteIt2 := make(chan byte)
go func() {
for _, b := range []byte{67, 85, 70, 134, 87, 38, 85, 194, 119, 50, 6, 18, 6, 103, 38, 246, 246, 66, 7, 118, 134, 242, 7, 38, 86, 22, 198, 199, 146, 6, 182, 230, 247, 119, 50, 7, 118, 134, 87, 38, 82, 6, 134, 151, 50, 7, 70, 247, 118, 86, 194, 6, 151, 50, 16, 236, 17, 236, 17, 236, 17, 236} {
byteIt2 <- b
}
close(byteIt2)
}()
vi = &versionInfo{5, Q, 18, 2, 16, 2, 15}
data = splitToBlocks(byteIt2, vi).interleave(vi)
if bytes.Compare(data, []byte{67, 246, 247, 247, 85, 66, 119, 118, 70, 7, 50, 86, 134, 118, 7, 194, 87, 134, 118, 6, 38, 242, 134, 151, 85, 7, 87, 50, 194, 38, 38, 16, 119, 86, 82, 236, 50, 22, 6, 17, 6, 198, 134, 236, 18, 199, 151, 17, 6, 146, 50, 236, 103, 6, 7, 17, 38, 182, 70, 236, 246, 230, 71, 101, 27, 62, 13, 91, 166, 86, 138, 16, 78, 229, 102, 11, 199, 107, 2, 182, 132, 103, 89, 66, 136, 69, 78, 255, 116, 129, 126, 163, 219, 234, 158, 216, 42, 234, 97, 62, 186, 59, 123, 148, 220, 191, 254, 145, 82, 95, 129, 79, 236, 254, 30, 174, 228, 50, 181, 110, 150, 205, 34, 235, 242, 0, 115, 147, 58, 243, 28, 140, 221, 219}) != 0 {
t.Fail()
}
}

416
vendor/github.com/boombuler/barcode/qr/encoder.go generated vendored Normal file
View file

@ -0,0 +1,416 @@
// Package qr can be used to create QR barcodes.
package qr
import (
"image"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type encodeFn func(content string, eccLevel ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error)
// Encoding mode for QR Codes.
type Encoding byte
const (
// Auto will choose ths best matching encoding
Auto Encoding = iota
// Numeric encoding only encodes numbers [0-9]
Numeric
// AlphaNumeric encoding only encodes uppercase letters, numbers and [Space], $, %, *, +, -, ., /, :
AlphaNumeric
// Unicode encoding encodes the string as utf-8
Unicode
// only for testing purpose
unknownEncoding
)
func (e Encoding) getEncoder() encodeFn {
switch e {
case Auto:
return encodeAuto
case Numeric:
return encodeNumeric
case AlphaNumeric:
return encodeAlphaNumeric
case Unicode:
return encodeUnicode
}
return nil
}
func (e Encoding) String() string {
switch e {
case Auto:
return "Auto"
case Numeric:
return "Numeric"
case AlphaNumeric:
return "AlphaNumeric"
case Unicode:
return "Unicode"
}
return ""
}
// Encode returns a QR barcode with the given content, error correction level and uses the given encoding
func Encode(content string, level ErrorCorrectionLevel, mode Encoding) (barcode.Barcode, error) {
bits, vi, err := mode.getEncoder()(content, level)
if err != nil {
return nil, err
}
blocks := splitToBlocks(bits.IterateBytes(), vi)
data := blocks.interleave(vi)
result := render(data, vi)
result.content = content
return result, nil
}
func render(data []byte, vi *versionInfo) *qrcode {
dim := vi.modulWidth()
results := make([]*qrcode, 8)
for i := 0; i < 8; i++ {
results[i] = newBarcode(dim)
}
occupied := newBarcode(dim)
setAll := func(x int, y int, val bool) {
occupied.Set(x, y, true)
for i := 0; i < 8; i++ {
results[i].Set(x, y, val)
}
}
drawFinderPatterns(vi, setAll)
drawAlignmentPatterns(occupied, vi, setAll)
//Timing Pattern:
var i int
for i = 0; i < dim; i++ {
if !occupied.Get(i, 6) {
setAll(i, 6, i%2 == 0)
}
if !occupied.Get(6, i) {
setAll(6, i, i%2 == 0)
}
}
// Dark Module
setAll(8, dim-8, true)
drawVersionInfo(vi, setAll)
drawFormatInfo(vi, -1, occupied.Set)
for i := 0; i < 8; i++ {
drawFormatInfo(vi, i, results[i].Set)
}
// Write the data
var curBitNo int
for pos := range iterateModules(occupied) {
var curBit bool
if curBitNo < len(data)*8 {
curBit = ((data[curBitNo/8] >> uint(7-(curBitNo%8))) & 1) == 1
} else {
curBit = false
}
for i := 0; i < 8; i++ {
setMasked(pos.X, pos.Y, curBit, i, results[i].Set)
}
curBitNo++
}
lowestPenalty := ^uint(0)
lowestPenaltyIdx := -1
for i := 0; i < 8; i++ {
p := results[i].calcPenalty()
if p < lowestPenalty {
lowestPenalty = p
lowestPenaltyIdx = i
}
}
return results[lowestPenaltyIdx]
}
func setMasked(x, y int, val bool, mask int, set func(int, int, bool)) {
switch mask {
case 0:
val = val != (((y + x) % 2) == 0)
break
case 1:
val = val != ((y % 2) == 0)
break
case 2:
val = val != ((x % 3) == 0)
break
case 3:
val = val != (((y + x) % 3) == 0)
break
case 4:
val = val != (((y/2 + x/3) % 2) == 0)
break
case 5:
val = val != (((y*x)%2)+((y*x)%3) == 0)
break
case 6:
val = val != ((((y*x)%2)+((y*x)%3))%2 == 0)
break
case 7:
val = val != ((((y+x)%2)+((y*x)%3))%2 == 0)
}
set(x, y, val)
}
func iterateModules(occupied *qrcode) <-chan image.Point {
result := make(chan image.Point)
allPoints := make(chan image.Point)
go func() {
curX := occupied.dimension - 1
curY := occupied.dimension - 1
isUpward := true
for true {
if isUpward {
allPoints <- image.Pt(curX, curY)
allPoints <- image.Pt(curX-1, curY)
curY--
if curY < 0 {
curY = 0
curX -= 2
if curX == 6 {
curX--
}
if curX < 0 {
break
}
isUpward = false
}
} else {
allPoints <- image.Pt(curX, curY)
allPoints <- image.Pt(curX-1, curY)
curY++
if curY >= occupied.dimension {
curY = occupied.dimension - 1
curX -= 2
if curX == 6 {
curX--
}
isUpward = true
if curX < 0 {
break
}
}
}
}
close(allPoints)
}()
go func() {
for pt := range allPoints {
if !occupied.Get(pt.X, pt.Y) {
result <- pt
}
}
close(result)
}()
return result
}
func drawFinderPatterns(vi *versionInfo, set func(int, int, bool)) {
dim := vi.modulWidth()
drawPattern := func(xoff int, yoff int) {
for x := -1; x < 8; x++ {
for y := -1; y < 8; y++ {
val := (x == 0 || x == 6 || y == 0 || y == 6 || (x > 1 && x < 5 && y > 1 && y < 5)) && (x <= 6 && y <= 6 && x >= 0 && y >= 0)
if x+xoff >= 0 && x+xoff < dim && y+yoff >= 0 && y+yoff < dim {
set(x+xoff, y+yoff, val)
}
}
}
}
drawPattern(0, 0)
drawPattern(0, dim-7)
drawPattern(dim-7, 0)
}
func drawAlignmentPatterns(occupied *qrcode, vi *versionInfo, set func(int, int, bool)) {
drawPattern := func(xoff int, yoff int) {
for x := -2; x <= 2; x++ {
for y := -2; y <= 2; y++ {
val := x == -2 || x == 2 || y == -2 || y == 2 || (x == 0 && y == 0)
set(x+xoff, y+yoff, val)
}
}
}
positions := vi.alignmentPatternPlacements()
for _, x := range positions {
for _, y := range positions {
if occupied.Get(x, y) {
continue
}
drawPattern(x, y)
}
}
}
var formatInfos = map[ErrorCorrectionLevel]map[int][]bool{
L: {
0: []bool{true, true, true, false, true, true, true, true, true, false, false, false, true, false, false},
1: []bool{true, true, true, false, false, true, false, true, true, true, true, false, false, true, true},
2: []bool{true, true, true, true, true, false, true, true, false, true, false, true, false, true, false},
3: []bool{true, true, true, true, false, false, false, true, false, false, true, true, true, false, true},
4: []bool{true, true, false, false, true, true, false, false, false, true, false, true, true, true, true},
5: []bool{true, true, false, false, false, true, true, false, false, false, true, true, false, false, false},
6: []bool{true, true, false, true, true, false, false, false, true, false, false, false, false, false, true},
7: []bool{true, true, false, true, false, false, true, false, true, true, true, false, true, true, false},
},
M: {
0: []bool{true, false, true, false, true, false, false, false, false, false, true, false, false, true, false},
1: []bool{true, false, true, false, false, false, true, false, false, true, false, false, true, false, true},
2: []bool{true, false, true, true, true, true, false, false, true, true, true, true, true, false, false},
3: []bool{true, false, true, true, false, true, true, false, true, false, false, true, false, true, true},
4: []bool{true, false, false, false, true, false, true, true, true, true, true, true, false, false, true},
5: []bool{true, false, false, false, false, false, false, true, true, false, false, true, true, true, false},
6: []bool{true, false, false, true, true, true, true, true, false, false, true, false, true, true, true},
7: []bool{true, false, false, true, false, true, false, true, false, true, false, false, false, false, false},
},
Q: {
0: []bool{false, true, true, false, true, false, true, false, true, false, true, true, true, true, true},
1: []bool{false, true, true, false, false, false, false, false, true, true, false, true, false, false, false},
2: []bool{false, true, true, true, true, true, true, false, false, true, true, false, false, false, true},
3: []bool{false, true, true, true, false, true, false, false, false, false, false, false, true, true, false},
4: []bool{false, true, false, false, true, false, false, true, false, true, true, false, true, false, false},
5: []bool{false, true, false, false, false, false, true, true, false, false, false, false, false, true, true},
6: []bool{false, true, false, true, true, true, false, true, true, false, true, true, false, true, false},
7: []bool{false, true, false, true, false, true, true, true, true, true, false, true, true, false, true},
},
H: {
0: []bool{false, false, true, false, true, true, false, true, false, false, false, true, false, false, true},
1: []bool{false, false, true, false, false, true, true, true, false, true, true, true, true, true, false},
2: []bool{false, false, true, true, true, false, false, true, true, true, false, false, true, true, true},
3: []bool{false, false, true, true, false, false, true, true, true, false, true, false, false, false, false},
4: []bool{false, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
5: []bool{false, false, false, false, false, true, false, false, true, false, true, false, true, false, true},
6: []bool{false, false, false, true, true, false, true, false, false, false, false, true, true, false, false},
7: []bool{false, false, false, true, false, false, false, false, false, true, true, true, false, true, true},
},
}
func drawFormatInfo(vi *versionInfo, usedMask int, set func(int, int, bool)) {
var formatInfo []bool
if usedMask == -1 {
formatInfo = []bool{true, true, true, true, true, true, true, true, true, true, true, true, true, true, true} // Set all to true cause -1 --> occupied mask.
} else {
formatInfo = formatInfos[vi.Level][usedMask]
}
if len(formatInfo) == 15 {
dim := vi.modulWidth()
set(0, 8, formatInfo[0])
set(1, 8, formatInfo[1])
set(2, 8, formatInfo[2])
set(3, 8, formatInfo[3])
set(4, 8, formatInfo[4])
set(5, 8, formatInfo[5])
set(7, 8, formatInfo[6])
set(8, 8, formatInfo[7])
set(8, 7, formatInfo[8])
set(8, 5, formatInfo[9])
set(8, 4, formatInfo[10])
set(8, 3, formatInfo[11])
set(8, 2, formatInfo[12])
set(8, 1, formatInfo[13])
set(8, 0, formatInfo[14])
set(8, dim-1, formatInfo[0])
set(8, dim-2, formatInfo[1])
set(8, dim-3, formatInfo[2])
set(8, dim-4, formatInfo[3])
set(8, dim-5, formatInfo[4])
set(8, dim-6, formatInfo[5])
set(8, dim-7, formatInfo[6])
set(dim-8, 8, formatInfo[7])
set(dim-7, 8, formatInfo[8])
set(dim-6, 8, formatInfo[9])
set(dim-5, 8, formatInfo[10])
set(dim-4, 8, formatInfo[11])
set(dim-3, 8, formatInfo[12])
set(dim-2, 8, formatInfo[13])
set(dim-1, 8, formatInfo[14])
}
}
var versionInfoBitsByVersion = map[byte][]bool{
7: []bool{false, false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false},
8: []bool{false, false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false},
9: []bool{false, false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true},
10: []bool{false, false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true},
11: []bool{false, false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false},
12: []bool{false, false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false},
13: []bool{false, false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true},
14: []bool{false, false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true},
15: []bool{false, false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false},
16: []bool{false, true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false},
17: []bool{false, true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true},
18: []bool{false, true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true},
19: []bool{false, true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false},
20: []bool{false, true, false, true, false, false, true, false, false, true, true, false, true, false, false, true, true, false},
21: []bool{false, true, false, true, false, true, false, true, true, false, true, false, false, false, false, false, true, true},
22: []bool{false, true, false, true, true, false, true, false, false, false, true, true, false, false, true, false, false, true},
23: []bool{false, true, false, true, true, true, false, true, true, true, true, true, true, false, true, true, false, false},
24: []bool{false, true, true, false, false, false, true, true, true, false, true, true, false, false, false, true, false, false},
25: []bool{false, true, true, false, false, true, false, false, false, true, true, true, true, false, false, false, false, true},
26: []bool{false, true, true, false, true, false, true, true, true, true, true, false, true, false, true, false, true, true},
27: []bool{false, true, true, false, true, true, false, false, false, false, true, false, false, false, true, true, true, false},
28: []bool{false, true, true, true, false, false, true, true, false, false, false, false, false, true, true, false, true, false},
29: []bool{false, true, true, true, false, true, false, false, true, true, false, false, true, true, true, true, true, true},
30: []bool{false, true, true, true, true, false, true, true, false, true, false, true, true, true, false, true, false, true},
31: []bool{false, true, true, true, true, true, false, false, true, false, false, true, false, true, false, false, false, false},
32: []bool{true, false, false, false, false, false, true, false, false, true, true, true, false, true, false, true, false, true},
33: []bool{true, false, false, false, false, true, false, true, true, false, true, true, true, true, false, false, false, false},
34: []bool{true, false, false, false, true, false, true, false, false, false, true, false, true, true, true, false, true, false},
35: []bool{true, false, false, false, true, true, false, true, true, true, true, false, false, true, true, true, true, true},
36: []bool{true, false, false, true, false, false, true, false, true, true, false, false, false, false, true, false, true, true},
37: []bool{true, false, false, true, false, true, false, true, false, false, false, false, true, false, true, true, true, false},
38: []bool{true, false, false, true, true, false, true, false, true, false, false, true, true, false, false, true, false, false},
39: []bool{true, false, false, true, true, true, false, true, false, true, false, true, false, false, false, false, false, true},
40: []bool{true, false, true, false, false, false, true, true, false, false, false, true, true, false, true, false, false, true},
}
func drawVersionInfo(vi *versionInfo, set func(int, int, bool)) {
versionInfoBits, ok := versionInfoBitsByVersion[vi.Version]
if ok && len(versionInfoBits) > 0 {
for i := 0; i < len(versionInfoBits); i++ {
x := (vi.modulWidth() - 11) + i%3
y := i / 3
set(x, y, versionInfoBits[len(versionInfoBits)-i-1])
set(y, x, versionInfoBits[len(versionInfoBits)-i-1])
}
}
}
func addPaddingAndTerminator(bl *utils.BitList, vi *versionInfo) {
for i := 0; i < 4 && bl.Len() < vi.totalDataBytes()*8; i++ {
bl.AddBit(false)
}
for bl.Len()%8 != 0 {
bl.AddBit(false)
}
for i := 0; bl.Len() < vi.totalDataBytes()*8; i++ {
if i%2 == 0 {
bl.AddByte(236)
} else {
bl.AddByte(17)
}
}
}

134
vendor/github.com/boombuler/barcode/qr/encoder_test.go generated vendored Normal file
View file

@ -0,0 +1,134 @@
package qr
import (
"fmt"
"image/png"
"os"
"testing"
"github.com/boombuler/barcode"
)
type test struct {
Text string
Mode Encoding
ECL ErrorCorrectionLevel
Result string
}
var tests = []test{
test{
Text: "hello world",
Mode: Unicode,
ECL: H,
Result: `
+++++++.+.+.+...+.+++++++
+.....+.++...+++..+.....+
+.+++.+.+.+.++.++.+.+++.+
+.+++.+....++.++..+.+++.+
+.+++.+..+...++.+.+.+++.+
+.....+.+..+..+++.+.....+
+++++++.+.+.+.+.+.+++++++
........++..+..+.........
..+++.+.+++.+.++++++..+++
+++..+..+...++.+...+..+..
+...+.++++....++.+..++.++
++.+.+.++...+...+.+....++
..+..+++.+.+++++.++++++++
+.+++...+..++..++..+..+..
+.....+..+.+.....+++++.++
+.+++.....+...+.+.+++...+
+.+..+++...++.+.+++++++..
........+....++.+...+.+..
+++++++......++++.+.+.+++
+.....+....+...++...++.+.
+.+++.+.+.+...+++++++++..
+.+++.+.++...++...+.++..+
+.+++.+.++.+++++..++.+..+
+.....+..+++..++.+.++...+
+++++++....+..+.+..+..+++`,
},
}
func Test_GetUnknownEncoder(t *testing.T) {
if unknownEncoding.getEncoder() != nil {
t.Fail()
}
}
func Test_EncodingStringer(t *testing.T) {
tests := map[Encoding]string{
Auto: "Auto",
Numeric: "Numeric",
AlphaNumeric: "AlphaNumeric",
Unicode: "Unicode",
unknownEncoding: "",
}
for enc, str := range tests {
if enc.String() != str {
t.Fail()
}
}
}
func Test_InvalidEncoding(t *testing.T) {
_, err := Encode("hello world", H, Numeric)
if err == nil {
t.Fail()
}
}
func imgStrToBools(str string) []bool {
res := make([]bool, 0, len(str))
for _, r := range str {
if r == '+' {
res = append(res, true)
} else if r == '.' {
res = append(res, false)
}
}
return res
}
func Test_Encode(t *testing.T) {
for _, tst := range tests {
res, err := Encode(tst.Text, tst.ECL, tst.Mode)
if err != nil {
t.Error(err)
}
qrCode, ok := res.(*qrcode)
if !ok {
t.Fail()
}
testRes := imgStrToBools(tst.Result)
if (qrCode.dimension * qrCode.dimension) != len(testRes) {
t.Fail()
}
t.Logf("dim %d", qrCode.dimension)
for i := 0; i < len(testRes); i++ {
x := i % qrCode.dimension
y := i / qrCode.dimension
if qrCode.Get(x, y) != testRes[i] {
t.Errorf("Failed at index %d", i)
}
}
}
}
func ExampleEncode() {
f, _ := os.Create("qrcode.png")
defer f.Close()
qrcode, err := Encode("hello world", L, Auto)
if err != nil {
fmt.Println(err)
} else {
qrcode, err = barcode.Scale(qrcode, 100, 100)
if err != nil {
fmt.Println(err)
} else {
png.Encode(f, qrcode)
}
}
}

View file

@ -0,0 +1,29 @@
package qr
import (
"github.com/boombuler/barcode/utils"
)
type errorCorrection struct {
rs *utils.ReedSolomonEncoder
}
var ec = newErrorCorrection()
func newErrorCorrection() *errorCorrection {
fld := utils.NewGaloisField(285, 256, 0)
return &errorCorrection{utils.NewReedSolomonEncoder(fld)}
}
func (ec *errorCorrection) calcECC(data []byte, eccCount byte) []byte {
dataInts := make([]int, len(data))
for i := 0; i < len(data); i++ {
dataInts[i] = int(data[i])
}
res := ec.rs.Encode(dataInts, int(eccCount))
result := make([]byte, len(res))
for i := 0; i < len(res); i++ {
result[i] = byte(res[i])
}
return result
}

View file

@ -0,0 +1,60 @@
package qr
import (
"bytes"
"testing"
)
func Test_ErrorCorrection(t *testing.T) {
doTest := func(b []byte, ecc []byte) {
cnt := byte(len(ecc))
res := ec.calcECC(b, cnt)
if bytes.Compare(res, ecc) != 0 {
t.Errorf("ECC error!\nGot: %v\nExpected:%v", res, ecc)
}
}
// Issue #5
doTest([]byte{66, 196, 148, 21, 99, 19, 151, 151, 53, 149, 54, 195, 4, 133, 87, 84, 115, 85, 22, 148, 52, 71, 102, 68, 134, 182, 247, 119, 22, 68, 117, 134, 35, 4, 134, 38, 21, 84, 21, 117, 87, 164, 135, 115, 211, 208, 236, 17, 236, 17, 236, 17, 236, 17, 236}, []byte{187, 187, 171, 253, 164, 129, 104, 133, 3, 75, 87, 98, 241, 146, 138})
// Other tests
doTest([]byte{17, 168, 162, 241, 255, 205, 240, 179, 88, 101, 71, 130, 2, 54, 147, 111, 232, 58, 202, 171, 85, 22, 229, 187}, []byte{30, 142, 171, 131, 189})
doTest([]byte{36, 153, 55, 100, 228, 252, 0, 35, 85, 7, 237, 117, 182, 73, 83, 244, 8, 64, 55, 252, 200, 250, 72, 92, 97, 125, 96}, []byte{129, 124, 218, 148, 49, 108, 68, 255, 58, 212, 56, 60, 142, 45, 216, 124, 253, 214, 206, 208, 145, 169, 43})
doTest([]byte{250, 195, 230, 128, 31, 168, 86, 123, 244, 129, 74, 130, 222, 225, 140, 129, 114, 132, 128, 88, 96, 13, 165, 132, 116, 22, 42, 81, 219, 3, 102, 156, 69, 70, 90, 68, 7, 245, 150, 160, 252, 121, 20}, []byte{124, 23, 233, 71, 200, 211, 54, 141, 10, 23, 206, 147, 116, 35, 45, 218, 158, 193, 80, 194, 129, 147, 8, 78, 229, 112, 89, 161, 167, 203, 11, 245, 186, 187, 17, 7, 175})
doTest([]byte{121, 234, 24, 188, 218, 238, 248, 223, 98, 124, 237, 30, 98, 12, 9, 126, 5, 160, 240, 27, 174, 60, 152, 134, 71, 122, 125, 238, 223, 91, 231, 248, 230, 152, 250, 44, 17, 149, 0, 20, 109, 188, 227, 202}, []byte{209, 71, 225, 216, 240, 127, 111, 98, 194, 133, 114, 63, 35, 167, 184, 4, 209, 211, 40, 14, 74, 37, 21, 76, 95, 206, 90, 152, 110, 64, 6, 92, 80, 255, 127, 35, 111, 25, 1, 73})
doTest([]byte{165, 233, 141, 34, 247, 216, 35, 163, 61, 61, 81, 146, 116, 96, 113, 10, 0, 6, 148, 244, 55, 201, 17, 220, 109, 111}, []byte{93, 173, 231, 160})
doTest([]byte{173, 242, 89, 205, 24, 33, 213, 147, 96, 189, 100, 15, 213, 67, 91, 189, 218, 127, 32, 160, 162, 99, 187, 221, 53, 121, 238, 219, 215, 176, 181, 135, 56, 71, 246, 74, 228}, []byte{194, 130, 43, 168, 223, 144, 223, 49, 5, 162, 62, 218, 50, 205, 249, 84, 188, 25, 109, 110, 49, 224, 194, 244, 83, 221, 236, 71, 197, 159, 182})
doTest([]byte{82, 138, 221, 169, 67, 161, 132, 31, 243, 110, 83, 1, 238, 79, 255, 57, 74, 54, 123, 151, 159, 50, 250, 188, 176, 8, 221, 215, 141, 77, 16}, []byte{197, 122, 225, 65, 40, 69, 153, 100, 73, 245, 150, 213, 104, 127, 3})
doTest([]byte{5, 206, 21, 196, 185, 120, 60, 177, 90, 251, 109, 131, 174, 199, 55, 56, 14, 171, 19, 104, 236, 218, 31, 144, 33, 249, 58, 195, 173, 145, 166, 93, 122, 171, 232, 128, 233, 116, 144, 189, 62, 230, 68, 55, 140, 56, 1, 65, 165, 158, 127}, []byte{73, 141, 230, 252, 225, 173, 251, 194, 150, 98, 141, 241, 246, 11, 16, 8, 42})
doTest([]byte{112, 106, 43, 174, 133, 163, 192, 61, 121, 3, 200, 84, 15, 9, 3, 222, 183, 78, 153, 26, 85, 41, 5, 149, 232, 3, 233, 247, 249, 29, 15, 18, 4, 96, 9, 64, 188, 210}, []byte{16, 254, 143, 110, 63, 167, 213, 242, 95, 78, 215, 145, 231, 59, 158, 36, 149, 247, 123, 114, 247, 202, 15, 56, 229, 163, 186, 73, 82, 230, 111, 108, 111, 182, 193, 46, 116})
doTest([]byte{208, 128, 197, 227, 124, 226, 125, 46, 253, 98, 238, 80, 229, 134, 167, 70, 101, 150, 198, 130, 185, 200, 68, 91}, []byte{229, 167, 187, 39, 92, 90, 210, 25, 206, 237, 90, 194, 206, 39, 2, 11, 78, 48, 247})
doTest([]byte{79, 175, 255, 194, 34, 229, 234, 200, 74, 213, 100, 33, 24, 5, 133, 186, 249, 151, 46, 190, 44, 126, 184, 195, 219, 37, 11, 225, 23, 8, 59, 106, 239, 198, 146, 205, 47, 59, 63, 9, 102, 29, 60, 209, 226, 67, 126, 193, 252, 255, 206, 172, 44, 53, 137, 209, 246}, []byte{237, 8, 12, 44, 90, 243, 24, 100, 123, 216, 185, 91, 182, 60, 9, 145, 126, 254, 139, 24, 211, 150, 219, 28, 138, 197, 13, 109, 227, 31, 60, 128, 237, 181, 183, 2, 138, 232, 112, 5})
doTest([]byte{253, 217, 8, 176, 66, 153, 249, 49, 82, 114, 184, 139, 190, 87}, []byte{28, 55, 193, 193, 179, 246, 222, 5, 95, 96, 13, 242})
doTest([]byte{15, 65, 231, 224, 151, 167, 74, 228, 23}, []byte{200, 90, 82})
doTest([]byte{61, 186, 61, 193, 215, 243, 84, 66, 48, 93, 108, 249, 55, 232}, []byte{0, 180, 53, 152, 134, 252, 165, 168})
doTest([]byte{78, 68, 116, 15, 85}, []byte{36})
doTest([]byte{122, 143}, []byte{245})
doTest([]byte{78, 85, 143, 35}, []byte{226, 85})
doTest([]byte{11, 188, 118, 21, 177, 224, 151, 105, 21, 245, 251, 162, 72, 175, 248, 134, 123, 251, 160, 163, 42, 57, 53, 222, 195, 49, 199, 151, 5, 236, 160, 57, 212, 241, 44, 43}, []byte{186, 106})
doTest([]byte{157, 99, 220, 166, 63, 18, 225, 215, 71, 95, 99, 200, 218, 147, 131, 245, 222, 209, 135, 152, 82, 128, 24, 0, 100, 40, 84, 193, 205, 86, 130, 204, 235, 100, 94, 61}, []byte{41, 171, 66, 233})
doTest([]byte{249, 34, 253, 235, 233, 104, 52, 60, 17, 13, 182, 223, 19, 91, 164, 2, 196, 29, 74, 219, 65, 23, 190, 31, 10, 241, 221, 150, 221, 118, 53, 69, 45, 90, 215, 100, 155, 102, 150, 176, 203, 39, 22, 70, 10, 238}, []byte{161, 49, 179, 149, 178, 146, 208, 144, 19, 158, 180, 152, 243, 138, 143, 243, 82, 112, 229, 10, 113, 255, 139, 246})
doTest([]byte{39, 232, 159, 64, 242, 235, 66, 226, 100, 221, 225, 247, 139, 157, 95, 155}, []byte{41, 9, 244})
doTest([]byte{177, 185, 131, 64, 103, 93, 134, 153, 15, 26, 0, 119, 21, 27, 174, 181, 111, 245, 214, 244, 83, 66, 24, 244, 255, 189, 133, 158, 37, 46, 199, 123, 110, 153, 61, 137, 163, 231, 129, 65, 186, 89, 219, 39, 226, 236, 199, 197, 73, 213}, []byte{37, 59, 125, 211, 249, 177, 107, 79, 107, 47, 242, 168, 49, 38, 168, 198, 199, 91, 212, 22, 107, 244})
doTest([]byte{196, 226, 29, 110, 161, 143, 64, 169, 216, 231, 115}, []byte{253, 93, 218, 129, 37})
doTest([]byte{133, 8, 124, 221, 36, 17, 135, 115, 149, 58, 250, 103, 241, 18, 19, 246, 191, 85, 80, 255, 93, 182, 140, 123, 206, 232, 20, 166, 216, 105, 210, 229, 249, 212, 93, 227, 75, 231, 36, 195, 166, 246, 47, 168, 35, 7, 176, 124, 44, 179, 24, 145}, []byte{78, 57, 134, 181, 215, 149, 111, 51, 172, 58, 114, 3, 140, 186, 126, 40, 190})
doTest([]byte{245, 206, 124, 0, 15, 59, 253, 225, 155}, []byte{65, 14, 188, 213, 18, 113, 161, 16})
doTest([]byte{20, 109, 28, 180, 48, 170, 216, 48, 140, 89, 103}, []byte{193, 147, 50, 209, 160})
doTest([]byte{87, 198, 56, 151, 121, 37, 81, 64, 193, 24, 222, 142, 102, 74, 216, 233, 198, 197, 90, 4, 65, 14, 154, 147, 200, 252, 8, 64, 97, 150, 136, 141}, []byte{231, 190, 32, 90, 100, 40, 41, 103, 200, 200, 243, 75, 177, 7, 93, 28, 83, 47, 188, 236, 20, 95, 69, 104, 155, 102, 110, 197})
doTest([]byte{168, 72, 2, 101, 103, 118, 218, 38, 82, 85, 62, 37, 201, 96, 255, 71, 198}, []byte{129, 33, 28, 228, 195, 120, 101, 46, 119, 126})
doTest([]byte{130, 162, 73, 44, 165, 207, 124, 28, 17, 223, 43, 143, 81, 70, 205, 161, 143, 230, 97, 94, 228, 41, 26, 187, 69, 85, 162, 51, 168, 64, 26, 207, 245, 128}, []byte{6, 171})
doTest([]byte{95, 28, 93, 149, 234, 89, 201, 71, 39, 197, 236, 223, 251, 190, 112, 96, 101, 53, 40, 88, 136, 141, 230, 80, 45, 73, 116, 208, 197, 91, 154, 209, 128, 214, 66, 114, 137, 204, 115, 139, 96, 211, 148, 127, 104, 194}, []byte{10, 102, 57, 95, 61, 212, 130, 71, 74, 58, 82, 115, 238, 213, 251, 184, 203, 250, 55, 186, 37, 16, 71, 247, 146, 194, 74, 208, 221, 6, 81, 172, 204, 73, 102, 40, 247, 174, 213, 37, 225, 246, 8, 58})
doTest([]byte{207, 185, 106, 191, 87, 109, 110, 210, 54, 12, 103, 161, 228}, []byte{214, 138, 159, 195, 154, 236, 33, 243, 53, 79, 227})
doTest([]byte{203, 43, 26, 94, 37, 123, 254, 215, 153, 193, 157, 248, 180, 249, 103, 232, 107, 17, 138, 0, 11, 240, 218, 122, 19, 103, 112, 60, 125, 100, 209, 166, 103, 81, 200, 84, 77, 100, 18, 110, 209, 225, 209, 254, 185, 116, 186, 216, 206, 36, 252, 144, 90, 247, 117, 219, 81, 160}, []byte{185, 176, 106, 253, 76, 153, 185, 211, 187, 153, 210, 31, 99, 4, 46, 145, 221, 99, 236, 19, 126, 138, 66, 26, 40, 217, 170, 217, 147})
doTest([]byte{11, 193, 90, 52, 239, 247, 144, 99, 48, 19, 154, 6, 255, 28, 47, 41, 30, 220}, []byte{235, 165, 125, 82, 28, 116, 21, 133, 243, 222, 241, 20, 134})
doTest([]byte{173, 151, 109, 88, 104, 65, 76, 111, 219, 237, 2, 173, 25, 84, 98, 16, 135, 157, 14, 194, 228, 86, 167, 187, 137, 245, 144, 61, 200, 76, 188, 117, 223, 172, 16, 116, 84, 1, 203, 173, 170, 32, 135, 67, 16}, []byte{150, 31, 11, 211, 82, 221, 251, 84, 254, 121, 68, 34, 211, 142, 197, 246, 138, 204, 60, 197, 210, 238, 142, 234, 187, 200, 179, 228})
doTest([]byte{171, 185, 30, 162, 129, 205, 254, 186, 86, 239, 178, 206, 115, 177, 14, 166, 143, 48, 141, 205, 109, 67, 238, 187, 134, 210, 96, 23, 195, 206, 100, 171, 156, 8, 229, 131, 169, 169, 59, 167, 224, 241, 185, 132, 162, 50, 87, 252, 156, 122, 248, 19, 130, 31, 127}, []byte{62, 42, 216, 109, 23, 176, 255, 137, 139, 90, 7, 186, 175, 243, 160, 206, 37, 94, 157, 217, 11, 169, 126, 41, 73, 133, 212, 232, 249, 117, 70, 147, 137, 156, 43, 243, 234, 155, 94, 38, 59, 211, 218, 165, 3, 33, 231, 237, 92, 16, 128})
doTest([]byte{98, 28, 174, 108, 231, 247, 135, 139, 6, 50, 107, 203, 138, 252, 229, 245, 230, 236, 124, 138, 105, 25, 83, 122}, []byte{97, 214, 25, 2, 14, 48, 65, 212, 241, 200, 81, 57, 176, 59, 16, 55, 20, 91, 66})
doTest([]byte{73, 214, 80, 41, 125, 136, 126, 184, 70, 141, 140, 58, 249, 250, 49, 249, 155, 0, 236, 49, 17, 125, 18, 29}, []byte{128, 16, 47, 235, 125, 128, 97, 245, 177, 210, 219, 195})
doTest([]byte{3, 220, 98, 73, 200, 52, 8, 107, 173, 177, 58, 221, 180, 226, 76, 210, 182, 88, 104, 171, 243, 129, 88, 112, 126, 83, 141, 50, 106, 204, 195, 51, 141, 75, 132, 161}, []byte{110, 178, 213, 174, 1, 241, 95})
doTest([]byte{196, 88, 50, 142, 76, 128, 190, 189, 76, 9, 228, 62, 198, 186, 180, 240, 62, 130, 132, 242}, []byte{244, 89, 17, 143, 3, 180, 150, 242, 167, 214, 209, 133, 120, 213, 173, 59, 25, 158, 251})
doTest([]byte{166, 214, 1, 225, 237, 7, 80, 104, 94, 170, 125, 184, 148, 16, 121, 101, 52, 216, 177, 192, 6, 132, 77, 44, 5, 9, 126, 156, 12, 2, 29, 99, 51, 78, 177, 92, 140, 107, 146, 183, 109, 227, 171, 57, 193, 14, 37}, []byte{245, 46, 189, 11, 202, 195, 89, 53, 215, 172, 132, 196, 145, 141, 239, 160, 242, 7, 85, 251, 193, 85})
}

56
vendor/github.com/boombuler/barcode/qr/numeric.go generated vendored Normal file
View file

@ -0,0 +1,56 @@
package qr
import (
"errors"
"fmt"
"strconv"
"github.com/boombuler/barcode/utils"
)
func encodeNumeric(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
contentBitCount := (len(content) / 3) * 10
switch len(content) % 3 {
case 1:
contentBitCount += 4
case 2:
contentBitCount += 7
}
vi := findSmallestVersionInfo(ecl, numericMode, contentBitCount)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
res := new(utils.BitList)
res.AddBits(int(numericMode), 4)
res.AddBits(len(content), vi.charCountBits(numericMode))
for pos := 0; pos < len(content); pos += 3 {
var curStr string
if pos+3 <= len(content) {
curStr = content[pos : pos+3]
} else {
curStr = content[pos:]
}
i, err := strconv.Atoi(curStr)
if err != nil || i < 0 {
return nil, nil, fmt.Errorf("\"%s\" can not be encoded as %s", content, Numeric)
}
var bitCnt byte
switch len(curStr) % 3 {
case 0:
bitCnt = 10
case 1:
bitCnt = 4
break
case 2:
bitCnt = 7
break
}
res.AddBits(i, bitCnt)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}

26
vendor/github.com/boombuler/barcode/qr/numeric_test.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
package qr
import (
"bytes"
"testing"
)
func Test_NumericEncoding(t *testing.T) {
encode := Numeric.getEncoder()
x, vi, err := encode("01234567", H)
if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{16, 32, 12, 86, 97, 128, 236, 17, 236}) != 0 {
t.Error("\"01234567\" failed to encode")
}
x, vi, err = encode("0123456789012345", H)
if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{16, 64, 12, 86, 106, 110, 20, 234, 80}) != 0 {
t.Error("\"0123456789012345\" failed to encode")
}
x, vi, err = encode("foo", H)
if err == nil {
t.Error("Numeric encoding should not be able to encode \"foo\"")
}
x, vi, err = encode(makeString(14297, "1"), H)
if x != nil || vi != nil || err == nil {
t.Fail()
}
}

166
vendor/github.com/boombuler/barcode/qr/qrcode.go generated vendored Normal file
View file

@ -0,0 +1,166 @@
package qr
import (
"image"
"image/color"
"math"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
type qrcode struct {
dimension int
data *utils.BitList
content string
}
func (qr *qrcode) Content() string {
return qr.content
}
func (qr *qrcode) Metadata() barcode.Metadata {
return barcode.Metadata{barcode.TypeQR, 2}
}
func (qr *qrcode) ColorModel() color.Model {
return color.Gray16Model
}
func (qr *qrcode) Bounds() image.Rectangle {
return image.Rect(0, 0, qr.dimension, qr.dimension)
}
func (qr *qrcode) At(x, y int) color.Color {
if qr.Get(x, y) {
return color.Black
}
return color.White
}
func (qr *qrcode) Get(x, y int) bool {
return qr.data.GetBit(x*qr.dimension + y)
}
func (qr *qrcode) Set(x, y int, val bool) {
qr.data.SetBit(x*qr.dimension+y, val)
}
func (qr *qrcode) calcPenalty() uint {
return qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
}
func (qr *qrcode) calcPenaltyRule1() uint {
var result uint
for x := 0; x < qr.dimension; x++ {
checkForX := false
var cntX uint
checkForY := false
var cntY uint
for y := 0; y < qr.dimension; y++ {
if qr.Get(x, y) == checkForX {
cntX++
} else {
checkForX = !checkForX
if cntX >= 5 {
result += cntX - 2
}
cntX = 1
}
if qr.Get(y, x) == checkForY {
cntY++
} else {
checkForY = !checkForY
if cntY >= 5 {
result += cntY - 2
}
cntY = 1
}
}
if cntX >= 5 {
result += cntX - 2
}
if cntY >= 5 {
result += cntY - 2
}
}
return result
}
func (qr *qrcode) calcPenaltyRule2() uint {
var result uint
for x := 0; x < qr.dimension-1; x++ {
for y := 0; y < qr.dimension-1; y++ {
check := qr.Get(x, y)
if qr.Get(x, y+1) == check && qr.Get(x+1, y) == check && qr.Get(x+1, y+1) == check {
result += 3
}
}
}
return result
}
func (qr *qrcode) calcPenaltyRule3() uint {
pattern1 := []bool{true, false, true, true, true, false, true, false, false, false, false}
pattern2 := []bool{false, false, false, false, true, false, true, true, true, false, true}
var result uint
for x := 0; x <= qr.dimension-len(pattern1); x++ {
for y := 0; y < qr.dimension; y++ {
pattern1XFound := true
pattern2XFound := true
pattern1YFound := true
pattern2YFound := true
for i := 0; i < len(pattern1); i++ {
iv := qr.Get(x+i, y)
if iv != pattern1[i] {
pattern1XFound = false
}
if iv != pattern2[i] {
pattern2XFound = false
}
iv = qr.Get(y, x+i)
if iv != pattern1[i] {
pattern1YFound = false
}
if iv != pattern2[i] {
pattern2YFound = false
}
}
if pattern1XFound || pattern2XFound {
result += 40
}
if pattern1YFound || pattern2YFound {
result += 40
}
}
}
return result
}
func (qr *qrcode) calcPenaltyRule4() uint {
totalNum := qr.data.Len()
trueCnt := 0
for i := 0; i < totalNum; i++ {
if qr.data.GetBit(i) {
trueCnt++
}
}
percDark := float64(trueCnt) * 100 / float64(totalNum)
floor := math.Abs(math.Floor(percDark/5) - 10)
ceil := math.Abs(math.Ceil(percDark/5) - 10)
return uint(math.Min(floor, ceil) * 10)
}
func newBarcode(dim int) *qrcode {
res := new(qrcode)
res.dimension = dim
res.data = utils.NewBitList(dim * dim)
return res
}

126
vendor/github.com/boombuler/barcode/qr/qrcode_test.go generated vendored Normal file
View file

@ -0,0 +1,126 @@
package qr
import (
"image/color"
"testing"
)
func Test_NewQRCode(t *testing.T) {
bc := newBarcode(2)
if bc == nil {
t.Fail()
}
if bc.data.Len() != 4 {
t.Fail()
}
if bc.dimension != 2 {
t.Fail()
}
}
func Test_QRBasics(t *testing.T) {
qr := newBarcode(10)
if qr.ColorModel() != color.Gray16Model {
t.Fail()
}
code, _ := Encode("test", L, Unicode)
if code.Content() != "test" {
t.Fail()
}
if code.Metadata().Dimensions != 2 {
t.Fail()
}
bounds := code.Bounds()
if bounds.Min.X != 0 || bounds.Min.Y != 0 || bounds.Max.X != 21 || bounds.Max.Y != 21 {
t.Fail()
}
if code.At(0, 0) != color.Black || code.At(0, 7) != color.White {
t.Fail()
}
qr = code.(*qrcode)
if !qr.Get(0, 0) || qr.Get(0, 7) {
t.Fail()
}
sum := qr.calcPenaltyRule1() + qr.calcPenaltyRule2() + qr.calcPenaltyRule3() + qr.calcPenaltyRule4()
if qr.calcPenalty() != sum {
t.Fail()
}
}
func Test_Penalty1(t *testing.T) {
qr := newBarcode(7)
if qr.calcPenaltyRule1() != 70 {
t.Fail()
}
qr.Set(0, 0, true)
if qr.calcPenaltyRule1() != 68 {
t.Fail()
}
qr.Set(0, 6, true)
if qr.calcPenaltyRule1() != 66 {
t.Fail()
}
}
func Test_Penalty2(t *testing.T) {
qr := newBarcode(3)
if qr.calcPenaltyRule2() != 12 {
t.Fail()
}
qr.Set(0, 0, true)
qr.Set(1, 1, true)
qr.Set(2, 0, true)
if qr.calcPenaltyRule2() != 0 {
t.Fail()
}
qr.Set(1, 1, false)
if qr.calcPenaltyRule2() != 6 {
t.Fail()
}
}
func Test_Penalty3(t *testing.T) {
runTest := func(content string, result uint) {
code, _ := Encode(content, L, AlphaNumeric)
qr := code.(*qrcode)
if qr.calcPenaltyRule3() != result {
t.Errorf("Failed Penalty Rule 3 for content \"%s\" got %d but expected %d", content, qr.calcPenaltyRule3(), result)
}
}
runTest("A", 80)
runTest("FOO", 40)
runTest("0815", 0)
}
func Test_Penalty4(t *testing.T) {
qr := newBarcode(3)
if qr.calcPenaltyRule4() != 100 {
t.Fail()
}
qr.Set(0, 0, true)
if qr.calcPenaltyRule4() != 70 {
t.Fail()
}
qr.Set(0, 1, true)
if qr.calcPenaltyRule4() != 50 {
t.Fail()
}
qr.Set(0, 2, true)
if qr.calcPenaltyRule4() != 30 {
t.Fail()
}
qr.Set(1, 0, true)
if qr.calcPenaltyRule4() != 10 {
t.Fail()
}
qr.Set(1, 1, true)
if qr.calcPenaltyRule4() != 10 {
t.Fail()
}
qr = newBarcode(2)
qr.Set(0, 0, true)
qr.Set(1, 0, true)
if qr.calcPenaltyRule4() != 0 {
t.Fail()
}
}

27
vendor/github.com/boombuler/barcode/qr/unicode.go generated vendored Normal file
View file

@ -0,0 +1,27 @@
package qr
import (
"errors"
"github.com/boombuler/barcode/utils"
)
func encodeUnicode(content string, ecl ErrorCorrectionLevel) (*utils.BitList, *versionInfo, error) {
data := []byte(content)
vi := findSmallestVersionInfo(ecl, byteMode, len(data)*8)
if vi == nil {
return nil, nil, errors.New("To much data to encode")
}
// It's not correct to add the unicode bytes to the result directly but most readers can't handle the
// required ECI header...
res := new(utils.BitList)
res.AddBits(int(byteMode), 4)
res.AddBits(len(content), vi.charCountBits(byteMode))
for _, b := range data {
res.AddByte(b)
}
addPaddingAndTerminator(res, vi)
return res, vi, nil
}

18
vendor/github.com/boombuler/barcode/qr/unicode_test.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
package qr
import (
"bytes"
"testing"
)
func Test_UnicodeEncoding(t *testing.T) {
encode := Unicode.getEncoder()
x, vi, err := encode("A", H) // 65
if x == nil || vi == nil || vi.Version != 1 || bytes.Compare(x.GetBytes(), []byte{64, 20, 16, 236, 17, 236, 17, 236, 17}) != 0 {
t.Errorf("\"A\" failed to encode: %s", err)
}
_, _, err = encode(makeString(3000, "A"), H)
if err == nil {
t.Error("Unicode encoding should not be able to encode a 3kb string")
}
}

310
vendor/github.com/boombuler/barcode/qr/versioninfo.go generated vendored Normal file
View file

@ -0,0 +1,310 @@
package qr
import "math"
// ErrorCorrectionLevel indicates the amount of "backup data" stored in the QR code
type ErrorCorrectionLevel byte
const (
// L recovers 7% of data
L ErrorCorrectionLevel = iota
// M recovers 15% of data
M
// Q recovers 25% of data
Q
// H recovers 30% of data
H
)
func (ecl ErrorCorrectionLevel) String() string {
switch ecl {
case L:
return "L"
case M:
return "M"
case Q:
return "Q"
case H:
return "H"
}
return "unknown"
}
type encodingMode byte
const (
numericMode encodingMode = 1
alphaNumericMode encodingMode = 2
byteMode encodingMode = 4
kanjiMode encodingMode = 8
)
type versionInfo struct {
Version byte
Level ErrorCorrectionLevel
ErrorCorrectionCodewordsPerBlock byte
NumberOfBlocksInGroup1 byte
DataCodeWordsPerBlockInGroup1 byte
NumberOfBlocksInGroup2 byte
DataCodeWordsPerBlockInGroup2 byte
}
var versionInfos = []*versionInfo{
&versionInfo{1, L, 7, 1, 19, 0, 0},
&versionInfo{1, M, 10, 1, 16, 0, 0},
&versionInfo{1, Q, 13, 1, 13, 0, 0},
&versionInfo{1, H, 17, 1, 9, 0, 0},
&versionInfo{2, L, 10, 1, 34, 0, 0},
&versionInfo{2, M, 16, 1, 28, 0, 0},
&versionInfo{2, Q, 22, 1, 22, 0, 0},
&versionInfo{2, H, 28, 1, 16, 0, 0},
&versionInfo{3, L, 15, 1, 55, 0, 0},
&versionInfo{3, M, 26, 1, 44, 0, 0},
&versionInfo{3, Q, 18, 2, 17, 0, 0},
&versionInfo{3, H, 22, 2, 13, 0, 0},
&versionInfo{4, L, 20, 1, 80, 0, 0},
&versionInfo{4, M, 18, 2, 32, 0, 0},
&versionInfo{4, Q, 26, 2, 24, 0, 0},
&versionInfo{4, H, 16, 4, 9, 0, 0},
&versionInfo{5, L, 26, 1, 108, 0, 0},
&versionInfo{5, M, 24, 2, 43, 0, 0},
&versionInfo{5, Q, 18, 2, 15, 2, 16},
&versionInfo{5, H, 22, 2, 11, 2, 12},
&versionInfo{6, L, 18, 2, 68, 0, 0},
&versionInfo{6, M, 16, 4, 27, 0, 0},
&versionInfo{6, Q, 24, 4, 19, 0, 0},
&versionInfo{6, H, 28, 4, 15, 0, 0},
&versionInfo{7, L, 20, 2, 78, 0, 0},
&versionInfo{7, M, 18, 4, 31, 0, 0},
&versionInfo{7, Q, 18, 2, 14, 4, 15},
&versionInfo{7, H, 26, 4, 13, 1, 14},
&versionInfo{8, L, 24, 2, 97, 0, 0},
&versionInfo{8, M, 22, 2, 38, 2, 39},
&versionInfo{8, Q, 22, 4, 18, 2, 19},
&versionInfo{8, H, 26, 4, 14, 2, 15},
&versionInfo{9, L, 30, 2, 116, 0, 0},
&versionInfo{9, M, 22, 3, 36, 2, 37},
&versionInfo{9, Q, 20, 4, 16, 4, 17},
&versionInfo{9, H, 24, 4, 12, 4, 13},
&versionInfo{10, L, 18, 2, 68, 2, 69},
&versionInfo{10, M, 26, 4, 43, 1, 44},
&versionInfo{10, Q, 24, 6, 19, 2, 20},
&versionInfo{10, H, 28, 6, 15, 2, 16},
&versionInfo{11, L, 20, 4, 81, 0, 0},
&versionInfo{11, M, 30, 1, 50, 4, 51},
&versionInfo{11, Q, 28, 4, 22, 4, 23},
&versionInfo{11, H, 24, 3, 12, 8, 13},
&versionInfo{12, L, 24, 2, 92, 2, 93},
&versionInfo{12, M, 22, 6, 36, 2, 37},
&versionInfo{12, Q, 26, 4, 20, 6, 21},
&versionInfo{12, H, 28, 7, 14, 4, 15},
&versionInfo{13, L, 26, 4, 107, 0, 0},
&versionInfo{13, M, 22, 8, 37, 1, 38},
&versionInfo{13, Q, 24, 8, 20, 4, 21},
&versionInfo{13, H, 22, 12, 11, 4, 12},
&versionInfo{14, L, 30, 3, 115, 1, 116},
&versionInfo{14, M, 24, 4, 40, 5, 41},
&versionInfo{14, Q, 20, 11, 16, 5, 17},
&versionInfo{14, H, 24, 11, 12, 5, 13},
&versionInfo{15, L, 22, 5, 87, 1, 88},
&versionInfo{15, M, 24, 5, 41, 5, 42},
&versionInfo{15, Q, 30, 5, 24, 7, 25},
&versionInfo{15, H, 24, 11, 12, 7, 13},
&versionInfo{16, L, 24, 5, 98, 1, 99},
&versionInfo{16, M, 28, 7, 45, 3, 46},
&versionInfo{16, Q, 24, 15, 19, 2, 20},
&versionInfo{16, H, 30, 3, 15, 13, 16},
&versionInfo{17, L, 28, 1, 107, 5, 108},
&versionInfo{17, M, 28, 10, 46, 1, 47},
&versionInfo{17, Q, 28, 1, 22, 15, 23},
&versionInfo{17, H, 28, 2, 14, 17, 15},
&versionInfo{18, L, 30, 5, 120, 1, 121},
&versionInfo{18, M, 26, 9, 43, 4, 44},
&versionInfo{18, Q, 28, 17, 22, 1, 23},
&versionInfo{18, H, 28, 2, 14, 19, 15},
&versionInfo{19, L, 28, 3, 113, 4, 114},
&versionInfo{19, M, 26, 3, 44, 11, 45},
&versionInfo{19, Q, 26, 17, 21, 4, 22},
&versionInfo{19, H, 26, 9, 13, 16, 14},
&versionInfo{20, L, 28, 3, 107, 5, 108},
&versionInfo{20, M, 26, 3, 41, 13, 42},
&versionInfo{20, Q, 30, 15, 24, 5, 25},
&versionInfo{20, H, 28, 15, 15, 10, 16},
&versionInfo{21, L, 28, 4, 116, 4, 117},
&versionInfo{21, M, 26, 17, 42, 0, 0},
&versionInfo{21, Q, 28, 17, 22, 6, 23},
&versionInfo{21, H, 30, 19, 16, 6, 17},
&versionInfo{22, L, 28, 2, 111, 7, 112},
&versionInfo{22, M, 28, 17, 46, 0, 0},
&versionInfo{22, Q, 30, 7, 24, 16, 25},
&versionInfo{22, H, 24, 34, 13, 0, 0},
&versionInfo{23, L, 30, 4, 121, 5, 122},
&versionInfo{23, M, 28, 4, 47, 14, 48},
&versionInfo{23, Q, 30, 11, 24, 14, 25},
&versionInfo{23, H, 30, 16, 15, 14, 16},
&versionInfo{24, L, 30, 6, 117, 4, 118},
&versionInfo{24, M, 28, 6, 45, 14, 46},
&versionInfo{24, Q, 30, 11, 24, 16, 25},
&versionInfo{24, H, 30, 30, 16, 2, 17},
&versionInfo{25, L, 26, 8, 106, 4, 107},
&versionInfo{25, M, 28, 8, 47, 13, 48},
&versionInfo{25, Q, 30, 7, 24, 22, 25},
&versionInfo{25, H, 30, 22, 15, 13, 16},
&versionInfo{26, L, 28, 10, 114, 2, 115},
&versionInfo{26, M, 28, 19, 46, 4, 47},
&versionInfo{26, Q, 28, 28, 22, 6, 23},
&versionInfo{26, H, 30, 33, 16, 4, 17},
&versionInfo{27, L, 30, 8, 122, 4, 123},
&versionInfo{27, M, 28, 22, 45, 3, 46},
&versionInfo{27, Q, 30, 8, 23, 26, 24},
&versionInfo{27, H, 30, 12, 15, 28, 16},
&versionInfo{28, L, 30, 3, 117, 10, 118},
&versionInfo{28, M, 28, 3, 45, 23, 46},
&versionInfo{28, Q, 30, 4, 24, 31, 25},
&versionInfo{28, H, 30, 11, 15, 31, 16},
&versionInfo{29, L, 30, 7, 116, 7, 117},
&versionInfo{29, M, 28, 21, 45, 7, 46},
&versionInfo{29, Q, 30, 1, 23, 37, 24},
&versionInfo{29, H, 30, 19, 15, 26, 16},
&versionInfo{30, L, 30, 5, 115, 10, 116},
&versionInfo{30, M, 28, 19, 47, 10, 48},
&versionInfo{30, Q, 30, 15, 24, 25, 25},
&versionInfo{30, H, 30, 23, 15, 25, 16},
&versionInfo{31, L, 30, 13, 115, 3, 116},
&versionInfo{31, M, 28, 2, 46, 29, 47},
&versionInfo{31, Q, 30, 42, 24, 1, 25},
&versionInfo{31, H, 30, 23, 15, 28, 16},
&versionInfo{32, L, 30, 17, 115, 0, 0},
&versionInfo{32, M, 28, 10, 46, 23, 47},
&versionInfo{32, Q, 30, 10, 24, 35, 25},
&versionInfo{32, H, 30, 19, 15, 35, 16},
&versionInfo{33, L, 30, 17, 115, 1, 116},
&versionInfo{33, M, 28, 14, 46, 21, 47},
&versionInfo{33, Q, 30, 29, 24, 19, 25},
&versionInfo{33, H, 30, 11, 15, 46, 16},
&versionInfo{34, L, 30, 13, 115, 6, 116},
&versionInfo{34, M, 28, 14, 46, 23, 47},
&versionInfo{34, Q, 30, 44, 24, 7, 25},
&versionInfo{34, H, 30, 59, 16, 1, 17},
&versionInfo{35, L, 30, 12, 121, 7, 122},
&versionInfo{35, M, 28, 12, 47, 26, 48},
&versionInfo{35, Q, 30, 39, 24, 14, 25},
&versionInfo{35, H, 30, 22, 15, 41, 16},
&versionInfo{36, L, 30, 6, 121, 14, 122},
&versionInfo{36, M, 28, 6, 47, 34, 48},
&versionInfo{36, Q, 30, 46, 24, 10, 25},
&versionInfo{36, H, 30, 2, 15, 64, 16},
&versionInfo{37, L, 30, 17, 122, 4, 123},
&versionInfo{37, M, 28, 29, 46, 14, 47},
&versionInfo{37, Q, 30, 49, 24, 10, 25},
&versionInfo{37, H, 30, 24, 15, 46, 16},
&versionInfo{38, L, 30, 4, 122, 18, 123},
&versionInfo{38, M, 28, 13, 46, 32, 47},
&versionInfo{38, Q, 30, 48, 24, 14, 25},
&versionInfo{38, H, 30, 42, 15, 32, 16},
&versionInfo{39, L, 30, 20, 117, 4, 118},
&versionInfo{39, M, 28, 40, 47, 7, 48},
&versionInfo{39, Q, 30, 43, 24, 22, 25},
&versionInfo{39, H, 30, 10, 15, 67, 16},
&versionInfo{40, L, 30, 19, 118, 6, 119},
&versionInfo{40, M, 28, 18, 47, 31, 48},
&versionInfo{40, Q, 30, 34, 24, 34, 25},
&versionInfo{40, H, 30, 20, 15, 61, 16},
}
func (vi *versionInfo) totalDataBytes() int {
g1Data := int(vi.NumberOfBlocksInGroup1) * int(vi.DataCodeWordsPerBlockInGroup1)
g2Data := int(vi.NumberOfBlocksInGroup2) * int(vi.DataCodeWordsPerBlockInGroup2)
return (g1Data + g2Data)
}
func (vi *versionInfo) charCountBits(m encodingMode) byte {
switch m {
case numericMode:
if vi.Version < 10 {
return 10
} else if vi.Version < 27 {
return 12
}
return 14
case alphaNumericMode:
if vi.Version < 10 {
return 9
} else if vi.Version < 27 {
return 11
}
return 13
case byteMode:
if vi.Version < 10 {
return 8
}
return 16
case kanjiMode:
if vi.Version < 10 {
return 8
} else if vi.Version < 27 {
return 10
}
return 12
default:
return 0
}
}
func (vi *versionInfo) modulWidth() int {
return ((int(vi.Version) - 1) * 4) + 21
}
func (vi *versionInfo) alignmentPatternPlacements() []int {
if vi.Version == 1 {
return make([]int, 0)
}
first := 6
last := vi.modulWidth() - 7
space := float64(last - first)
count := int(math.Ceil(space/28)) + 1
result := make([]int, count)
result[0] = first
result[len(result)-1] = last
if count > 2 {
step := int(math.Ceil(float64(last-first) / float64(count-1)))
if step%2 == 1 {
frac := float64(last-first) / float64(count-1)
_, x := math.Modf(frac)
if x >= 0.5 {
frac = math.Ceil(frac)
} else {
frac = math.Floor(frac)
}
if int(frac)%2 == 0 {
step--
} else {
step++
}
}
for i := 1; i <= count-2; i++ {
result[i] = last - (step * (count - 1 - i))
}
}
return result
}
func findSmallestVersionInfo(ecl ErrorCorrectionLevel, mode encodingMode, dataBits int) *versionInfo {
dataBits = dataBits + 4 // mode indicator
for _, vi := range versionInfos {
if vi.Level == ecl {
if (vi.totalDataBytes() * 8) >= (dataBits + int(vi.charCountBits(mode))) {
return vi
}
}
}
return nil
}

View file

@ -0,0 +1,157 @@
package qr
import "testing"
var testvi = &versionInfo{7, M, 0, 1, 10, 2, 5} // Fake versionInfo to run some of the tests
func Test_ErrorCorrectionStringer(t *testing.T) {
tests := map[ErrorCorrectionLevel]string{
L: "L", M: "M", Q: "Q", H: "H", ErrorCorrectionLevel(99): "unknown",
}
for ecl, str := range tests {
if ecl.String() != str {
t.Fail()
}
}
}
func Test_CharCountBits(t *testing.T) {
v1 := &versionInfo{5, M, 0, 0, 0, 0, 0}
v2 := &versionInfo{15, M, 0, 0, 0, 0, 0}
v3 := &versionInfo{30, M, 0, 0, 0, 0, 0}
if v1.charCountBits(numericMode) != 10 {
t.Fail()
}
if v1.charCountBits(alphaNumericMode) != 9 {
t.Fail()
}
if v1.charCountBits(byteMode) != 8 {
t.Fail()
}
if v1.charCountBits(kanjiMode) != 8 {
t.Fail()
}
if v2.charCountBits(numericMode) != 12 {
t.Fail()
}
if v2.charCountBits(alphaNumericMode) != 11 {
t.Fail()
}
if v2.charCountBits(byteMode) != 16 {
t.Fail()
}
if v2.charCountBits(kanjiMode) != 10 {
t.Fail()
}
if v3.charCountBits(numericMode) != 14 {
t.Fail()
}
if v3.charCountBits(alphaNumericMode) != 13 {
t.Fail()
}
if v3.charCountBits(byteMode) != 16 {
t.Fail()
}
if v3.charCountBits(kanjiMode) != 12 {
t.Fail()
}
if v1.charCountBits(encodingMode(3)) != 0 {
t.Fail()
}
}
func Test_TotalDataBytes(t *testing.T) {
if testvi.totalDataBytes() != 20 {
t.Fail()
}
}
func Test_ModulWidth(t *testing.T) {
if testvi.modulWidth() != 45 {
t.Fail()
}
}
func Test_FindSmallestVersionInfo(t *testing.T) {
if findSmallestVersionInfo(H, alphaNumericMode, 10208) != nil {
t.Error("there should be no version with this capacity")
}
test := func(cap int, tVersion byte) {
v := findSmallestVersionInfo(H, alphaNumericMode, cap)
if v == nil || v.Version != tVersion {
t.Errorf("version %d should be returned.", tVersion)
}
}
test(10191, 40)
test(5591, 29)
test(5592, 30)
test(190, 3)
test(200, 4)
}
type aligmnentTest struct {
version byte
patterns []int
}
var allAligmnentTests = []*aligmnentTest{
&aligmnentTest{1, []int{}},
&aligmnentTest{2, []int{6, 18}},
&aligmnentTest{3, []int{6, 22}},
&aligmnentTest{4, []int{6, 26}},
&aligmnentTest{5, []int{6, 30}},
&aligmnentTest{6, []int{6, 34}},
&aligmnentTest{7, []int{6, 22, 38}},
&aligmnentTest{8, []int{6, 24, 42}},
&aligmnentTest{9, []int{6, 26, 46}},
&aligmnentTest{10, []int{6, 28, 50}},
&aligmnentTest{11, []int{6, 30, 54}},
&aligmnentTest{12, []int{6, 32, 58}},
&aligmnentTest{13, []int{6, 34, 62}},
&aligmnentTest{14, []int{6, 26, 46, 66}},
&aligmnentTest{15, []int{6, 26, 48, 70}},
&aligmnentTest{16, []int{6, 26, 50, 74}},
&aligmnentTest{17, []int{6, 30, 54, 78}},
&aligmnentTest{18, []int{6, 30, 56, 82}},
&aligmnentTest{19, []int{6, 30, 58, 86}},
&aligmnentTest{20, []int{6, 34, 62, 90}},
&aligmnentTest{21, []int{6, 28, 50, 72, 94}},
&aligmnentTest{22, []int{6, 26, 50, 74, 98}},
&aligmnentTest{23, []int{6, 30, 54, 78, 102}},
&aligmnentTest{24, []int{6, 28, 54, 80, 106}},
&aligmnentTest{25, []int{6, 32, 58, 84, 110}},
&aligmnentTest{26, []int{6, 30, 58, 86, 114}},
&aligmnentTest{27, []int{6, 34, 62, 90, 118}},
&aligmnentTest{28, []int{6, 26, 50, 74, 98, 122}},
&aligmnentTest{29, []int{6, 30, 54, 78, 102, 126}},
&aligmnentTest{30, []int{6, 26, 52, 78, 104, 130}},
&aligmnentTest{31, []int{6, 30, 56, 82, 108, 134}},
&aligmnentTest{32, []int{6, 34, 60, 86, 112, 138}},
&aligmnentTest{33, []int{6, 30, 58, 86, 114, 142}},
&aligmnentTest{34, []int{6, 34, 62, 90, 118, 146}},
&aligmnentTest{35, []int{6, 30, 54, 78, 102, 126, 150}},
&aligmnentTest{36, []int{6, 24, 50, 76, 102, 128, 154}},
&aligmnentTest{37, []int{6, 28, 54, 80, 106, 132, 158}},
&aligmnentTest{38, []int{6, 32, 58, 84, 110, 136, 162}},
&aligmnentTest{39, []int{6, 26, 54, 82, 110, 138, 166}},
&aligmnentTest{40, []int{6, 30, 58, 86, 114, 142, 170}},
}
func Test_AlignmentPatternPlacements(t *testing.T) {
for _, at := range allAligmnentTests {
vi := &versionInfo{at.version, M, 0, 0, 0, 0, 0}
res := vi.alignmentPatternPlacements()
if len(res) != len(at.patterns) {
t.Errorf("number of alignmentpatterns missmatch for version %d", at.version)
}
for i := 0; i < len(res); i++ {
if res[i] != at.patterns[i] {
t.Errorf("alignmentpatterns for version %d missmatch on index %d", at.version, i)
}
}
}
}

134
vendor/github.com/boombuler/barcode/scaledbarcode.go generated vendored Normal file
View file

@ -0,0 +1,134 @@
package barcode
import (
"errors"
"fmt"
"image"
"image/color"
"math"
)
type wrapFunc func(x, y int) color.Color
type scaledBarcode struct {
wrapped Barcode
wrapperFunc wrapFunc
rect image.Rectangle
}
type intCSscaledBC struct {
scaledBarcode
}
func (bc *scaledBarcode) Content() string {
return bc.wrapped.Content()
}
func (bc *scaledBarcode) Metadata() Metadata {
return bc.wrapped.Metadata()
}
func (bc *scaledBarcode) ColorModel() color.Model {
return bc.wrapped.ColorModel()
}
func (bc *scaledBarcode) Bounds() image.Rectangle {
return bc.rect
}
func (bc *scaledBarcode) At(x, y int) color.Color {
return bc.wrapperFunc(x, y)
}
func (bc *intCSscaledBC) CheckSum() int {
if cs, ok := bc.wrapped.(BarcodeIntCS); ok {
return cs.CheckSum()
}
return 0
}
// Scale returns a resized barcode with the given width and height.
func Scale(bc Barcode, width, height int) (Barcode, error) {
switch bc.Metadata().Dimensions {
case 1:
return scale1DCode(bc, width, height)
case 2:
return scale2DCode(bc, width, height)
}
return nil, errors.New("unsupported barcode format")
}
func newScaledBC(wrapped Barcode, wrapperFunc wrapFunc, rect image.Rectangle) Barcode {
result := &scaledBarcode{
wrapped: wrapped,
wrapperFunc: wrapperFunc,
rect: rect,
}
if _, ok := wrapped.(BarcodeIntCS); ok {
return &intCSscaledBC{*result}
}
return result
}
func scale2DCode(bc Barcode, width, height int) (Barcode, error) {
orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X
orgHeight := orgBounds.Max.Y - orgBounds.Min.Y
factor := int(math.Min(float64(width)/float64(orgWidth), float64(height)/float64(orgHeight)))
if factor <= 0 {
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx%d", orgWidth, orgHeight)
}
offsetX := (width - (orgWidth * factor)) / 2
offsetY := (height - (orgHeight * factor)) / 2
wrap := func(x, y int) color.Color {
if x < offsetX || y < offsetY {
return color.White
}
x = (x - offsetX) / factor
y = (y - offsetY) / factor
if x >= orgWidth || y >= orgHeight {
return color.White
}
return bc.At(x, y)
}
return newScaledBC(
bc,
wrap,
image.Rect(0, 0, width, height),
), nil
}
func scale1DCode(bc Barcode, width, height int) (Barcode, error) {
orgBounds := bc.Bounds()
orgWidth := orgBounds.Max.X - orgBounds.Min.X
factor := int(float64(width) / float64(orgWidth))
if factor <= 0 {
return nil, fmt.Errorf("can not scale barcode to an image smaller than %dx1", orgWidth)
}
offsetX := (width - (orgWidth * factor)) / 2
wrap := func(x, y int) color.Color {
if x < offsetX {
return color.White
}
x = (x - offsetX) / factor
if x >= orgWidth {
return color.White
}
return bc.At(x, 0)
}
return newScaledBC(
bc,
wrap,
image.Rect(0, 0, width, height),
), nil
}

View file

@ -0,0 +1,138 @@
// Package twooffive can create interleaved and standard "2 of 5" barcodes.
package twooffive
import (
"errors"
"fmt"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/utils"
)
const patternWidth = 5
type pattern [patternWidth]bool
type encodeInfo struct {
start []bool
end []bool
widths map[bool]int
}
var (
encodingTable = map[rune]pattern{
'0': pattern{false, false, true, true, false},
'1': pattern{true, false, false, false, true},
'2': pattern{false, true, false, false, true},
'3': pattern{true, true, false, false, false},
'4': pattern{false, false, true, false, true},
'5': pattern{true, false, true, false, false},
'6': pattern{false, true, true, false, false},
'7': pattern{false, false, false, true, true},
'8': pattern{true, false, false, true, false},
'9': pattern{false, true, false, true, false},
}
modes = map[bool]encodeInfo{
false: encodeInfo{ // non-interleaved
start: []bool{true, true, false, true, true, false, true, false},
end: []bool{true, true, false, true, false, true, true},
widths: map[bool]int{
true: 3,
false: 1,
},
},
true: encodeInfo{ // interleaved
start: []bool{true, false, true, false},
end: []bool{true, true, false, true},
widths: map[bool]int{
true: 3,
false: 1,
},
},
}
nonInterleavedSpace = pattern{false, false, false, false, false}
)
// AddCheckSum calculates the correct check-digit and appends it to the given content.
func AddCheckSum(content string) (string, error) {
if content == "" {
return "", errors.New("content is empty")
}
even := len(content)%2 == 1
sum := 0
for _, r := range content {
if _, ok := encodingTable[r]; ok {
value := utils.RuneToInt(r)
if even {
sum += value * 3
} else {
sum += value
}
even = !even
} else {
return "", fmt.Errorf("can not encode \"%s\"", content)
}
}
return content + string(utils.IntToRune(sum%10)), nil
}
// Encode creates a codabar barcode for the given content
func Encode(content string, interleaved bool) (barcode.Barcode, error) {
if content == "" {
return nil, errors.New("content is empty")
}
if interleaved && len(content)%2 == 1 {
return nil, errors.New("can only encode even number of digits in interleaved mode")
}
mode := modes[interleaved]
resBits := new(utils.BitList)
resBits.AddBit(mode.start...)
var lastRune *rune
for _, r := range content {
var a, b pattern
if interleaved {
if lastRune == nil {
lastRune = new(rune)
*lastRune = r
continue
} else {
var o1, o2 bool
a, o1 = encodingTable[*lastRune]
b, o2 = encodingTable[r]
if !o1 || !o2 {
return nil, fmt.Errorf("can not encode \"%s\"", content)
}
lastRune = nil
}
} else {
var ok bool
a, ok = encodingTable[r]
if !ok {
return nil, fmt.Errorf("can not encode \"%s\"", content)
}
b = nonInterleavedSpace
}
for i := 0; i < patternWidth; i++ {
for x := 0; x < mode.widths[a[i]]; x++ {
resBits.AddBit(true)
}
for x := 0; x < mode.widths[b[i]]; x++ {
resBits.AddBit(false)
}
}
}
resBits.AddBit(mode.end...)
if interleaved {
return utils.New1DCode(barcode.Type2of5Interleaved, content, resBits), nil
} else {
return utils.New1DCode(barcode.Type2of5, content, resBits), nil
}
}

View file

@ -0,0 +1,45 @@
package twooffive
import (
"image/color"
"testing"
)
func Test_AddCheckSum(t *testing.T) {
if sum, err := AddCheckSum("1234567"); err != nil || sum != "12345670" {
t.Fail()
}
if _, err := AddCheckSum("1ABC"); err == nil {
t.Fail()
}
if _, err := AddCheckSum(""); err == nil {
t.Fail()
}
}
func Test_Encode(t *testing.T) {
_, err := Encode("FOOBAR", false)
if err == nil {
t.Error("\"FOOBAR\" should not be encodable")
}
testEncode := func(interleaved bool, txt, testResult string) {
code, err := Encode(txt, interleaved)
if err != nil || code == nil {
t.Fail()
} else {
if code.Bounds().Max.X != len(testResult) {
t.Errorf("%v: length missmatch! %v != %v", txt, code.Bounds().Max.X, len(testResult))
} else {
for i, r := range testResult {
if (code.At(i, 0) == color.Black) != (r == '1') {
t.Errorf("%v: code missmatch on position %d", txt, i)
}
}
}
}
}
testEncode(false, "12345670", "1101101011101010101110101110101011101110111010101010101110101110111010111010101011101110101010101011101110101011101110101101011")
testEncode(true, "12345670", "10101110100010101110001110111010001010001110100011100010101010100011100011101101")
}

View file

@ -0,0 +1,57 @@
// Package utils contain some utilities which are needed to create barcodes
package utils
import (
"image"
"image/color"
"github.com/boombuler/barcode"
)
type base1DCode struct {
*BitList
kind string
content string
}
type base1DCodeIntCS struct {
base1DCode
checksum int
}
func (c *base1DCode) Content() string {
return c.content
}
func (c *base1DCode) Metadata() barcode.Metadata {
return barcode.Metadata{c.kind, 1}
}
func (c *base1DCode) ColorModel() color.Model {
return color.Gray16Model
}
func (c *base1DCode) Bounds() image.Rectangle {
return image.Rect(0, 0, c.Len(), 1)
}
func (c *base1DCode) At(x, y int) color.Color {
if c.GetBit(x) {
return color.Black
}
return color.White
}
func (c *base1DCodeIntCS) CheckSum() int {
return c.checksum
}
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCodeIntCheckSum(codeKind, content string, bars *BitList, checksum int) barcode.BarcodeIntCS {
return &base1DCodeIntCS{base1DCode{bars, codeKind, content}, checksum}
}
// New1DCode creates a new 1D barcode where the bars are represented by the bits in the bars BitList
func New1DCode(codeKind, content string, bars *BitList) barcode.Barcode {
return &base1DCode{bars, codeKind, content}
}

119
vendor/github.com/boombuler/barcode/utils/bitlist.go generated vendored Normal file
View file

@ -0,0 +1,119 @@
package utils
// BitList is a list that contains bits
type BitList struct {
count int
data []int32
}
// NewBitList returns a new BitList with the given length
// all bits are initialize with false
func NewBitList(capacity int) *BitList {
bl := new(BitList)
bl.count = capacity
x := 0
if capacity%32 != 0 {
x = 1
}
bl.data = make([]int32, capacity/32+x)
return bl
}
// Len returns the number of contained bits
func (bl *BitList) Len() int {
return bl.count
}
func (bl *BitList) grow() {
growBy := len(bl.data)
if growBy < 128 {
growBy = 128
} else if growBy >= 1024 {
growBy = 1024
}
nd := make([]int32, len(bl.data)+growBy)
copy(nd, bl.data)
bl.data = nd
}
// AddBit appends the given bits to the end of the list
func (bl *BitList) AddBit(bits ...bool) {
for _, bit := range bits {
itmIndex := bl.count / 32
for itmIndex >= len(bl.data) {
bl.grow()
}
bl.SetBit(bl.count, bit)
bl.count++
}
}
// SetBit sets the bit at the given index to the given value
func (bl *BitList) SetBit(index int, value bool) {
itmIndex := index / 32
itmBitShift := 31 - (index % 32)
if value {
bl.data[itmIndex] = bl.data[itmIndex] | 1<<uint(itmBitShift)
} else {
bl.data[itmIndex] = bl.data[itmIndex] & ^(1 << uint(itmBitShift))
}
}
// GetBit returns the bit at the given index
func (bl *BitList) GetBit(index int) bool {
itmIndex := index / 32
itmBitShift := 31 - (index % 32)
return ((bl.data[itmIndex] >> uint(itmBitShift)) & 1) == 1
}
// AddByte appends all 8 bits of the given byte to the end of the list
func (bl *BitList) AddByte(b byte) {
for i := 7; i >= 0; i-- {
bl.AddBit(((b >> uint(i)) & 1) == 1)
}
}
// AddBits appends the last (LSB) 'count' bits of 'b' the the end of the list
func (bl *BitList) AddBits(b int, count byte) {
for i := int(count) - 1; i >= 0; i-- {
bl.AddBit(((b >> uint(i)) & 1) == 1)
}
}
// GetBytes returns all bits of the BitList as a []byte
func (bl *BitList) GetBytes() []byte {
len := bl.count >> 3
if (bl.count % 8) != 0 {
len++
}
result := make([]byte, len)
for i := 0; i < len; i++ {
shift := (3 - (i % 4)) * 8
result[i] = (byte)((bl.data[i/4] >> uint(shift)) & 0xFF)
}
return result
}
// IterateBytes iterates through all bytes contained in the BitList
func (bl *BitList) IterateBytes() <-chan byte {
res := make(chan byte)
go func() {
c := bl.count
shift := 24
i := 0
for c > 0 {
res <- byte((bl.data[i] >> uint(shift)) & 0xFF)
shift -= 8
if shift < 0 {
shift = 24
i++
}
c -= 8
}
close(res)
}()
return res
}

View file

@ -0,0 +1,65 @@
package utils
// GaloisField encapsulates galois field arithmetics
type GaloisField struct {
Size int
Base int
ALogTbl []int
LogTbl []int
}
// NewGaloisField creates a new galois field
func NewGaloisField(pp, fieldSize, b int) *GaloisField {
result := new(GaloisField)
result.Size = fieldSize
result.Base = b
result.ALogTbl = make([]int, fieldSize)
result.LogTbl = make([]int, fieldSize)
x := 1
for i := 0; i < fieldSize; i++ {
result.ALogTbl[i] = x
x = x * 2
if x >= fieldSize {
x = (x ^ pp) & (fieldSize - 1)
}
}
for i := 0; i < fieldSize; i++ {
result.LogTbl[result.ALogTbl[i]] = int(i)
}
return result
}
func (gf *GaloisField) Zero() *GFPoly {
return NewGFPoly(gf, []int{0})
}
// AddOrSub add or substract two numbers
func (gf *GaloisField) AddOrSub(a, b int) int {
return a ^ b
}
// Multiply multiplys two numbers
func (gf *GaloisField) Multiply(a, b int) int {
if a == 0 || b == 0 {
return 0
}
return gf.ALogTbl[(gf.LogTbl[a]+gf.LogTbl[b])%(gf.Size-1)]
}
// Divide divides two numbers
func (gf *GaloisField) Divide(a, b int) int {
if b == 0 {
panic("divide by zero")
} else if a == 0 {
return 0
}
return gf.ALogTbl[(gf.LogTbl[a]-gf.LogTbl[b])%(gf.Size-1)]
}
func (gf *GaloisField) Invers(num int) int {
return gf.ALogTbl[(gf.Size-1)-gf.LogTbl[num]]
}

View file

@ -0,0 +1,59 @@
package utils
import (
"testing"
)
func Test_GF(t *testing.T) {
log := []int{
0, 255, 1, 240, 2, 225, 241, 53, 3, 38, 226, 133, 242, 43, 54, 210,
4, 195, 39, 114, 227, 106, 134, 28, 243, 140, 44, 23, 55, 118, 211, 234,
5, 219, 196, 96, 40, 222, 115, 103, 228, 78, 107, 125, 135, 8, 29, 162,
244, 186, 141, 180, 45, 99, 24, 49, 56, 13, 119, 153, 212, 199, 235, 91,
6, 76, 220, 217, 197, 11, 97, 184, 41, 36, 223, 253, 116, 138, 104, 193,
229, 86, 79, 171, 108, 165, 126, 145, 136, 34, 9, 74, 30, 32, 163, 84,
245, 173, 187, 204, 142, 81, 181, 190, 46, 88, 100, 159, 25, 231, 50, 207,
57, 147, 14, 67, 120, 128, 154, 248, 213, 167, 200, 63, 236, 110, 92, 176,
7, 161, 77, 124, 221, 102, 218, 95, 198, 90, 12, 152, 98, 48, 185, 179,
42, 209, 37, 132, 224, 52, 254, 239, 117, 233, 139, 22, 105, 27, 194, 113,
230, 206, 87, 158, 80, 189, 172, 203, 109, 175, 166, 62, 127, 247, 146, 66,
137, 192, 35, 252, 10, 183, 75, 216, 31, 83, 33, 73, 164, 144, 85, 170,
246, 65, 174, 61, 188, 202, 205, 157, 143, 169, 82, 72, 182, 215, 191, 251,
47, 178, 89, 151, 101, 94, 160, 123, 26, 112, 232, 21, 51, 238, 208, 131,
58, 69, 148, 18, 15, 16, 68, 17, 121, 149, 129, 19, 155, 59, 249, 70,
214, 250, 168, 71, 201, 156, 64, 60, 237, 130, 111, 20, 93, 122, 177, 150,
}
alog := []int{
1, 2, 4, 8, 16, 32, 64, 128, 45, 90, 180, 69, 138, 57, 114, 228,
229, 231, 227, 235, 251, 219, 155, 27, 54, 108, 216, 157, 23, 46, 92, 184,
93, 186, 89, 178, 73, 146, 9, 18, 36, 72, 144, 13, 26, 52, 104, 208,
141, 55, 110, 220, 149, 7, 14, 28, 56, 112, 224, 237, 247, 195, 171, 123,
246, 193, 175, 115, 230, 225, 239, 243, 203, 187, 91, 182, 65, 130, 41, 82,
164, 101, 202, 185, 95, 190, 81, 162, 105, 210, 137, 63, 126, 252, 213, 135,
35, 70, 140, 53, 106, 212, 133, 39, 78, 156, 21, 42, 84, 168, 125, 250,
217, 159, 19, 38, 76, 152, 29, 58, 116, 232, 253, 215, 131, 43, 86, 172,
117, 234, 249, 223, 147, 11, 22, 44, 88, 176, 77, 154, 25, 50, 100, 200,
189, 87, 174, 113, 226, 233, 255, 211, 139, 59, 118, 236, 245, 199, 163, 107,
214, 129, 47, 94, 188, 85, 170, 121, 242, 201, 191, 83, 166, 97, 194, 169,
127, 254, 209, 143, 51, 102, 204, 181, 71, 142, 49, 98, 196, 165, 103, 206,
177, 79, 158, 17, 34, 68, 136, 61, 122, 244, 197, 167, 99, 198, 161, 111,
222, 145, 15, 30, 60, 120, 240, 205, 183, 67, 134, 33, 66, 132, 37, 74,
148, 5, 10, 20, 40, 80, 160, 109, 218, 153, 31, 62, 124, 248, 221, 151,
3, 6, 12, 24, 48, 96, 192, 173, 119, 238, 241, 207, 179, 75, 150, 1,
}
gf := NewGaloisField(301, 256, 1)
if len(gf.LogTbl) != len(gf.ALogTbl) || len(gf.LogTbl) != len(log) {
t.Fail()
}
for i := 0; i < len(log); i++ {
if gf.LogTbl[i] != log[i] {
t.Error("Invalid Log Table")
}
if gf.ALogTbl[i] != alog[i] {
t.Error("Invalid ALog Table")
}
}
}

103
vendor/github.com/boombuler/barcode/utils/gfpoly.go generated vendored Normal file
View file

@ -0,0 +1,103 @@
package utils
type GFPoly struct {
gf *GaloisField
Coefficients []int
}
func (gp *GFPoly) Degree() int {
return len(gp.Coefficients) - 1
}
func (gp *GFPoly) Zero() bool {
return gp.Coefficients[0] == 0
}
// GetCoefficient returns the coefficient of x ^ degree
func (gp *GFPoly) GetCoefficient(degree int) int {
return gp.Coefficients[gp.Degree()-degree]
}
func (gp *GFPoly) AddOrSubstract(other *GFPoly) *GFPoly {
if gp.Zero() {
return other
} else if other.Zero() {
return gp
}
smallCoeff := gp.Coefficients
largeCoeff := other.Coefficients
if len(smallCoeff) > len(largeCoeff) {
largeCoeff, smallCoeff = smallCoeff, largeCoeff
}
sumDiff := make([]int, len(largeCoeff))
lenDiff := len(largeCoeff) - len(smallCoeff)
copy(sumDiff, largeCoeff[:lenDiff])
for i := lenDiff; i < len(largeCoeff); i++ {
sumDiff[i] = int(gp.gf.AddOrSub(int(smallCoeff[i-lenDiff]), int(largeCoeff[i])))
}
return NewGFPoly(gp.gf, sumDiff)
}
func (gp *GFPoly) MultByMonominal(degree int, coeff int) *GFPoly {
if coeff == 0 {
return gp.gf.Zero()
}
size := len(gp.Coefficients)
result := make([]int, size+degree)
for i := 0; i < size; i++ {
result[i] = int(gp.gf.Multiply(int(gp.Coefficients[i]), int(coeff)))
}
return NewGFPoly(gp.gf, result)
}
func (gp *GFPoly) Multiply(other *GFPoly) *GFPoly {
if gp.Zero() || other.Zero() {
return gp.gf.Zero()
}
aCoeff := gp.Coefficients
aLen := len(aCoeff)
bCoeff := other.Coefficients
bLen := len(bCoeff)
product := make([]int, aLen+bLen-1)
for i := 0; i < aLen; i++ {
ac := int(aCoeff[i])
for j := 0; j < bLen; j++ {
bc := int(bCoeff[j])
product[i+j] = int(gp.gf.AddOrSub(int(product[i+j]), gp.gf.Multiply(ac, bc)))
}
}
return NewGFPoly(gp.gf, product)
}
func (gp *GFPoly) Divide(other *GFPoly) (quotient *GFPoly, remainder *GFPoly) {
quotient = gp.gf.Zero()
remainder = gp
fld := gp.gf
denomLeadTerm := other.GetCoefficient(other.Degree())
inversDenomLeadTerm := fld.Invers(int(denomLeadTerm))
for remainder.Degree() >= other.Degree() && !remainder.Zero() {
degreeDiff := remainder.Degree() - other.Degree()
scale := int(fld.Multiply(int(remainder.GetCoefficient(remainder.Degree())), inversDenomLeadTerm))
term := other.MultByMonominal(degreeDiff, scale)
itQuot := NewMonominalPoly(fld, degreeDiff, scale)
quotient = quotient.AddOrSubstract(itQuot)
remainder = remainder.AddOrSubstract(term)
}
return
}
func NewMonominalPoly(field *GaloisField, degree int, coeff int) *GFPoly {
if coeff == 0 {
return field.Zero()
}
result := make([]int, degree+1)
result[0] = coeff
return NewGFPoly(field, result)
}
func NewGFPoly(field *GaloisField, coefficients []int) *GFPoly {
for len(coefficients) > 1 && coefficients[0] == 0 {
coefficients = coefficients[1:]
}
return &GFPoly{field, coefficients}
}

View file

@ -0,0 +1,44 @@
package utils
import (
"sync"
)
type ReedSolomonEncoder struct {
gf *GaloisField
polynomes []*GFPoly
m *sync.Mutex
}
func NewReedSolomonEncoder(gf *GaloisField) *ReedSolomonEncoder {
return &ReedSolomonEncoder{
gf, []*GFPoly{NewGFPoly(gf, []int{1})}, new(sync.Mutex),
}
}
func (rs *ReedSolomonEncoder) getPolynomial(degree int) *GFPoly {
rs.m.Lock()
defer rs.m.Unlock()
if degree >= len(rs.polynomes) {
last := rs.polynomes[len(rs.polynomes)-1]
for d := len(rs.polynomes); d <= degree; d++ {
next := last.Multiply(NewGFPoly(rs.gf, []int{1, rs.gf.ALogTbl[d-1+rs.gf.Base]}))
rs.polynomes = append(rs.polynomes, next)
last = next
}
}
return rs.polynomes[degree]
}
func (rs *ReedSolomonEncoder) Encode(data []int, eccCount int) []int {
generator := rs.getPolynomial(eccCount)
info := NewGFPoly(rs.gf, data)
info = info.MultByMonominal(eccCount, 1)
_, remainder := info.Divide(generator)
result := make([]int, eccCount)
numZero := int(eccCount) - len(remainder.Coefficients)
copy(result[numZero:], remainder.Coefficients)
return result
}

19
vendor/github.com/boombuler/barcode/utils/runeint.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
package utils
// RuneToInt converts a rune between '0' and '9' to an integer between 0 and 9
// If the rune is outside of this range -1 is returned.
func RuneToInt(r rune) int {
if r >= '0' && r <= '9' {
return int(r - '0')
}
return -1
}
// IntToRune converts a digit 0 - 9 to the rune '0' - '9'. If the given int is outside
// of this range 'F' is returned!
func IntToRune(i int) rune {
if i >= 0 && i <= 9 {
return rune(i + '0')
}
return 'F'
}

View file

@ -0,0 +1,24 @@
package utils
import "testing"
func Test_RuneToIntIntToRune(t *testing.T) {
if IntToRune(0) != '0' {
t.Errorf("failed IntToRune(0) returned %d", string(IntToRune(0)))
}
if IntToRune(9) != '9' {
t.Errorf("failed IntToRune(9) returned %d", IntToRune(9))
}
if IntToRune(10) != 'F' {
t.Errorf("failed IntToRune(10) returned %d", IntToRune(10))
}
if RuneToInt('0') != 0 {
t.Error("failed RuneToInt('0') returned %d", RuneToInt(0))
}
if RuneToInt('9') != 9 {
t.Error("failed RuneToInt('9') returned %d", RuneToInt(9))
}
if RuneToInt('F') != -1 {
t.Error("failed RuneToInt('F') returned %d", RuneToInt('F'))
}
}

6
vendor/github.com/pquerna/otp/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,6 @@
language: go
go:
- 1.7
- 1.8
- tip

202
vendor/github.com/pquerna/otp/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

5
vendor/github.com/pquerna/otp/NOTICE generated vendored Normal file
View file

@ -0,0 +1,5 @@
otp
Copyright (c) 2014, Paul Querna
This product includes software developed by
Paul Querna (http://paul.querna.org/).

60
vendor/github.com/pquerna/otp/README.md generated vendored Normal file
View file

@ -0,0 +1,60 @@
# otp: One Time Password utilities Go / Golang
[![GoDoc](https://godoc.org/github.com/pquerna/otp?status.svg)](https://godoc.org/github.com/pquerna/otp) [![Build Status](https://travis-ci.org/pquerna/otp.svg?branch=master)](https://travis-ci.org/pquerna/otp)
# Why One Time Passwords?
One Time Passwords (OTPs) are an mechanism to improve security over passwords alone. When a Time-based OTP (TOTP) is stored on a user's phone, and combined with something the user knows (Password), you have an easy on-ramp to [Multi-factor authentication](http://en.wikipedia.org/wiki/Multi-factor_authentication) without adding a dependency on a SMS provider. This Password and TOTP combination is used by many popular websites including Google, Github, Facebook, Salesforce and many others.
The `otp` library enables you to easily add TOTPs to your own application, increasing your user's security against mass-password breaches and malware.
Because TOTP is standardized and widely deployed, there are many [mobile clients and software implementations](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm#Client_implementations).
## `otp` Supports:
* Generating QR Code images for easy user enrollment.
* Time-based One-time Password Algorithm (TOTP) (RFC 6238): Time based OTP, the most commonly used method.
* HMAC-based One-time Password Algorithm (HOTP) (RFC 4226): Counter based OTP, which TOTP is based upon.
* Generation and Validation of codes for either algorithm.
## Implementing TOTP in your application:
### User Enrollment
For an example of a working enrollment work flow, [Github has documented theirs](https://help.github.com/articles/configuring-two-factor-authentication-via-a-totp-mobile-app/
), but the basics are:
1. Generate new TOTP Key for a User. `key,_ := totp.Generate(...)`.
1. Display the Key's Secret and QR-Code for the User. `key.Secret()` and `key.Image(...)`.
1. Test that the user can successfully use their TOTP. `totp.Validate(...)`.
1. Store TOTP Secret for the User in your backend. `key.Secret()`
1. Provide the user with "recovery codes". (See Recovery Codes bellow)
### Code Generation
* In either TOTP or HOTP cases, use the `GenerateCode` function and a counter or
`time.Time` struct to generate a valid code compatible with most implementations.
* For uncommon or custom settings, or to catch unlikely errors, use `GenerateCodeCustom`
in either module.
### Validation
1. Prompt and validate User's password as normal.
1. If the user has TOTP enabled, prompt for TOTP passcode.
1. Retrieve the User's TOTP Secret from your backend.
1. Validate the user's passcode. `totp.Validate(...)`
### Recovery Codes
When a user loses access to their TOTP device, they would no longer have access to their account. Because TOTPs are often configured on mobile devices that can be lost, stolen or damaged, this is a common problem. For this reason many providers give their users "backup codes" or "recovery codes". These are a set of one time use codes that can be used instead of the TOTP. These can simply be randomly generated strings that you store in your backend. [Github's documentation provides an overview of the user experience](
https://help.github.com/articles/downloading-your-two-factor-authentication-recovery-codes/).
## Improvements, bugs, adding feature, etc:
Please [open issues in Github](https://github.com/pquerna/otp/issues) for ideas, bugs, and general thoughts. Pull requests are of course preferred :)
## License
`otp` is licensed under the [Apache License, Version 2.0](./LICENSE)

70
vendor/github.com/pquerna/otp/doc.go generated vendored Normal file
View file

@ -0,0 +1,70 @@
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// Package otp implements both HOTP and TOTP based
// one time passcodes in a Google Authenticator compatible manner.
//
// When adding a TOTP for a user, you must store the "secret" value
// persistently. It is recommend to store the secret in an encrypted field in your
// datastore. Due to how TOTP works, it is not possible to store a hash
// for the secret value like you would a password.
//
// To enroll a user, you must first generate an OTP for them. Google
// Authenticator supports using a QR code as an enrollment method:
//
// import (
// "github.com/pquerna/otp/totp"
//
// "bytes"
// "image/png"
// )
//
// key, err := totp.Generate(totp.GenerateOpts{
// Issuer: "Example.com",
// AccountName: "alice@example.com",
// })
//
// // Convert TOTP key into a QR code encoded as a PNG image.
// var buf bytes.Buffer
// img, err := key.Image(200, 200)
// png.Encode(&buf, img)
//
// // display the QR code to the user.
// display(buf.Bytes())
//
// // Now Validate that the user's successfully added the passcode.
// passcode := promptForPasscode()
// valid := totp.Validate(passcode, key.Secret())
//
// if valid {
// // User successfully used their TOTP, save it to your backend!
// storeSecret("alice@example.com", key.Secret())
// }
//
// Validating a TOTP passcode is very easy, just prompt the user for a passcode
// and retrieve the associated user's previously stored secret.
// import "github.com/pquerna/otp/totp"
//
// passcode := promptForPasscode()
// secret := getSecret("alice@example.com")
//
// valid := totp.Validate(passcode, secret)
//
// if valid {
// // Success! continue login process.
// }
package otp

63
vendor/github.com/pquerna/otp/example/main.go generated vendored Normal file
View file

@ -0,0 +1,63 @@
package main
import (
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
"bufio"
"bytes"
"fmt"
"image/png"
"io/ioutil"
"os"
)
func display(key *otp.Key, data []byte) {
fmt.Printf("Issuer: %s\n", key.Issuer())
fmt.Printf("Account Name: %s\n", key.AccountName())
fmt.Printf("Secret: %s\n", key.Secret())
fmt.Println("Writing PNG to qr-code.png....")
ioutil.WriteFile("qr-code.png", data, 0644)
fmt.Println("")
fmt.Println("Please add your TOTP to your OTP Application now!")
fmt.Println("")
}
func promptForPasscode() string {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Passcode: ")
text, _ := reader.ReadString('\n')
return text
}
func main() {
key, err := totp.Generate(totp.GenerateOpts{
Issuer: "Example.com",
AccountName: "alice@example.com",
})
if err != nil {
panic(err)
}
// Convert TOTP key into a PNG
var buf bytes.Buffer
img, err := key.Image(200, 200)
if err != nil {
panic(err)
}
png.Encode(&buf, img)
// display the QR code to the user.
display(key, buf.Bytes())
// Now Validate that the user's successfully added the passcode.
fmt.Println("Validating TOTP...")
passcode := promptForPasscode()
valid := totp.Validate(passcode, key.Secret())
if valid {
println("Valid passcode!")
os.Exit(0)
} else {
println("Invalid passocde!")
os.Exit(1)
}
}

188
vendor/github.com/pquerna/otp/hotp/hotp.go generated vendored Normal file
View file

@ -0,0 +1,188 @@
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package hotp
import (
"github.com/pquerna/otp"
"crypto/hmac"
"crypto/rand"
"crypto/subtle"
"encoding/base32"
"encoding/binary"
"fmt"
"math"
"net/url"
"strings"
)
const debug = false
// Validate a HOTP passcode given a counter and secret.
// This is a shortcut for ValidateCustom, with parameters that
// are compataible with Google-Authenticator.
func Validate(passcode string, counter uint64, secret string) bool {
rv, _ := ValidateCustom(
passcode,
counter,
secret,
ValidateOpts{
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
},
)
return rv
}
// ValidateOpts provides options for ValidateCustom().
type ValidateOpts struct {
// Digits as part of the input. Defaults to 6.
Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm
}
// GenerateCode creates a HOTP passcode given a counter and secret.
// This is a shortcut for GenerateCodeCustom, with parameters that
// are compataible with Google-Authenticator.
func GenerateCode(secret string, counter uint64) (string, error) {
return GenerateCodeCustom(secret, counter, ValidateOpts{
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
}
// GenerateCodeCustom uses a counter and secret value and options struct to
// create a passcode.
func GenerateCodeCustom(secret string, counter uint64, opts ValidateOpts) (passcode string, err error) {
// As noted in issue #10 and #17 this adds support for TOTP secrets that are
// missing their padding.
secret = strings.TrimSpace(secret)
if n := len(secret) % 8; n != 0 {
secret = secret + strings.Repeat("=", 8-n)
}
secretBytes, err := base32.StdEncoding.DecodeString(secret)
if err != nil {
return "", otp.ErrValidateSecretInvalidBase32
}
buf := make([]byte, 8)
mac := hmac.New(opts.Algorithm.Hash, secretBytes)
binary.BigEndian.PutUint64(buf, counter)
if debug {
fmt.Printf("counter=%v\n", counter)
fmt.Printf("buf=%v\n", buf)
}
mac.Write(buf)
sum := mac.Sum(nil)
// "Dynamic truncation" in RFC 4226
// http://tools.ietf.org/html/rfc4226#section-5.4
offset := sum[len(sum)-1] & 0xf
value := int64(((int(sum[offset]) & 0x7f) << 24) |
((int(sum[offset+1] & 0xff)) << 16) |
((int(sum[offset+2] & 0xff)) << 8) |
(int(sum[offset+3]) & 0xff))
l := opts.Digits.Length()
mod := int32(value % int64(math.Pow10(l)))
if debug {
fmt.Printf("offset=%v\n", offset)
fmt.Printf("value=%v\n", value)
fmt.Printf("mod'ed=%v\n", mod)
}
return opts.Digits.Format(mod), nil
}
// ValidateCustom validates an HOTP with customizable options. Most users should
// use Validate().
func ValidateCustom(passcode string, counter uint64, secret string, opts ValidateOpts) (bool, error) {
passcode = strings.TrimSpace(passcode)
if len(passcode) != opts.Digits.Length() {
return false, otp.ErrValidateInputInvalidLength
}
otpstr, err := GenerateCodeCustom(secret, counter, opts)
if err != nil {
return false, err
}
if subtle.ConstantTimeCompare([]byte(otpstr), []byte(passcode)) == 1 {
return true, nil
}
return false, nil
}
// GenerateOpts provides options for .Generate()
type GenerateOpts struct {
// Name of the issuing Organization/Company.
Issuer string
// Name of the User's Account (eg, email address)
AccountName string
// Size in size of the generated Secret. Defaults to 10 bytes.
SecretSize uint
// Digits to request. Defaults to 6.
Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm
}
// Generate creates a new HOTP Key.
func Generate(opts GenerateOpts) (*otp.Key, error) {
// url encode the Issuer/AccountName
if opts.Issuer == "" {
return nil, otp.ErrGenerateMissingIssuer
}
if opts.AccountName == "" {
return nil, otp.ErrGenerateMissingAccountName
}
if opts.SecretSize == 0 {
opts.SecretSize = 10
}
// otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
v := url.Values{}
secret := make([]byte, opts.SecretSize)
_, err := rand.Read(secret)
if err != nil {
return nil, err
}
v.Set("secret", strings.TrimRight(base32.StdEncoding.EncodeToString(secret), "="))
v.Set("issuer", opts.Issuer)
v.Set("algorithm", opts.Algorithm.String())
v.Set("digits", opts.Digits.String())
u := url.URL{
Scheme: "otpauth",
Host: "hotp",
Path: "/" + opts.Issuer + ":" + opts.AccountName,
RawQuery: v.Encode(),
}
return otp.NewKeyFromURL(u.String())
}

162
vendor/github.com/pquerna/otp/hotp/hotp_test.go generated vendored Normal file
View file

@ -0,0 +1,162 @@
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package hotp
import (
"github.com/pquerna/otp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"encoding/base32"
"testing"
)
type tc struct {
Counter uint64
TOTP string
Mode otp.Algorithm
Secret string
}
var (
secSha1 = base32.StdEncoding.EncodeToString([]byte("12345678901234567890"))
rfcMatrixTCs = []tc{
tc{0, "755224", otp.AlgorithmSHA1, secSha1},
tc{1, "287082", otp.AlgorithmSHA1, secSha1},
tc{2, "359152", otp.AlgorithmSHA1, secSha1},
tc{3, "969429", otp.AlgorithmSHA1, secSha1},
tc{4, "338314", otp.AlgorithmSHA1, secSha1},
tc{5, "254676", otp.AlgorithmSHA1, secSha1},
tc{6, "287922", otp.AlgorithmSHA1, secSha1},
tc{7, "162583", otp.AlgorithmSHA1, secSha1},
tc{8, "399871", otp.AlgorithmSHA1, secSha1},
tc{9, "520489", otp.AlgorithmSHA1, secSha1},
}
)
// Test values from http://tools.ietf.org/html/rfc4226#appendix-D
func TestValidateRFCMatrix(t *testing.T) {
for _, tx := range rfcMatrixTCs {
valid, err := ValidateCustom(tx.TOTP, tx.Counter, tx.Secret,
ValidateOpts{
Digits: otp.DigitsSix,
Algorithm: tx.Mode,
})
require.NoError(t, err,
"unexpected error totp=%s mode=%v counter=%v", tx.TOTP, tx.Mode, tx.Counter)
require.True(t, valid,
"unexpected totp failure totp=%s mode=%v counter=%v", tx.TOTP, tx.Mode, tx.Counter)
}
}
func TestGenerateRFCMatrix(t *testing.T) {
for _, tx := range rfcMatrixTCs {
passcode, err := GenerateCodeCustom(tx.Secret, tx.Counter,
ValidateOpts{
Digits: otp.DigitsSix,
Algorithm: tx.Mode,
})
assert.Nil(t, err)
assert.Equal(t, tx.TOTP, passcode)
}
}
func TestValidateInvalid(t *testing.T) {
secSha1 := base32.StdEncoding.EncodeToString([]byte("12345678901234567890"))
valid, err := ValidateCustom("foo", 11, secSha1,
ValidateOpts{
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
require.Equal(t, otp.ErrValidateInputInvalidLength, err, "Expected Invalid length error.")
require.Equal(t, false, valid, "Valid should be false when we have an error.")
valid, err = ValidateCustom("foo", 11, secSha1,
ValidateOpts{
Digits: otp.DigitsEight,
Algorithm: otp.AlgorithmSHA1,
})
require.Equal(t, otp.ErrValidateInputInvalidLength, err, "Expected Invalid length error.")
require.Equal(t, false, valid, "Valid should be false when we have an error.")
valid, err = ValidateCustom("000000", 11, secSha1,
ValidateOpts{
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
require.NoError(t, err, "Expected no error.")
require.Equal(t, false, valid, "Valid should be false.")
valid = Validate("000000", 11, secSha1)
require.Equal(t, false, valid, "Valid should be false.")
}
// This tests for issue #10 - secrets without padding
func TestValidatePadding(t *testing.T) {
valid, err := ValidateCustom("831097", 0, "JBSWY3DPEHPK3PX",
ValidateOpts{
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
require.NoError(t, err, "Expected no error.")
require.Equal(t, true, valid, "Valid should be true.")
}
func TestGenerate(t *testing.T) {
k, err := Generate(GenerateOpts{
Issuer: "SnakeOil",
AccountName: "alice@example.com",
})
require.NoError(t, err, "generate basic TOTP")
require.Equal(t, "SnakeOil", k.Issuer(), "Extracting Issuer")
require.Equal(t, "alice@example.com", k.AccountName(), "Extracting Account Name")
require.Equal(t, 16, len(k.Secret()), "Secret is 16 bytes long as base32.")
k, err = Generate(GenerateOpts{
Issuer: "SnakeOil",
AccountName: "alice@example.com",
SecretSize: 20,
})
require.NoError(t, err, "generate larger TOTP")
require.Equal(t, 32, len(k.Secret()), "Secret is 32 bytes long as base32.")
k, err = Generate(GenerateOpts{
Issuer: "",
AccountName: "alice@example.com",
})
require.Equal(t, otp.ErrGenerateMissingIssuer, err, "generate missing issuer")
require.Nil(t, k, "key should be nil on error.")
k, err = Generate(GenerateOpts{
Issuer: "Foobar, Inc",
AccountName: "",
})
require.Equal(t, otp.ErrGenerateMissingAccountName, err, "generate missing account name.")
require.Nil(t, k, "key should be nil on error.")
k, err = Generate(GenerateOpts{
Issuer: "SnakeOil",
AccountName: "alice@example.com",
SecretSize: 17, // anything that is not divisable by 5, really
})
require.NoError(t, err, "Secret size is valid when length not divisable by 5.")
require.NotContains(t, k.Secret(), "=", "Secret has no escaped characters.")
}

202
vendor/github.com/pquerna/otp/otp.go generated vendored Normal file
View file

@ -0,0 +1,202 @@
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package otp
import (
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/qr"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"errors"
"fmt"
"hash"
"image"
"net/url"
"strings"
)
// Error when attempting to convert the secret from base32 to raw bytes.
var ErrValidateSecretInvalidBase32 = errors.New("Decoding of secret as base32 failed.")
// The user provided passcode length was not expected.
var ErrValidateInputInvalidLength = errors.New("Input length unexpected")
// When generating a Key, the Issuer must be set.
var ErrGenerateMissingIssuer = errors.New("Issuer must be set")
// When generating a Key, the Account Name must be set.
var ErrGenerateMissingAccountName = errors.New("AccountName must be set")
// Key represents an TOTP or HTOP key.
type Key struct {
orig string
url *url.URL
}
// NewKeyFromURL creates a new Key from an TOTP or HOTP url.
//
// The URL format is documented here:
// https://github.com/google/google-authenticator/wiki/Key-Uri-Format
//
func NewKeyFromURL(orig string) (*Key, error) {
s := strings.TrimSpace(orig)
u, err := url.Parse(s)
if err != nil {
return nil, err
}
return &Key{
orig: s,
url: u,
}, nil
}
func (k *Key) String() string {
return k.orig
}
// Image returns an QR-Code image of the specified width and height,
// suitable for use by many clients like Google-Authenricator
// to enroll a user's TOTP/HOTP key.
func (k *Key) Image(width int, height int) (image.Image, error) {
b, err := qr.Encode(k.orig, qr.M, qr.Auto)
if err != nil {
return nil, err
}
b, err = barcode.Scale(b, width, height)
if err != nil {
return nil, err
}
return b, nil
}
// Type returns "hotp" or "totp".
func (k *Key) Type() string {
return k.url.Host
}
// Issuer returns the name of the issuing organization.
func (k *Key) Issuer() string {
q := k.url.Query()
issuer := q.Get("issuer")
if issuer != "" {
return issuer
}
p := strings.TrimPrefix(k.url.Path, "/")
i := strings.Index(p, ":")
if i == -1 {
return ""
}
return p[:i]
}
// AccountName returns the name of the user's account.
func (k *Key) AccountName() string {
p := strings.TrimPrefix(k.url.Path, "/")
i := strings.Index(p, ":")
if i == -1 {
return p
}
return p[i+1:]
}
// Secret returns the opaque secret for this Key.
func (k *Key) Secret() string {
q := k.url.Query()
return q.Get("secret")
}
// Algorithm represents the hashing function to use in the HMAC
// operation needed for OTPs.
type Algorithm int
const (
AlgorithmSHA1 Algorithm = iota
AlgorithmSHA256
AlgorithmSHA512
AlgorithmMD5
)
func (a Algorithm) String() string {
switch a {
case AlgorithmSHA1:
return "SHA1"
case AlgorithmSHA256:
return "SHA256"
case AlgorithmSHA512:
return "SHA512"
case AlgorithmMD5:
return "MD5"
}
panic("unreached")
}
func (a Algorithm) Hash() hash.Hash {
switch a {
case AlgorithmSHA1:
return sha1.New()
case AlgorithmSHA256:
return sha256.New()
case AlgorithmSHA512:
return sha512.New()
case AlgorithmMD5:
return md5.New()
}
panic("unreached")
}
// Digits represents the number of digits present in the
// user's OTP passcode. Six and Eight are the most common values.
type Digits int
const (
DigitsSix Digits = 6
DigitsEight Digits = 8
)
// Format converts an integer into the zero-filled size for this Digits.
func (d Digits) Format(in int32) string {
f := fmt.Sprintf("%%0%dd", d)
return fmt.Sprintf(f, in)
}
// Length returns the number of characters for this Digits.
func (d Digits) Length() int {
return int(d)
}
func (d Digits) String() string {
return fmt.Sprintf("%d", d)
}

55
vendor/github.com/pquerna/otp/otp_test.go generated vendored Normal file
View file

@ -0,0 +1,55 @@
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package otp
import (
"github.com/stretchr/testify/require"
"testing"
)
func TestKeyAllThere(t *testing.T) {
k, err := NewKeyFromURL(`otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example`)
require.NoError(t, err, "failed to parse url")
require.Equal(t, "totp", k.Type(), "Extracting Type")
require.Equal(t, "Example", k.Issuer(), "Extracting Issuer")
require.Equal(t, "alice@google.com", k.AccountName(), "Extracting Account Name")
require.Equal(t, "JBSWY3DPEHPK3PXP", k.Secret(), "Extracting Secret")
}
func TestKeyIssuerOnlyInPath(t *testing.T) {
k, err := NewKeyFromURL(`otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP`)
require.NoError(t, err, "failed to parse url")
require.Equal(t, "Example", k.Issuer(), "Extracting Issuer")
require.Equal(t, "alice@google.com", k.AccountName(), "Extracting Account Name")
}
func TestKeyNoIssuer(t *testing.T) {
k, err := NewKeyFromURL(`otpauth://totp/alice@google.com?secret=JBSWY3DPEHPK3PXP`)
require.NoError(t, err, "failed to parse url")
require.Equal(t, "", k.Issuer(), "Extracting Issuer")
require.Equal(t, "alice@google.com", k.AccountName(), "Extracting Account Name")
}
func TestKeyWithNewLine(t *testing.T) {
w, err := NewKeyFromURL(`otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP
`)
require.NoError(t, err)
sec := w.Secret()
require.Equal(t, "JBSWY3DPEHPK3PXP", sec)
}

193
vendor/github.com/pquerna/otp/totp/totp.go generated vendored Normal file
View file

@ -0,0 +1,193 @@
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package totp
import (
"strings"
"github.com/pquerna/otp"
"github.com/pquerna/otp/hotp"
"crypto/rand"
"encoding/base32"
"math"
"net/url"
"strconv"
"time"
)
// Validate a TOTP using the current time.
// A shortcut for ValidateCustom, Validate uses a configuration
// that is compatible with Google-Authenticator and most clients.
func Validate(passcode string, secret string) bool {
rv, _ := ValidateCustom(
passcode,
secret,
time.Now().UTC(),
ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
},
)
return rv
}
// GenerateCode creates a TOTP token using the current time.
// A shortcut for GenerateCodeCustom, GenerateCode uses a configuration
// that is compatible with Google-Authenticator and most clients.
func GenerateCode(secret string, t time.Time) (string, error) {
return GenerateCodeCustom(secret, t, ValidateOpts{
Period: 30,
Skew: 1,
Digits: otp.DigitsSix,
Algorithm: otp.AlgorithmSHA1,
})
}
// ValidateOpts provides options for ValidateCustom().
type ValidateOpts struct {
// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
Period uint
// Periods before or after the current time to allow. Value of 1 allows up to Period
// of either side of the specified time. Defaults to 0 allowed skews. Values greater
// than 1 are likely sketchy.
Skew uint
// Digits as part of the input. Defaults to 6.
Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm
}
// GenerateCodeCustom takes a timepoint and produces a passcode using a
// secret and the provided opts. (Under the hood, this is making an adapted
// call to hotp.GenerateCodeCustom)
func GenerateCodeCustom(secret string, t time.Time, opts ValidateOpts) (passcode string, err error) {
if opts.Period == 0 {
opts.Period = 30
}
counter := uint64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
passcode, err = hotp.GenerateCodeCustom(secret, counter, hotp.ValidateOpts{
Digits: opts.Digits,
Algorithm: opts.Algorithm,
})
if err != nil {
return "", err
}
return passcode, nil
}
// ValidateCustom validates a TOTP given a user specified time and custom options.
// Most users should use Validate() to provide an interpolatable TOTP experience.
func ValidateCustom(passcode string, secret string, t time.Time, opts ValidateOpts) (bool, error) {
if opts.Period == 0 {
opts.Period = 30
}
counters := []uint64{}
counter := int64(math.Floor(float64(t.Unix()) / float64(opts.Period)))
counters = append(counters, uint64(counter))
for i := 1; i <= int(opts.Skew); i++ {
counters = append(counters, uint64(counter+int64(i)))
counters = append(counters, uint64(counter-int64(i)))
}
for _, counter := range counters {
rv, err := hotp.ValidateCustom(passcode, counter, secret, hotp.ValidateOpts{
Digits: opts.Digits,
Algorithm: opts.Algorithm,
})
if err != nil {
return false, err
}
if rv == true {
return true, nil
}
}
return false, nil
}
// GenerateOpts provides options for Generate(). The default values
// are compatible with Google-Authenticator.
type GenerateOpts struct {
// Name of the issuing Organization/Company.
Issuer string
// Name of the User's Account (eg, email address)
AccountName string
// Number of seconds a TOTP hash is valid for. Defaults to 30 seconds.
Period uint
// Size in size of the generated Secret. Defaults to 10 bytes.
SecretSize uint
// Digits to request. Defaults to 6.
Digits otp.Digits
// Algorithm to use for HMAC. Defaults to SHA1.
Algorithm otp.Algorithm
}
// Generate a new TOTP Key.
func Generate(opts GenerateOpts) (*otp.Key, error) {
// url encode the Issuer/AccountName
if opts.Issuer == "" {
return nil, otp.ErrGenerateMissingIssuer
}
if opts.AccountName == "" {
return nil, otp.ErrGenerateMissingAccountName
}
if opts.Period == 0 {
opts.Period = 30
}
if opts.SecretSize == 0 {
opts.SecretSize = 10
}
if opts.Digits == 0 {
opts.Digits = otp.DigitsSix
}
// otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example
v := url.Values{}
secret := make([]byte, opts.SecretSize)
_, err := rand.Read(secret)
if err != nil {
return nil, err
}
v.Set("secret", strings.TrimRight(base32.StdEncoding.EncodeToString(secret), "="))
v.Set("issuer", opts.Issuer)
v.Set("period", strconv.FormatUint(uint64(opts.Period), 10))
v.Set("algorithm", opts.Algorithm.String())
v.Set("digits", opts.Digits.String())
u := url.URL{
Scheme: "otpauth",
Host: "totp",
Path: "/" + opts.Issuer + ":" + opts.AccountName,
RawQuery: v.Encode(),
}
return otp.NewKeyFromURL(u.String())
}

146
vendor/github.com/pquerna/otp/totp/totp_test.go generated vendored Normal file
View file

@ -0,0 +1,146 @@
/**
* Copyright 2014 Paul Querna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package totp
import (
"github.com/pquerna/otp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"encoding/base32"
"testing"
"time"
)
type tc struct {
TS int64
TOTP string
Mode otp.Algorithm
Secret string
}
var (
secSha1 = base32.StdEncoding.EncodeToString([]byte("12345678901234567890"))
secSha256 = base32.StdEncoding.EncodeToString([]byte("12345678901234567890123456789012"))
secSha512 = base32.StdEncoding.EncodeToString([]byte("1234567890123456789012345678901234567890123456789012345678901234"))
rfcMatrixTCs = []tc{
tc{59, "94287082", otp.AlgorithmSHA1, secSha1},
tc{59, "46119246", otp.AlgorithmSHA256, secSha256},
tc{59, "90693936", otp.AlgorithmSHA512, secSha512},
tc{1111111109, "07081804", otp.AlgorithmSHA1, secSha1},
tc{1111111109, "68084774", otp.AlgorithmSHA256, secSha256},
tc{1111111109, "25091201", otp.AlgorithmSHA512, secSha512},
tc{1111111111, "14050471", otp.AlgorithmSHA1, secSha1},
tc{1111111111, "67062674", otp.AlgorithmSHA256, secSha256},
tc{1111111111, "99943326", otp.AlgorithmSHA512, secSha512},
tc{1234567890, "89005924", otp.AlgorithmSHA1, secSha1},
tc{1234567890, "91819424", otp.AlgorithmSHA256, secSha256},
tc{1234567890, "93441116", otp.AlgorithmSHA512, secSha512},
tc{2000000000, "69279037", otp.AlgorithmSHA1, secSha1},
tc{2000000000, "90698825", otp.AlgorithmSHA256, secSha256},
tc{2000000000, "38618901", otp.AlgorithmSHA512, secSha512},
tc{20000000000, "65353130", otp.AlgorithmSHA1, secSha1},
tc{20000000000, "77737706", otp.AlgorithmSHA256, secSha256},
tc{20000000000, "47863826", otp.AlgorithmSHA512, secSha512},
}
)
//
// Test vectors from http://tools.ietf.org/html/rfc6238#appendix-B
// NOTE -- the test vectors are documented as having the SAME
// secret -- this is WRONG -- they have a variable secret
// depending upon the hmac algorithm:
// http://www.rfc-editor.org/errata_search.php?rfc=6238
// this only took a few hours of head/desk interaction to figure out.
//
func TestValidateRFCMatrix(t *testing.T) {
for _, tx := range rfcMatrixTCs {
valid, err := ValidateCustom(tx.TOTP, tx.Secret, time.Unix(tx.TS, 0).UTC(),
ValidateOpts{
Digits: otp.DigitsEight,
Algorithm: tx.Mode,
})
require.NoError(t, err,
"unexpected error totp=%s mode=%v ts=%v", tx.TOTP, tx.Mode, tx.TS)
require.True(t, valid,
"unexpected totp failure totp=%s mode=%v ts=%v", tx.TOTP, tx.Mode, tx.TS)
}
}
func TestGenerateRFCTCs(t *testing.T) {
for _, tx := range rfcMatrixTCs {
passcode, err := GenerateCodeCustom(tx.Secret, time.Unix(tx.TS, 0).UTC(),
ValidateOpts{
Digits: otp.DigitsEight,
Algorithm: tx.Mode,
})
assert.Nil(t, err)
assert.Equal(t, tx.TOTP, passcode)
}
}
func TestValidateSkew(t *testing.T) {
secSha1 := base32.StdEncoding.EncodeToString([]byte("12345678901234567890"))
tests := []tc{
tc{29, "94287082", otp.AlgorithmSHA1, secSha1},
tc{59, "94287082", otp.AlgorithmSHA1, secSha1},
tc{61, "94287082", otp.AlgorithmSHA1, secSha1},
}
for _, tx := range tests {
valid, err := ValidateCustom(tx.TOTP, tx.Secret, time.Unix(tx.TS, 0).UTC(),
ValidateOpts{
Digits: otp.DigitsEight,
Algorithm: tx.Mode,
Skew: 1,
})
require.NoError(t, err,
"unexpected error totp=%s mode=%v ts=%v", tx.TOTP, tx.Mode, tx.TS)
require.True(t, valid,
"unexpected totp failure totp=%s mode=%v ts=%v", tx.TOTP, tx.Mode, tx.TS)
}
}
func TestGenerate(t *testing.T) {
k, err := Generate(GenerateOpts{
Issuer: "SnakeOil",
AccountName: "alice@example.com",
})
require.NoError(t, err, "generate basic TOTP")
require.Equal(t, "SnakeOil", k.Issuer(), "Extracting Issuer")
require.Equal(t, "alice@example.com", k.AccountName(), "Extracting Account Name")
require.Equal(t, 16, len(k.Secret()), "Secret is 16 bytes long as base32.")
k, err = Generate(GenerateOpts{
Issuer: "SnakeOil",
AccountName: "alice@example.com",
SecretSize: 20,
})
require.NoError(t, err, "generate larger TOTP")
require.Equal(t, 32, len(k.Secret()), "Secret is 32 bytes long as base32.")
k, err = Generate(GenerateOpts{
Issuer: "SnakeOil",
AccountName: "alice@example.com",
SecretSize: 13, // anything that is not divisable by 5, really
})
require.NoError(t, err, "Secret size is valid when length not divisable by 5.")
require.NotContains(t, k.Secret(), "=", "Secret has no escaped characters.")
}