fix(glance): support prob iso image (#24133)

This commit is contained in:
屈轩
2026-01-28 15:28:56 +08:00
committed by GitHub
parent 7f54e038c0
commit fa08f8fd8f
261 changed files with 38588 additions and 9171 deletions
-1
View File
@@ -2,7 +2,6 @@ package llm
import (
"yunion.io/x/onecloud/cmd/climc/shell"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/llm"
commonoptions "yunion.io/x/onecloud/pkg/mcclient/options"
options "yunion.io/x/onecloud/pkg/mcclient/options/llm"
+87
View File
@@ -0,0 +1,87 @@
// Copyright 2019 Yunion
//
// 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 main
import (
"fmt"
"os"
"yunion.io/x/pkg/util/shellutils"
"yunion.io/x/structarg"
_ "yunion.io/x/onecloud/cmd/isocli/shell"
)
type BaseOptions struct {
Debug bool `help:"debug mode"`
SUBCOMMAND string `help:"aliyuncli subcommand" subcommand:"true"`
}
func getSubcommandParser() (*structarg.ArgumentParser, error) {
parse, e := structarg.NewArgumentParserWithHelp(&BaseOptions{},
"isocli",
"Command-line ISO tools.",
`See "isocli COMMAND --help" for help on a specific command.`)
if e != nil {
return nil, e
}
subcmd := parse.GetSubcommand()
if subcmd == nil {
return nil, fmt.Errorf("No subcommand argument.")
}
for _, v := range shellutils.CommandTable {
_, e := subcmd.AddSubParserWithHelp(v.Options, v.Command, v.Desc, v.Callback)
if e != nil {
return nil, e
}
}
return parse, nil
}
func showErrorAndExit(e error) {
fmt.Fprintf(os.Stderr, "%s", e)
fmt.Fprintln(os.Stderr)
os.Exit(1)
}
func main() {
parser, e := getSubcommandParser()
if e != nil {
showErrorAndExit(e)
}
e = parser.ParseArgs(os.Args[1:], false)
if parser.IsHelpSet() {
fmt.Print(parser.HelpString())
return
}
subcmd := parser.GetSubcommand()
subparser := subcmd.GetSubParser()
if e != nil || subparser == nil {
if subparser != nil {
fmt.Print(subparser.Usage())
} else {
fmt.Print(parser.Usage())
}
showErrorAndExit(e)
}
suboptions := subparser.Options()
e = subcmd.Invoke(suboptions)
if e != nil {
showErrorAndExit(e)
}
}
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2019 Yunion
//
// 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 shell
import (
"fmt"
"os"
"path/filepath"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/pkg/util/shellutils"
"yunion.io/x/onecloud/pkg/util/isoutils"
)
func init() {
type DetectOSOptions struct {
ISO string `help:"ISO file"`
}
shellutils.R(&DetectOSOptions{}, "detect", "Detect", func(args *DetectOSOptions) error {
stat, err := os.Stat(args.ISO)
if err != nil {
return err
}
if stat.IsDir() {
// 如果是目录,仅遍历一层目录下的 .iso 文件
entries, err := os.ReadDir(args.ISO)
if err != nil {
return err
}
for _, entry := range entries {
if entry.IsDir() {
continue // 跳过子目录
}
if strings.HasSuffix(strings.ToLower(entry.Name()), ".iso") {
path := filepath.Join(args.ISO, entry.Name())
fmt.Printf("Processing: %s\n", path)
f, err := os.Open(path)
if err != nil {
fmt.Printf("Error opening %s: %v\n", path, err)
continue // 继续处理其他文件
}
isoInfo, err := isoutils.DetectOSFromISO(f)
f.Close()
if err != nil {
fmt.Printf("Error detecting OS from %s: %v\n", path, err)
continue
}
fmt.Printf("%s: %s\n", path, jsonutils.Marshal(isoInfo))
}
}
return nil
}
// 如果是文件,按原逻辑处理
f, err := os.Open(args.ISO)
if err != nil {
return err
}
defer f.Close()
isoInfo, err := isoutils.DetectOSFromISO(f)
if err != nil {
return err
}
fmt.Printf("%s: %s\n", args.ISO, jsonutils.Marshal(isoInfo))
return nil
})
}
+12 -10
View File
@@ -6,6 +6,7 @@ require (
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669
github.com/360EntSecGroup-Skylar/excelize v1.4.0
github.com/LeeEirc/terminalparser v0.0.0-20240205084113-fbf78c8480f2
github.com/Microsoft/go-winio v0.6.2
github.com/aliyun/alibaba-cloud-sdk-go v1.61.684
github.com/anacrolix/torrent v1.57.0
github.com/benbjohnson/clock v1.0.0
@@ -39,6 +40,7 @@ require (
github.com/influxdata/influxql v1.1.0
github.com/influxdata/promql/v2 v2.12.0
github.com/jaypipes/ghw v0.11.0
github.com/kdomanski/iso9660 v0.4.0
github.com/koding/websocketproxy v0.0.0-20181220232114-7ed82d81a28c
github.com/lestrrat-go/jwx v1.0.2
github.com/lestrrat/go-jwx v0.0.0-20180221005942-b7d4802280ae
@@ -52,6 +54,7 @@ require (
github.com/miekg/dns v1.1.25
github.com/minio/minio-go v6.0.14+incompatible
github.com/mitchellh/go-wordwrap v1.0.1
github.com/mogaika/udf v0.0.0-20171019171931-167f0ab01c73
github.com/petermattis/goid v0.0.0-20250904145737-900bdf8bb490
github.com/pierrec/lz4/v4 v4.1.15
github.com/pkg/errors v0.9.1
@@ -78,19 +81,21 @@ require (
github.com/zexi/influxql-to-metricsql v0.1.1
go.etcd.io/etcd/api/v3 v3.5.0
go.etcd.io/etcd/client/v3 v3.5.0
golang.org/x/crypto v0.21.0
golang.org/x/net v0.23.0
golang.org/x/sync v0.8.0
golang.org/x/sys v0.26.0
golang.org/x/text v0.14.0
golang.org/x/crypto v0.41.0
golang.org/x/net v0.43.0
golang.org/x/sync v0.16.0
golang.org/x/sys v0.35.0
golang.org/x/text v0.28.0
golang.org/x/time v0.5.0
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028
golang.zx2c4.com/wireguard/wgctrl v0.0.0-20230215201556-9c5414ab4bde
google.golang.org/grpc v1.62.0
google.golang.org/protobuf v1.35.1
gopkg.in/fatih/set.v0 v0.2.1
gopkg.in/ini.v1 v1.62.0
gopkg.in/mail.v2 v2.3.1
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.1
k8s.io/api v0.19.3
k8s.io/apimachinery v0.19.3
k8s.io/client-go v0.19.3
@@ -103,7 +108,7 @@ require (
yunion.io/x/jsonutils v1.0.1-0.20250507052344-1abcf4f443b1
yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900
yunion.io/x/pkg v1.10.4-0.20251114095758-2a2f105d9712
yunion.io/x/pkg v1.10.4-0.20260127060125-8939521ef75e
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1
yunion.io/x/sqlchemy v1.1.3-0.20251231025938-b0a38f6e9fab
yunion.io/x/structarg v0.0.0-20231017124457-df4d5009457c
@@ -127,7 +132,6 @@ require (
github.com/ClickHouse/clickhouse-go v1.5.4 // indirect
github.com/DataDog/dd-trace-go v0.6.1 // indirect
github.com/DataDog/zstd v1.3.4 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.8.14 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect
github.com/Shopify/sarama v1.20.0 // indirect
@@ -371,7 +375,7 @@ require (
go.uber.org/zap v1.17.0 // indirect
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/term v0.34.0 // indirect
google.golang.org/api v0.167.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
@@ -379,8 +383,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20240228224816-df926f6c8641 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
lukechampine.com/blake3 v1.1.6 // indirect
modernc.org/libc v1.22.3 // indirect
+18 -14
View File
@@ -656,6 +656,8 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uia
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw=
github.com/karrick/godirwalk v1.16.1/go.mod h1:j4mkqPuvaLI8mp1DroR3P6ad7cyYd4c1qeJ3RV7ULlk=
github.com/kdomanski/iso9660 v0.4.0 h1:BPKKdcINz3m0MdjIMwS0wx1nofsOjxOq8TOr45WGHFg=
github.com/kdomanski/iso9660 v0.4.0/go.mod h1:OxUSupHsO9ceI8lBLPJKWBTphLemjrCQY8LPXM7qSzU=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
@@ -777,6 +779,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/mogaika/udf v0.0.0-20171019171931-167f0ab01c73 h1:HpHNB68mF30LkUorpr7B22Xy0XwParP/n+AL0z4VlFc=
github.com/mogaika/udf v0.0.0-20171019171931-167f0ab01c73/go.mod h1:OVErfG87tRGCHZpQGO8pL4vDZuY0qr4blPOTR0go6e8=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
@@ -1165,8 +1169,8 @@ golang.org/x/crypto v0.0.0-20220516162934-403b01795ae8/go.mod h1:IxCIyHEi3zRg3s0
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -1268,8 +1272,8 @@ golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -1292,8 +1296,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -1381,16 +1385,16 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -1405,8 +1409,8 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -1676,8 +1680,8 @@ yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900 h1:Hu/4ERvoWaN6aiFs4h4/yvVB
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900/go.mod h1:0vLkNEhlmA64HViPBAnSTUMrx5QP1CLsxXmxDKQ80tc=
yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
yunion.io/x/pkg v1.10.4-0.20251114095758-2a2f105d9712 h1:LUYhaE2PbRrlO2dJTII3K5L62wYlwDet2drziGv6yLY=
yunion.io/x/pkg v1.10.4-0.20251114095758-2a2f105d9712/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
yunion.io/x/pkg v1.10.4-0.20260127060125-8939521ef75e h1:py5Kd6cgP6pxB9pSc4ry/gnmA6rJZwMVOnAjra4sb2g=
yunion.io/x/pkg v1.10.4-0.20260127060125-8939521ef75e/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1 h1:1KJ3YYinydPHpDEQRXdr/T8SYcKZ5Er+m489H+PnaQ4=
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
yunion.io/x/sqlchemy v1.1.3-0.20251231025938-b0a38f6e9fab h1:5m/bSzW3uTAk83rp9eethbYsxJFYInFVeU1RDkteW4E=
+2 -1
View File
@@ -19,13 +19,14 @@ import (
"net/http"
"strings"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/apigateway/clientman"
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/util/netutils2"
"yunion.io/x/pkg/errors"
)
const (
+1
View File
@@ -60,6 +60,7 @@ const (
IMAGE_OS_DISTRO = "os_distribution"
IMAGE_OS_TYPE = "os_type"
IMAGE_OS_VERSION = "os_version"
IMAGE_OS_LANGUAGE = "os_language"
IMAGE_DISK_FORMAT = "disk_format"
IMAGE_UEFI_SUPPORT = "uefi_support"
IMAGE_BIOS_SUPPORT = "bios_support"
@@ -24,9 +24,9 @@ import (
"strings"
"time"
"github.com/coredns/coredns/plugin/pkg/log"
"yunion.io/x/cloudmux/pkg/cloudprovider"
"yunion.io/x/cloudmux/pkg/multicloud/objectstore"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/streamutils"
)
+54 -5
View File
@@ -63,6 +63,7 @@ import (
"yunion.io/x/onecloud/pkg/mcclient/modules/notify"
"yunion.io/x/onecloud/pkg/util/cephutils"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/isoutils"
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/procutils"
"yunion.io/x/onecloud/pkg/util/qemuimg"
@@ -1734,8 +1735,24 @@ func (m *SImageManager) PerformVmwareAccountAdded(ctx context.Context, userCred
func (image *SImage) doProbeImageInfo(ctx context.Context, userCred mcclient.TokenCredential) (bool, error) {
if image.IsIso() {
// no need to probe
return false, nil
imagePath := image.GetLocalLocation()
if len(imagePath) == 0 {
return false, errors.Wrapf(httperrors.ErrNotFound, "image file %s not found", image.Location)
}
fp, err := os.Open(imagePath)
if err != nil {
return false, errors.Wrap(err, "Open image file")
}
defer fp.Close()
isoInfo, err := isoutils.DetectOSFromISO(fp)
if err != nil {
return false, errors.Wrap(err, "DetectOSFromISO")
}
err = image.updateIsoInfo(ctx, userCred, isoInfo)
if err != nil {
return false, errors.Wrap(err, "updateIsoInfo")
}
return true, nil
}
if image.IsData.IsTrue() {
// no need to probe
@@ -1789,9 +1806,9 @@ func (image *SImage) updateImageInfo(
imageProperties := jsonutils.Marshal(imageInfo.OsInfo).(*jsonutils.JSONDict)
imageProperties.Set(api.IMAGE_OS_ARCH, jsonutils.NewString(imageInfo.OsInfo.Arch))
imageProperties.Set("os_version", jsonutils.NewString(imageInfo.OsInfo.Version))
imageProperties.Set("os_distribution", jsonutils.NewString(imageInfo.OsInfo.Distro))
imageProperties.Set("os_language", jsonutils.NewString(imageInfo.OsInfo.Language))
imageProperties.Set(api.IMAGE_OS_VERSION, jsonutils.NewString(imageInfo.OsInfo.Version))
imageProperties.Set(api.IMAGE_OS_DISTRO, jsonutils.NewString(imageInfo.OsInfo.Distro))
imageProperties.Set(api.IMAGE_OS_LANGUAGE, jsonutils.NewString(imageInfo.OsInfo.Language))
imageProperties.Set(api.IMAGE_OS_TYPE, jsonutils.NewString(imageInfo.OsType))
imageProperties.Set(api.IMAGE_PARTITION_TYPE, jsonutils.NewString(imageInfo.PhysicalPartitionType))
@@ -1804,6 +1821,38 @@ func (image *SImage) updateImageInfo(
return ImagePropertyManager.SaveProperties(ctx, userCred, image.Id, imageProperties)
}
func (image *SImage) updateIsoInfo(ctx context.Context, userCred mcclient.TokenCredential, imageInfo *isoutils.ISOInfo) error {
if gotypes.IsNil(imageInfo) || len(imageInfo.Distro) == 0 {
return nil
}
change := false
imageProperties := jsonutils.Marshal(imageInfo).(*jsonutils.JSONDict)
if len(imageInfo.Arch) > 0 {
imageProperties.Set(api.IMAGE_OS_ARCH, jsonutils.NewString(imageInfo.Arch))
change = true
db.Update(image, func() error {
image.OsArch = imageInfo.Arch
return nil
})
}
if len(imageInfo.Version) > 0 {
imageProperties.Set(api.IMAGE_OS_VERSION, jsonutils.NewString(imageInfo.Version))
change = true
}
if len(imageInfo.Distro) > 0 {
imageProperties.Set(api.IMAGE_OS_DISTRO, jsonutils.NewString(imageInfo.Distro))
change = true
}
if len(imageInfo.Language) > 0 {
imageProperties.Set(api.IMAGE_OS_LANGUAGE, jsonutils.NewString(imageInfo.Language))
change = true
}
if change {
return ImagePropertyManager.SaveProperties(ctx, userCred, image.Id, imageProperties)
}
return nil
}
func (image *SImage) updateChecksum() error {
imagePath := image.GetLocalLocation()
if len(imagePath) == 0 {
+1
View File
@@ -12,6 +12,7 @@ import (
"time"
"github.com/mark3labs/mcp-go/mcp"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/llm"
+1
View File
@@ -11,6 +11,7 @@ import (
"time"
"github.com/mark3labs/mcp-go/mcp"
"yunion.io/x/pkg/errors"
api "yunion.io/x/onecloud/pkg/apis/llm"
+2 -3
View File
@@ -18,9 +18,11 @@ import (
commonapis "yunion.io/x/onecloud/pkg/apis"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
imageapi "yunion.io/x/onecloud/pkg/apis/image"
apis "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/llm/options"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
computemodules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
@@ -29,9 +31,6 @@ import (
"yunion.io/x/onecloud/pkg/util/logclient"
"yunion.io/x/onecloud/pkg/util/procutils"
"yunion.io/x/onecloud/pkg/util/stringutils2"
apis "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/llm/options"
)
var instantModelManager *SInstantModelManager
+1 -2
View File
@@ -7,10 +7,9 @@ import (
"yunion.io/x/pkg/errors"
"yunion.io/x/sqlchemy"
apis "yunion.io/x/onecloud/pkg/apis/llm"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
apis "yunion.io/x/onecloud/pkg/apis/llm"
)
type SMountedModelsResourceManager struct {
@@ -18,6 +18,7 @@ import (
"context"
"yunion.io/x/log"
api "yunion.io/x/onecloud/pkg/apis/identity"
"yunion.io/x/onecloud/pkg/cloudcommon/policy"
"yunion.io/x/onecloud/pkg/mcclient"
-1
View File
@@ -26,7 +26,6 @@ import (
api "yunion.io/x/onecloud/pkg/apis/identity"
"yunion.io/x/onecloud/pkg/mcclient/auth"
"yunion.io/x/onecloud/pkg/mcp-server/adapters"
"yunion.io/x/onecloud/pkg/mcp-server/options"
"yunion.io/x/onecloud/pkg/mcp-server/registry"
+1
View File
@@ -0,0 +1 @@
package isoutils // import "yunion.io/x/onecloud/pkg/util/isoutils"
+148
View File
@@ -0,0 +1,148 @@
// Copyright 2019 Yunion
//
// 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 isoutils
import (
"fmt"
"io"
"strings"
"github.com/kdomanski/iso9660"
"yunion.io/x/log"
)
// findISO9660File 在ISO9660中查找文件,返回 *iso9660.File
func (r *ISOFileReader) findISO9660File(path string) (*iso9660.File, error) {
if r.format != ISOFormatISO9660 || r.iso9660Img == nil {
return nil, fmt.Errorf("ISO9660格式未初始化")
}
rootDir, err := r.iso9660Img.RootDir()
if err != nil {
return nil, fmt.Errorf("获取根目录失败: %v", err)
}
// 规范化路径
path = strings.Trim(path, "/")
if path == "" {
return rootDir, nil
}
parts := strings.Split(path, "/")
currentDir := rootDir
for i, part := range parts {
if part == "" {
continue
}
// 获取当前目录的子项
children, err := currentDir.GetChildren()
if err != nil {
return nil, fmt.Errorf("读取目录失败: %v", err)
}
// 查找匹配的文件或目录(不区分大小写)
var found *iso9660.File
for _, child := range children {
if strings.EqualFold(child.Name(), part) {
found = child
break
}
}
if found == nil {
return nil, fmt.Errorf("文件或目录不存在: %s", part)
}
// 如果是最后一个部分,返回找到的文件
if i == len(parts)-1 {
return found, nil
}
// 检查是否为目录
if !found.IsDir() {
return nil, fmt.Errorf("路径中的%s不是目录", part)
}
currentDir = found
}
return currentDir, nil
}
// listISO9660Dir 列出ISO9660格式指定目录下的所有文件和子目录
func (r *ISOFileReader) listISO9660Dir(path string) ([]ISO9660FileInfo, error) {
if r.format != ISOFormatISO9660 {
return nil, fmt.Errorf("此方法仅支持ISO9660格式")
}
// 获取目录
dir, err := r.findISO9660File(path)
if err != nil {
return nil, err
}
if !dir.IsDir() {
return nil, fmt.Errorf("路径%s不是目录", path)
}
// 获取子项
children, err := dir.GetChildren()
if err != nil {
return nil, fmt.Errorf("读取目录失败: %v", err)
}
var files []ISO9660FileInfo
for _, child := range children {
fileInfo := ISO9660FileInfo{
Name: child.Name(),
IsDir: child.IsDir(),
Size: child.Size(),
Location: 0, // 使用库时不需要直接访问位置
}
files = append(files, fileInfo)
}
return files, nil
}
// readISO9660FileContent 读取ISO9660格式文件内容
func (r *ISOFileReader) readISO9660FileContent(path string) (string, error) {
file, err := r.findISO9660File(path)
if err != nil {
return "", fmt.Errorf("文件%s不存在: %v", path, err)
}
if file.IsDir() {
return "", fmt.Errorf("路径%s是目录,不是文件", path)
}
reader := file.Reader()
if reader == nil {
return "", fmt.Errorf("无法读取文件%s", path)
}
// 读取前10KB内容(足够识别特征)
buf := make([]byte, 10*1024)
n, err := reader.Read(buf)
if err != nil && err != io.EOF {
log.Errorf("读取ISO9660文件%s失败: %v", path, err)
return "", fmt.Errorf("读取文件%s失败: %v", path, err)
}
return strings.TrimSpace(string(buf[:n])), nil
}
+428
View File
@@ -0,0 +1,428 @@
// Copyright 2019 Yunion
//
// 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 isoutils
import (
"bytes"
"fmt"
"io"
"regexp"
"strings"
"sync"
"github.com/kdomanski/iso9660"
"github.com/mogaika/udf"
"gopkg.in/ini.v1"
"yunion.io/x/log"
"yunion.io/x/pkg/util/imagetools"
)
// ========== 2. 新增结构化返回结果(包含发行版、版本号、架构) ==========
type ISOInfo struct {
Distro string // 发行版(如 CentOS、Ubuntu Server
Version string // 版本号(如 7.9、22.04 LTS、2022
Arch string // 架构(如 x86_64、riscv64、arm64
Language string // 语言(如 en-US、zh-CN
}
// ISO格式类型
type ISOFormat string
const (
ISOFormatUnknown ISOFormat = "unknown"
ISOFormatUDF ISOFormat = "udf"
ISOFormatISO9660 ISOFormat = "iso9660"
)
// ========== 3. 优化ISOFileReader:增加缓存、日志、架构识别 ==========
type ISOFileReader struct {
format ISOFormat
img *udf.Udf
iso9660Img *iso9660.Image // ISO9660格式的读取器
reader io.Reader
cache sync.Map // 缓存已读取的文件内容:key=文件路径,value=文件内容
}
// isIsoFile 检测ISO格式(UDF或ISO9660
func isIsoFile(readerAt io.ReaderAt) (bool, error) {
// 读取0x8000地址的内容(ISO9660的Primary Volume Descriptor位置)
buf := make([]byte, 6)
n, err := readerAt.ReadAt(buf, 0x8000)
if err != nil && err != io.EOF {
return false, fmt.Errorf("读取ISO格式标识失败: %v", err)
}
if n < 6 {
return false, fmt.Errorf("读取数据不足")
}
// ISO9660格式:偏移0x8001-0x8005应该是"CD001"
if bytes.Equal(buf[1:6], []byte("CD001")) {
return true, nil
}
return false, nil
}
// NewISOFileReader 初始化ISO读取器(新增格式检测和日志配置)
func NewISOFileReader(reader io.Reader) (*ISOFileReader, error) {
readerAt, ok := reader.(io.ReaderAt)
if !ok {
return nil, fmt.Errorf("reader is not io.ReaderAt")
}
// 检测ISO格式
isIso, err := isIsoFile(readerAt)
if err != nil {
return nil, err
}
if !isIso {
return nil, fmt.Errorf("ISO镜像格式不正确")
}
ret := &ISOFileReader{
format: ISOFormatISO9660,
reader: reader,
cache: sync.Map{},
}
if isUdfFile(readerAt) {
ret.format = ISOFormatUDF
ret.img = udf.NewUdfFromReader(readerAt)
return ret, nil
}
isoImg, err := iso9660.OpenImage(readerAt)
if err != nil {
return nil, fmt.Errorf("打开ISO9660镜像失败: %v", err)
}
ret.iso9660Img = isoImg
return ret, nil
}
// ISO9660FileInfo ISO9660文件信息
type ISO9660FileInfo struct {
Name string // 文件名
IsDir bool // 是否为目录
Size int64 // 文件大小(字节)
Location int64 // 文件在ISO中的位置(字节偏移,使用库时可能为0)
}
func (r *ISOFileReader) list(path string) ([]ISO9660FileInfo, error) {
if r.format == ISOFormatISO9660 {
return r.listISO9660Dir(path)
}
return r.listUdfDir(path)
}
// FileExists 检查ISO内指定路径的文件是否存在(支持UDF和ISO9660)
func (r *ISOFileReader) FileExists(path string) bool {
if r.format == ISOFormatISO9660 {
_, err := r.findISO9660File(path)
return err == nil
}
_, err := r.GetFile(path)
return err == nil
}
// ReadFileContent 读取ISO内指定文件的内容(新增缓存、日志,支持UDF和ISO9660)
func (r *ISOFileReader) ReadFileContent(path string) (string, error) {
// 优先从缓存读取
if cacheVal, ok := r.cache.Load(path); ok {
log.Debugf("从缓存读取文件内容: %s", path)
return cacheVal.(string), nil
}
var content string
var err error
// 根据格式选择相应的读取方法
if r.format == ISOFormatISO9660 {
content, err = r.readISO9660FileContent(path)
} else if r.format == ISOFormatUDF {
content, err = r.readUdfFileContent(path)
} else {
return "", fmt.Errorf("未知的ISO格式: %s", r.format)
}
if err != nil {
return "", err
}
// 写入缓存
r.cache.Store(path, content)
log.Debugf("读取文件%s内容(长度: %d)并缓存", path, len(content))
return content, nil
}
// ========== 6. 核心识别函数:整合版本号、架构、日志、缓存 ==========
func DetectOSFromISO(r io.Reader) (*ISOInfo, error) {
defer func() {
if r := recover(); r != nil {
log.Errorf("DetectOSFromISO panic error: %v", r)
}
}()
result := &ISOInfo{}
// 初始化读取器
reader, err := NewISOFileReader(r)
if err != nil {
return nil, err
}
// ========== 识别Windows系列 ==========
if reader.FileExists("sources/install.wim") {
return DetectWindowsEdition(reader)
}
files, err := reader.list("/")
if err != nil {
return nil, err
}
for _, file := range files {
fileName := file.Name
if fileName == ".treeinfo" {
content, _ := reader.ReadFileContent(fileName)
result = getOsInfoByIniFile(content)
break
}
}
if reader.FileExists(".disk/info") {
content, _ := reader.ReadFileContent(".disk/info")
info := imagetools.NormalizeImageInfo(content, "", "", "", "")
result = &ISOInfo{
Distro: info.OsDistro,
Version: info.OsVersion,
Arch: info.OsArch,
Language: info.OsLang,
}
}
if len(result.Distro) == 0 || result.Distro == imagetools.OS_DIST_OTHER_LINUX {
realeaseFile := ""
if reader.FileExists("dists") {
files, err := reader.list("dists")
if err != nil {
return nil, err
}
for _, file := range files {
log.Debugf("file: %s", file.Name)
if !file.IsDir {
continue
}
subFiles, err := reader.list(fmt.Sprintf("dists/%s", file.Name))
if err != nil {
return nil, err
}
for _, subFile := range subFiles {
if subFile.Name == "Release" {
realeaseFile = fmt.Sprintf("dists/%s/%s", file.Name, subFile.Name)
break
}
}
if realeaseFile != "" {
break
}
}
}
if realeaseFile != "" {
content, _ := reader.ReadFileContent(realeaseFile)
result = getOsInfoByReleaseFile(content)
} else if reader.FileExists("boot/grub2/grub.cfg") {
content, _ := reader.ReadFileContent("boot/grub2/grub.cfg")
result = getOsInfoByGrub(content)
} else if reader.FileExists("EFI/BOOT/grub.cfg") {
content, _ := reader.ReadFileContent("EFI/BOOT/grub.cfg")
result = getOsInfoByGrub(content)
} else if reader.FileExists("isolinux/isolinux.cfg") {
content, _ := reader.ReadFileContent("isolinux/isolinux.cfg")
result = getOsInfoByIsoLinux(content)
}
}
return result, nil
}
func getOsInfoByReleaseFile(content string) *ISOInfo {
result := &ISOInfo{}
for _, line := range strings.Split(content, "\n") {
if strings.HasPrefix(line, "Origin:") {
result.Distro = strings.TrimSpace(strings.TrimPrefix(line, "Origin:"))
}
if strings.HasPrefix(line, "Label:") && len(result.Distro) == 0 {
result.Distro = strings.TrimSpace(strings.TrimPrefix(line, "Label:"))
}
if strings.HasPrefix(line, "Version:") {
result.Version = strings.TrimSpace(strings.TrimPrefix(line, "Version:"))
}
if strings.HasPrefix(line, "Architectures:") {
result.Arch = strings.TrimSpace(strings.TrimPrefix(line, "Architectures:"))
result.Arch = detectArchitecture(strings.ToLower(result.Arch), result.Arch)
}
}
return result
}
func getOsInfoByIniFile(content string) *ISOInfo {
cfg, err := ini.Load([]byte(content))
if err != nil {
// 兼容手动解析(应对部分非标准 INI 格式的 .treeinfo
return parseTreeInfoFallback(content)
}
release := cfg.Section("release")
ret := &ISOInfo{}
ret.Distro = release.Key("name").String()
ret.Version = release.Key("version").String()
general := cfg.Section("general")
ret.Arch = general.Key("arch").String()
if len(ret.Version) == 0 {
ret.Version = general.Key("version").String()
}
return ret
}
func parseTreeInfoFallback(content string) *ISOInfo {
result := &ISOInfo{}
info := strings.Split(content, "\n")
for _, line := range info {
if strings.HasPrefix(line, "arch =") {
result.Arch = strings.TrimPrefix(line, "arch = ")
}
if strings.HasPrefix(line, "version =") {
result.Version = strings.TrimPrefix(line, "version = ")
}
if strings.HasPrefix(line, "name =") {
result.Distro = strings.TrimPrefix(line, "name = ")
}
}
return result
}
func getOsInfoByIsoLinux(content string) *ISOInfo {
result := &ISOInfo{}
lowerContent := strings.ToLower(content)
// 5.1 识别发行版(关键词匹配)
result.Distro = detectDistro(lowerContent)
// 5.2 识别版本号(正则提取)
result.Version = detectVersion(content)
// 5.3 识别 CPU 架构(关键词+正则)
result.Arch = detectArchitecture(lowerContent, content)
return result
}
func getOsInfoByGrub(content string) *ISOInfo {
result := &ISOInfo{}
lowerContent := strings.ToLower(content)
result.Distro = detectDistro(lowerContent)
result.Version = detectGrubVersion(content)
result.Arch = detectArchitecture(lowerContent, content)
return result
}
func detectDistro(lowerContent string) string {
info := imagetools.NormalizeImageInfo(lowerContent, "", "", "", "")
return info.OsDistro
}
// detectVersion 从配置内容中提取版本号
func detectVersion(content string) string {
// 匹配版本号的正则(支持 x x.y、x.y.z、x.y-LTS 等格式)
versionRegex := regexp.MustCompile(`(\d+(\.\d+(\.\d+)?)?(-[A-Za-z0-9]+)?)`)
// 优先从启动标题(label/menu label)中提取
labelLines := regexp.MustCompile(`(?i)menu label .+Install.+`).FindAllStringSubmatch(content, -1)
for _, line := range labelLines {
log.Debugf("line: %s", line)
if len(line) >= 1 {
version := versionRegex.FindString(line[0])
if version != "" {
return version
}
}
}
// 从整个内容中提取第一个匹配的版本号
return versionRegex.FindString(content)
}
// detectArchitecture 识别 CPU 架构
func detectArchitecture(lowerContent, rawContent string) string {
// 架构关键词映射
archKeywords := map[string][]string{
"x86_64": {"x86_64", "amd64"},
"aarch64": {"aarch64", "arm64"},
"i386": {"i386", "i686"},
"armhfp": {"armhfp", "armv7"},
"ppc64le": {"ppc64le"},
"s390x": {"s390x"},
}
for arch, keywords := range archKeywords {
for _, kw := range keywords {
if strings.Contains(lowerContent, kw) {
return arch
}
}
}
// 从内核文件名(vmlinuz/initrd)中提取
kernelRegex := regexp.MustCompile(`vmlinuz-([a-zA-Z0-9_]+)`)
match := kernelRegex.FindStringSubmatch(rawContent)
if len(match) >= 2 {
return match[1]
}
return ""
}
// detectGrubVersion 从 grub.cfg 提取版本号
func detectGrubVersion(content string) string {
// 匹配版本号的正则(支持 x x.y、x.y.z、x.y-LTS、x.y.z-xxx 等格式)
versionRegex := regexp.MustCompile(`(\d+(\.\d+(\.\d+)?)?(-[A-Za-z0-9]+)?)`)
// 优先从 GRUB 菜单标题(menuentry)中提取(准确性更高)
menuEntryRegex := regexp.MustCompile(`(?i)menuentry\s+["'](.+?)["']`)
menuEntries := menuEntryRegex.FindAllStringSubmatch(content, -1)
for _, entry := range menuEntries {
log.Debugf("entry: %s", entry)
if len(entry) >= 1 {
version := versionRegex.FindString(entry[0])
if version != "" {
return version
}
}
}
// 从内核文件名/参数中提取
kernelLines := regexp.MustCompile(`linux\s+.+`).FindAllString(content, -1)
for _, line := range kernelLines {
version := versionRegex.FindString(line)
if version != "" {
return version
}
}
// 最后从整个内容中提取第一个匹配的版本号
return versionRegex.FindString(content)
}
+187
View File
@@ -0,0 +1,187 @@
// Copyright 2019 Yunion
//
// 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 isoutils
import (
"fmt"
"io"
"strings"
"github.com/mogaika/udf"
"yunion.io/x/log"
)
// isUdfFile 检测是否为UDF格式
func isUdfFile(readerAt io.ReaderAt) bool {
defer func() {
recover()
}()
img := udf.NewUdfFromReader(readerAt)
files := img.ReadDir(nil)
if len(files) == 0 {
return false
}
return true
}
// findUdfDir 在UDF中查找目录,返回目录的 FileEntry
func (r *ISOFileReader) findUdfDir(path string) ([]udf.File, error) {
if r.format != ISOFormatUDF || r.img == nil {
return nil, fmt.Errorf("UDF格式未初始化")
}
// 规范化路径
path = strings.Trim(path, "/")
if path == "" {
// 根目录
return r.img.ReadDir(nil), nil
}
// 查找目录路径
parts := strings.Split(path, "/")
var entry *udf.FileEntry = nil
currentDirEntry := r.img.ReadDir(entry) // 从根目录开始
for i, part := range parts {
if part == "" {
continue
}
var found *udf.File
// 在当前目录中查找
for idx := range currentDirEntry {
child := &currentDirEntry[idx]
childName := child.Name()
// 匹配文件名(不区分大小写)
if strings.EqualFold(childName, part) {
found = child
break
}
}
if found == nil {
return nil, fmt.Errorf("目录不存在: %s", part)
}
// 如果是最后一个部分,返回该目录的内容
if i == len(parts)-1 {
return found.ReadDir(), nil
}
// 继续查找下一级目录
currentDirEntry = found.ReadDir()
}
return nil, fmt.Errorf("未找到目录: %s", path)
}
// listUdfDir 列出UDF格式指定目录下的所有文件和子目录
func (r *ISOFileReader) listUdfDir(path string) ([]ISO9660FileInfo, error) {
if r.format != ISOFormatUDF {
return nil, fmt.Errorf("此方法仅支持UDF格式")
}
// 获取目录内容
children, err := r.findUdfDir(path)
if err != nil {
return nil, err
}
var files []ISO9660FileInfo
for _, child := range children {
fileInfo := ISO9660FileInfo{
Name: child.Name(),
IsDir: child.IsDir(),
Size: child.Size(),
Location: 0, // 使用库时不需要直接访问位置
}
files = append(files, fileInfo)
}
return files, nil
}
// GetFile 在UDF中查找指定路径的文件
func (r *ISOFileReader) GetFile(path string) (*udf.File, error) {
if r.format != ISOFormatUDF {
return nil, fmt.Errorf("此方法仅支持UDF格式")
}
// UDF格式的原有逻辑
parts := strings.Split(strings.Trim(path, "/"), "/")
var entry *udf.FileEntry = nil
currentDirEntry := r.img.ReadDir(entry) // 从根目录开始
for i, part := range parts {
if part == "" {
continue
}
var found *udf.File
// 在当前目录中查找
for idx := range currentDirEntry {
child := &currentDirEntry[idx]
childName := child.Name()
// 匹配文件名(UDF 文件名通常不包含版本号后缀)
if childName == part {
found = child
break
}
}
if found == nil {
return nil, fmt.Errorf("文件或目录不存在: %s", part)
}
// 如果是最后一个部分,返回文件
if i == len(parts)-1 {
return found, nil
}
currentDirEntry = found.ReadDir()
}
return nil, fmt.Errorf("未找到文件: %s", path)
}
// readUdfFileContent 读取UDF格式文件内容
func (r *ISOFileReader) readUdfFileContent(path string) (string, error) {
file, err := r.GetFile(path)
if err != nil {
return "", fmt.Errorf("文件%s不存在: %v", path, err)
}
if file.IsDir() {
return "", fmt.Errorf("路径%s是目录,不是文件", path)
}
reader := file.NewReader()
if reader == nil {
return "", fmt.Errorf("无法读取文件%s", path)
}
// 读取前10KB内容(足够识别特征)
buf := make([]byte, 10*1024)
n, err := reader.Read(buf)
if err != nil && err != io.EOF {
log.Errorf("读取UDF文件%s失败: %v", path, err)
return "", fmt.Errorf("读取文件%s失败: %v", path, err)
}
return strings.TrimSpace(string(buf[:n])), nil
}
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2019 Yunion
//
// 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.
//go:build !linux
// +build !linux
package isoutils
import (
"yunion.io/x/pkg/errors"
)
func DetectWindowsEdition(r *ISOFileReader) (*ISOInfo, error) {
return nil, errors.Wrap(errors.ErrNotSupported, "not supported")
}
+98
View File
@@ -0,0 +1,98 @@
// Copyright 2019 Yunion
//
// 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.
//go:build linux
// +build linux
package isoutils
import (
"fmt"
"github.com/Microsoft/go-winio/wim"
"yunion.io/x/log"
"yunion.io/x/pkg/util/imagetools"
)
// ========== 7. 保留Windows版本识别函数(适配新结构) ==========
func DetectWindowsEdition(r *ISOFileReader) (*ISOInfo, error) {
wimFile, err := r.GetFile("sources/install.wim")
if err != nil {
return nil, err
}
wim, err := wim.NewReader(wimFile.NewReader())
if err != nil {
return nil, err
}
result := &ISOInfo{}
for _, image := range wim.Image {
version := fmt.Sprintf("%d.%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor, image.Windows.Version.Build)
if image.Windows != nil {
if image.Windows.Arch == 9 {
result.Arch = "x86_64"
} else if image.Windows.Arch == 12 {
result.Arch = "arm64"
} else if image.Windows.Arch == 0 {
result.Arch = "x86"
}
result.Distro = imagetools.OS_DIST_WINDOWS
result.Language = image.Windows.DefaultLanguage
switch fmt.Sprintf("%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor) {
case "6.0":
result.Version = "Windows Vista"
case "6.1":
result.Version = "Windows 7"
case "6.2":
result.Version = "Windows 8"
case "6.3":
result.Version = "Windows 8.1"
case "10.0":
if image.Windows.Version.Build >= 27500 {
result.Version = "Windows 12"
} else if image.Windows.Version.Build >= 22000 {
result.Version = "Windows 11"
} else {
result.Version = "Windows 10"
}
}
if image.Windows.ProductType == "ServerNT" {
result.Distro = imagetools.OS_DIST_WINDOWS_SERVER
switch fmt.Sprintf("%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor) {
case "6.0":
result.Version = "Windows Server 2008"
case "6.1":
result.Version = "Windows Server 2008 R2"
case "6.2":
result.Version = "Windows Server 2012"
case "6.3":
result.Version = "Windows Server 2012 R2"
case "10.0":
if image.Windows.Version.Build >= 26040 {
result.Version = "Windows Server 2025"
} else if image.Windows.Version.Build >= 20348 {
result.Version = "Windows Server 2022"
} else if image.Windows.Version.Build >= 17763 {
result.Version = "Windows Server 2019"
} else if image.Windows.Version.Build >= 14393 {
result.Version = "Windows Server 2016"
}
}
}
log.Debugf("识别到 %s 版本: %s -> %s", result.Distro, version, result.Version)
break
}
}
return result, nil
}
+139
View File
@@ -0,0 +1,139 @@
//go:build windows || linux
// +build windows linux
package wim
import (
"encoding/binary"
"io"
"github.com/Microsoft/go-winio/wim/lzx"
)
const chunkSize = 32768 // Compressed resource chunk size
type compressedReader struct {
r *io.SectionReader
d io.ReadCloser
chunks []int64
curChunk int
originalSize int64
}
func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) (*compressedReader, error) {
nchunks := (originalSize + chunkSize - 1) / chunkSize
var base int64
chunks := make([]int64, nchunks)
if originalSize <= 0xffffffff {
// 32-bit chunk offsets
base = (nchunks - 1) * 4
chunks32 := make([]uint32, nchunks-1)
err := binary.Read(r, binary.LittleEndian, chunks32)
if err != nil {
return nil, err
}
for i, n := range chunks32 {
chunks[i+1] = int64(n)
}
} else {
// 64-bit chunk offsets
base = (nchunks - 1) * 8
err := binary.Read(r, binary.LittleEndian, chunks[1:])
if err != nil {
return nil, err
}
}
for i, c := range chunks {
chunks[i] = c + base
}
cr := &compressedReader{
r: r,
chunks: chunks,
originalSize: originalSize,
}
err := cr.reset(int(offset / chunkSize))
if err != nil {
return nil, err
}
suboff := offset % chunkSize
if suboff != 0 {
_, err := io.CopyN(io.Discard, cr.d, suboff)
if err != nil {
return nil, err
}
}
return cr, nil
}
func (r *compressedReader) chunkOffset(n int) int64 {
if n == len(r.chunks) {
return r.r.Size()
}
return r.chunks[n]
}
func (r *compressedReader) chunkSize(n int) int {
return int(r.chunkOffset(n+1) - r.chunkOffset(n))
}
func (r *compressedReader) uncompressedSize(n int) int {
if n < len(r.chunks)-1 {
return chunkSize
}
size := int(r.originalSize % chunkSize)
if size == 0 {
size = chunkSize
}
return size
}
func (r *compressedReader) reset(n int) error {
if n >= len(r.chunks) {
return io.EOF
}
if r.d != nil {
r.d.Close()
}
r.curChunk = n
size := r.chunkSize(n)
uncompressedSize := r.uncompressedSize(n)
section := io.NewSectionReader(r.r, r.chunkOffset(n), int64(size))
if size != uncompressedSize {
d, err := lzx.NewReader(section, uncompressedSize)
if err != nil {
return err
}
r.d = d
} else {
r.d = io.NopCloser(section)
}
return nil
}
func (r *compressedReader) Read(b []byte) (int, error) {
for {
n, err := r.d.Read(b)
if err != io.EOF { //nolint:errorlint
return n, err
}
err = r.reset(r.curChunk + 1)
if err != nil {
return n, err
}
}
}
func (r *compressedReader) Close() error {
var err error
if r.d != nil {
err = r.d.Close()
r.d = nil
}
return err
}
+598
View File
@@ -0,0 +1,598 @@
// Package lzx implements a decompressor for the the WIM variant of the
// LZX compression algorithm.
//
// The LZX algorithm is an earlier variant of LZX DELTA, which is documented
// at https://msdn.microsoft.com/en-us/library/cc483133(v=exchg.80).aspx.
package lzx
import (
"bytes"
"encoding/binary"
"errors"
"io"
)
const (
maincodecount = 496
maincodesplit = 256
lencodecount = 249
lenshift = 9
codemask = 0x1ff
tablebits = 9
tablesize = 1 << tablebits
maxBlockSize = 32768
windowSize = 32768
maxTreePathLen = 16
e8filesize = 12000000
maxe8offset = 0x3fffffff
verbatimBlock = 1
alignedOffsetBlock = 2
uncompressedBlock = 3
)
var footerBits = [...]byte{
0, 0, 0, 0, 1, 1, 2, 2,
3, 3, 4, 4, 5, 5, 6, 6,
7, 7, 8, 8, 9, 9, 10, 10,
11, 11, 12, 12, 13, 13, 14,
}
var basePosition = [...]uint16{
0, 1, 2, 3, 4, 6, 8, 12,
16, 24, 32, 48, 64, 96, 128, 192,
256, 384, 512, 768, 1024, 1536, 2048, 3072,
4096, 6144, 8192, 12288, 16384, 24576, 32768,
}
var (
errCorrupt = errors.New("LZX data corrupt")
)
// Reader is an interface used by the decompressor to access
// the input stream. If the provided io.Reader does not implement
// Reader, then a bufio.Reader is used.
type Reader interface {
io.Reader
io.ByteReader
}
type decompressor struct {
r io.Reader
err error
unaligned bool
nbits byte
c uint32
lru [3]uint16
uncompressed int
windowReader *bytes.Reader
mainlens [maincodecount]byte
lenlens [lencodecount]byte
window [windowSize]byte
b []byte
bv int
bo int
}
//go:noinline
func (f *decompressor) fail(err error) {
if f.err == nil {
f.err = err
}
f.bo = 0
f.bv = 0
}
func (f *decompressor) ensureAtLeast(n int) error {
if f.bv-f.bo >= n {
return nil
}
if f.err != nil {
return f.err
}
if f.bv != f.bo {
copy(f.b[:f.bv-f.bo], f.b[f.bo:f.bv])
}
n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n)
if err != nil {
if err == io.EOF { //nolint:errorlint
err = io.ErrUnexpectedEOF
} else {
f.fail(err)
}
return err
}
f.bv = f.bv - f.bo + n
f.bo = 0
return nil
}
// feed retrieves another 16-bit word from the stream and consumes
// it into f.c. It returns false if there are no more bytes available.
// Otherwise, on error, it sets f.err.
func (f *decompressor) feed() bool {
err := f.ensureAtLeast(2)
if err == io.ErrUnexpectedEOF { //nolint:errorlint // returns io.ErrUnexpectedEOF by contract
return false
}
f.c |= (uint32(f.b[f.bo+1])<<8 | uint32(f.b[f.bo])) << (16 - f.nbits)
f.nbits += 16
f.bo += 2
return true
}
// getBits retrieves the next n bits from the byte stream. n
// must be <= 16. It sets f.err on error.
func (f *decompressor) getBits(n byte) uint16 {
if f.nbits < n {
if !f.feed() {
f.fail(io.ErrUnexpectedEOF)
}
}
c := uint16(f.c >> (32 - n))
f.c <<= n
f.nbits -= n
return c
}
type huffman struct {
extra [][]uint16
maxbits byte
table [tablesize]uint16
}
// buildTable builds a huffman decoding table from a slice of code lengths,
// one per code, in order. Each code length must be <= maxTreePathLen.
// See https://en.wikipedia.org/wiki/Canonical_Huffman_code.
func buildTable(codelens []byte) *huffman {
// Determine the number of codes of each length, and the
// maximum length.
var count [maxTreePathLen + 1]uint
var max byte
for _, cl := range codelens {
count[cl]++
if max < cl {
max = cl
}
}
if max == 0 {
return &huffman{}
}
// Determine the first code of each length.
var first [maxTreePathLen + 1]uint
code := uint(0)
for i := byte(1); i <= max; i++ {
code <<= 1
first[i] = code
code += count[i]
}
if code != 1<<max {
return nil
}
// Build a table for code lookup. For code sizes < max,
// put all possible suffixes for the code into the table, too.
// For max > tablebits, split long codes into additional tables
// of suffixes of max-tablebits length.
h := &huffman{maxbits: max}
if max > tablebits {
core := first[tablebits+1] / 2 // Number of codes that fit without extra tables
nextra := 1<<tablebits - core // Number of extra entries
h.extra = make([][]uint16, nextra)
for code := core; code < 1<<tablebits; code++ {
h.table[code] = uint16(code - core)
h.extra[code-core] = make([]uint16, 1<<(max-tablebits))
}
}
for i, cl := range codelens {
if cl != 0 {
code := first[cl]
first[cl]++
v := uint16(cl)<<lenshift | uint16(i)
if cl <= tablebits {
extendedCode := code << (tablebits - cl)
for j := uint(0); j < 1<<(tablebits-cl); j++ {
h.table[extendedCode+j] = v
}
} else {
prefix := code >> (cl - tablebits)
suffix := code & (1<<(cl-tablebits) - 1)
extendedCode := suffix << (max - cl)
for j := uint(0); j < 1<<(max-cl); j++ {
h.extra[h.table[prefix]][extendedCode+j] = v
}
}
}
}
return h
}
// getCode retrieves the next code using the provided
// huffman tree. It sets f.err on error.
func (f *decompressor) getCode(h *huffman) uint16 {
if h.maxbits > 0 {
if f.nbits < maxTreePathLen {
f.feed()
}
// For codes with length < tablebits, it doesn't matter
// what the remainder of the bits used for table lookup
// are, since entries with all possible suffixes were
// added to the table.
c := h.table[f.c>>(32-tablebits)]
if !(c >= 1<<lenshift) {
// The code is not in c.
c = h.extra[c][f.c<<tablebits>>(32-(h.maxbits-tablebits))]
}
n := byte(c >> lenshift)
if f.nbits >= n {
// Only consume the length of the code, not the maximum
// code length.
f.c <<= n
f.nbits -= n
return c & codemask
}
f.fail(io.ErrUnexpectedEOF)
return 0
}
// This is an empty tree. It should not be used.
f.fail(errCorrupt)
return 0
}
// readTree updates the huffman tree path lengths in lens by
// reading and decoding lengths from the byte stream. lens
// should be prepopulated with the previous block's tree's path
// lengths. For the first block, lens should be zero.
func (f *decompressor) readTree(lens []byte) error {
// Get the pre-tree for the main tree.
var pretreeLen [20]byte
for i := range pretreeLen {
pretreeLen[i] = byte(f.getBits(4))
}
if f.err != nil {
return f.err
}
h := buildTable(pretreeLen[:])
// The lengths are encoded as a series of huffman codes
// encoded by the pre-tree.
for i := 0; i < len(lens); {
c := byte(f.getCode(h))
if f.err != nil {
return f.err
}
switch {
case c <= 16: // length is delta from previous length
lens[i] = (lens[i] + 17 - c) % 17
i++
case c == 17: // next n + 4 lengths are zero
zeroes := int(f.getBits(4)) + 4
if i+zeroes > len(lens) {
return errCorrupt
}
for j := 0; j < zeroes; j++ {
lens[i+j] = 0
}
i += zeroes
case c == 18: // next n + 20 lengths are zero
zeroes := int(f.getBits(5)) + 20
if i+zeroes > len(lens) {
return errCorrupt
}
for j := 0; j < zeroes; j++ {
lens[i+j] = 0
}
i += zeroes
case c == 19: // next n + 4 lengths all have the same value
same := int(f.getBits(1)) + 4
if i+same > len(lens) {
return errCorrupt
}
c = byte(f.getCode(h))
if c > 16 {
return errCorrupt
}
l := (lens[i] + 17 - c) % 17
for j := 0; j < same; j++ {
lens[i+j] = l
}
i += same
default:
return errCorrupt
}
}
if f.err != nil {
return f.err
}
return nil
}
func (f *decompressor) readBlockHeader() (byte, uint16, error) {
// If the previous block was an unaligned uncompressed block, restore
// 2-byte alignment.
if f.unaligned {
err := f.ensureAtLeast(1)
if err != nil {
return 0, 0, err
}
f.bo++
f.unaligned = false
}
blockType := f.getBits(3)
full := f.getBits(1)
var blockSize uint16
if full != 0 {
blockSize = maxBlockSize
} else {
blockSize = f.getBits(16)
if blockSize > maxBlockSize {
return 0, 0, errCorrupt
}
}
if f.err != nil {
return 0, 0, f.err
}
switch blockType {
case verbatimBlock, alignedOffsetBlock:
// The caller will read the huffman trees.
case uncompressedBlock:
if f.nbits > 16 {
panic("impossible: more than one 16-bit word remains")
}
// Drop the remaining bits in the current 16-bit word
// If there are no bits left, discard a full 16-bit word.
n := f.nbits
if n == 0 {
n = 16
}
f.getBits(n)
// Read the LRU values for the next block.
err := f.ensureAtLeast(12)
if err != nil {
return 0, 0, err
}
f.lru[0] = uint16(binary.LittleEndian.Uint32(f.b[f.bo : f.bo+4]))
f.lru[1] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+4 : f.bo+8]))
f.lru[2] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+8 : f.bo+12]))
f.bo += 12
default:
return 0, 0, errCorrupt
}
return byte(blockType), blockSize, nil
}
// readTrees reads the two or three huffman trees for the current block.
// readAligned specifies whether to read the aligned offset tree.
func (f *decompressor) readTrees(readAligned bool) (main *huffman, length *huffman, aligned *huffman, err error) {
// Aligned offset blocks start with a small aligned offset tree.
if readAligned {
var alignedLen [8]byte
for i := range alignedLen {
alignedLen[i] = byte(f.getBits(3))
}
aligned = buildTable(alignedLen[:])
if aligned == nil {
return main, length, aligned, errors.New("corrupt")
}
}
// The main tree is encoded in two parts.
err = f.readTree(f.mainlens[:maincodesplit])
if err != nil {
return main, length, aligned, err
}
err = f.readTree(f.mainlens[maincodesplit:])
if err != nil {
return main, length, aligned, err
}
main = buildTable(f.mainlens[:])
if main == nil {
return main, length, aligned, errors.New("corrupt")
}
// The length tree is encoding in a single part.
err = f.readTree(f.lenlens[:])
if err != nil {
return main, length, aligned, err
}
length = buildTable(f.lenlens[:])
if length == nil {
return main, length, aligned, errors.New("corrupt")
}
return main, length, aligned, f.err
}
// readCompressedBlock decodes a compressed block, writing into the window
// starting at start and ending at end, and using the provided huffman trees.
func (f *decompressor) readCompressedBlock(start, end uint16, hmain, hlength, haligned *huffman) (int, error) {
i := start
for i < end {
main := f.getCode(hmain)
if f.err != nil {
break
}
if main < 256 {
// Literal byte.
f.window[i] = byte(main)
i++
continue
}
// This is a match backward in the window. Determine
// the offset and dlength.
matchlen := (main - 256) % 8
slot := (main - 256) / 8
// The length is either the low bits of the code,
// or if this is 7, is encoded with the length tree.
if matchlen == 7 {
matchlen += f.getCode(hlength)
}
matchlen += 2
var matchoffset uint16
if slot < 3 { //nolint:nestif // todo: simplify nested complexity
// The offset is one of the LRU values.
matchoffset = f.lru[slot]
f.lru[slot] = f.lru[0]
f.lru[0] = matchoffset
} else {
// The offset is encoded as a combination of the
// slot and more bits from the bit stream.
offsetbits := footerBits[slot]
var verbatimbits, alignedbits uint16
if offsetbits > 0 {
if haligned != nil && offsetbits >= 3 {
// This is an aligned offset block. Combine
// the bits written verbatim with the aligned
// offset tree code.
verbatimbits = f.getBits(offsetbits-3) * 8
alignedbits = f.getCode(haligned)
} else {
// There are no aligned offset bits to read,
// only verbatim bits.
verbatimbits = f.getBits(offsetbits)
alignedbits = 0
}
}
matchoffset = basePosition[slot] + verbatimbits + alignedbits - 2
// Update the LRU cache.
f.lru[2] = f.lru[1]
f.lru[1] = f.lru[0]
f.lru[0] = matchoffset
}
if !(matchoffset <= i && matchlen <= end-i) {
f.fail(errCorrupt)
break
}
copyend := i + matchlen
for ; i < copyend; i++ {
f.window[i] = f.window[i-matchoffset]
}
}
return int(i - start), f.err
}
// readBlock decodes the current block and returns the number of uncompressed bytes.
func (f *decompressor) readBlock(start uint16) (int, error) {
blockType, size, err := f.readBlockHeader()
if err != nil {
return 0, err
}
if blockType == uncompressedBlock {
if size%2 == 1 {
// Remember to realign the byte stream at the next block.
f.unaligned = true
}
copied := 0
if f.bo < f.bv {
copied = int(size)
s := int(start)
if copied > f.bv-f.bo {
copied = f.bv - f.bo
}
copy(f.window[s:s+copied], f.b[f.bo:f.bo+copied])
f.bo += copied
}
n, err := io.ReadFull(f.r, f.window[start+uint16(copied):start+size])
return copied + n, err
}
hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock)
if err != nil {
return 0, err
}
return f.readCompressedBlock(start, start+size, hmain, hlength, haligned)
}
// decodeE8 reverses the 0xe8 x86 instruction encoding that was performed
// to the uncompressed data before it was compressed.
func decodeE8(b []byte, off int64) {
if off > maxe8offset || len(b) < 10 {
return
}
for i := 0; i < len(b)-10; i++ {
if b[i] == 0xe8 {
currentPtr := int32(off) + int32(i)
abs := int32(binary.LittleEndian.Uint32(b[i+1 : i+5]))
if abs >= -currentPtr && abs < e8filesize {
var rel int32
if abs >= 0 {
rel = abs - currentPtr
} else {
rel = abs + e8filesize
}
binary.LittleEndian.PutUint32(b[i+1:i+5], uint32(rel))
}
i += 4
}
}
}
func (f *decompressor) Read(b []byte) (int, error) {
// Read and uncompress everything.
if f.windowReader == nil {
n := 0
for n < f.uncompressed {
k, err := f.readBlock(uint16(n))
if err != nil {
return 0, err
}
n += k
}
decodeE8(f.window[:f.uncompressed], 0)
f.windowReader = bytes.NewReader(f.window[:f.uncompressed])
}
// Just read directly from the window.
return f.windowReader.Read(b)
}
func (*decompressor) Close() error {
return nil
}
// NewReader returns a new io.ReadCloser that decompresses a
// WIM LZX stream until uncompressedSize bytes have been returned.
func NewReader(r io.Reader, uncompressedSize int) (io.ReadCloser, error) {
if uncompressedSize > windowSize {
return nil, errors.New("uncompressed size is limited to 32KB")
}
f := &decompressor{
lru: [3]uint16{1, 1, 1},
uncompressed: uncompressedSize,
b: make([]byte, 4096),
r: r,
}
return f, nil
}
+898
View File
@@ -0,0 +1,898 @@
//go:build windows || linux
// +build windows linux
// Package wim implements a WIM file parser.
//
// WIM files are used to distribute Windows file system and container images.
// They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx.
package wim
import (
"bytes"
"crypto/sha1" //nolint:gosec // not used for secure application
"encoding/binary"
"encoding/xml"
"errors"
"fmt"
"io"
"strconv"
"sync"
"time"
"unicode/utf16"
)
// File attribute constants from Windows.
//
//nolint:revive // var-naming: ALL_CAPS
const (
FILE_ATTRIBUTE_READONLY = 0x00000001
FILE_ATTRIBUTE_HIDDEN = 0x00000002
FILE_ATTRIBUTE_SYSTEM = 0x00000004
FILE_ATTRIBUTE_DIRECTORY = 0x00000010
FILE_ATTRIBUTE_ARCHIVE = 0x00000020
FILE_ATTRIBUTE_DEVICE = 0x00000040
FILE_ATTRIBUTE_NORMAL = 0x00000080
FILE_ATTRIBUTE_TEMPORARY = 0x00000100
FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
FILE_ATTRIBUTE_COMPRESSED = 0x00000800
FILE_ATTRIBUTE_OFFLINE = 0x00001000
FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
FILE_ATTRIBUTE_ENCRYPTED = 0x00004000
FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000
FILE_ATTRIBUTE_VIRTUAL = 0x00010000
FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000
FILE_ATTRIBUTE_EA = 0x00040000
)
// Windows processor architectures.
//
//nolint:revive // var-naming: ALL_CAPS
const (
PROCESSOR_ARCHITECTURE_INTEL = 0
PROCESSOR_ARCHITECTURE_MIPS = 1
PROCESSOR_ARCHITECTURE_ALPHA = 2
PROCESSOR_ARCHITECTURE_PPC = 3
PROCESSOR_ARCHITECTURE_SHX = 4
PROCESSOR_ARCHITECTURE_ARM = 5
PROCESSOR_ARCHITECTURE_IA64 = 6
PROCESSOR_ARCHITECTURE_ALPHA64 = 7
PROCESSOR_ARCHITECTURE_MSIL = 8
PROCESSOR_ARCHITECTURE_AMD64 = 9
PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10
PROCESSOR_ARCHITECTURE_NEUTRAL = 11
PROCESSOR_ARCHITECTURE_ARM64 = 12
)
var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0}
// todo: replace this with pkg/guid.GUID (and add tests to make sure nothing breaks)
type guid struct {
Data1 uint32
Data2 uint16
Data3 uint16
Data4 [8]byte
}
func (g guid) String() string {
return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
g.Data1,
g.Data2,
g.Data3,
g.Data4[0],
g.Data4[1],
g.Data4[2],
g.Data4[3],
g.Data4[4],
g.Data4[5],
g.Data4[6],
g.Data4[7])
}
type resourceDescriptor struct {
FlagsAndCompressedSize uint64
Offset int64
OriginalSize int64
}
type resFlag byte
//nolint:deadcode,varcheck // need unused variables for iota to work
const (
resFlagFree resFlag = 1 << iota
resFlagMetadata
resFlagCompressed
resFlagSpanned
)
const validate = false
const supportedResFlags = resFlagMetadata | resFlagCompressed
func (r *resourceDescriptor) Flags() resFlag {
return resFlag(r.FlagsAndCompressedSize >> 56)
}
func (r *resourceDescriptor) CompressedSize() int64 {
return int64(r.FlagsAndCompressedSize & 0xffffffffffffff)
}
func (r *resourceDescriptor) String() string {
s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset)
if r.Flags()&4 != 0 {
s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize)
}
return s
}
// SHA1Hash contains the SHA1 hash of a file or stream.
type SHA1Hash [20]byte
type streamDescriptor struct {
resourceDescriptor
PartNumber uint16
RefCount uint32
Hash SHA1Hash
}
type hdrFlag uint32
//nolint:deadcode,varcheck // need unused variables for iota to work
const (
hdrFlagReserved hdrFlag = 1 << iota
hdrFlagCompressed
hdrFlagReadOnly
hdrFlagSpanned
hdrFlagResourceOnly
hdrFlagMetadataOnly
hdrFlagWriteInProgress
hdrFlagRpFix
)
//nolint:deadcode,varcheck // need unused variables for iota to work
const (
hdrFlagCompressReserved hdrFlag = 1 << (iota + 16)
hdrFlagCompressXpress
hdrFlagCompressLzx
)
const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx
type wimHeader struct {
ImageTag [8]byte
Size uint32
Version uint32
Flags hdrFlag
CompressionSize uint32
WIMGuid guid
PartNumber uint16
TotalParts uint16
ImageCount uint32
OffsetTable resourceDescriptor
XMLData resourceDescriptor
BootMetadata resourceDescriptor
BootIndex uint32
Padding uint32
Integrity resourceDescriptor
Unused [60]byte
}
type securityblockDisk struct {
TotalLength uint32
NumEntries uint32
}
const securityblockDiskSize = 8
type direntry struct {
Attributes uint32
SecurityID uint32
SubdirOffset int64
Unused1, Unused2 int64
CreationTime Filetime
LastAccessTime Filetime
LastWriteTime Filetime
Hash SHA1Hash
Padding uint32
ReparseHardLink int64
StreamCount uint16
ShortNameLength uint16
FileNameLength uint16
}
var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix
type streamentry struct {
Unused int64
Hash SHA1Hash
NameLength int16
}
var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix
// Filetime represents a Windows time.
type Filetime struct {
LowDateTime uint32
HighDateTime uint32
}
// Time returns the time as time.Time.
func (ft *Filetime) Time() time.Time {
// 100-nanosecond intervals since January 1, 1601
nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
// change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
nsec -= 116444736000000000
// convert into nanoseconds
nsec *= 100
return time.Unix(0, nsec)
}
// UnmarshalXML unmarshalls the time from a WIM XML blob.
func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
type Time struct {
Low string `xml:"LOWPART"`
High string `xml:"HIGHPART"`
}
var t Time
err := d.DecodeElement(&t, &start)
if err != nil {
return err
}
low, err := strconv.ParseUint(t.Low, 0, 32)
if err != nil {
return err
}
high, err := strconv.ParseUint(t.High, 0, 32)
if err != nil {
return err
}
ft.LowDateTime = uint32(low)
ft.HighDateTime = uint32(high)
return nil
}
type info struct {
Image []ImageInfo `xml:"IMAGE"`
}
// ImageInfo contains information about the image.
type ImageInfo struct {
Name string `xml:"NAME"`
Index int `xml:"INDEX,attr"`
CreationTime Filetime `xml:"CREATIONTIME"`
ModTime Filetime `xml:"LASTMODIFICATIONTIME"`
Windows *WindowsInfo `xml:"WINDOWS"`
}
// WindowsInfo contains information about the Windows installation in the image.
type WindowsInfo struct {
Arch byte `xml:"ARCH"`
ProductName string `xml:"PRODUCTNAME"`
EditionID string `xml:"EDITIONID"`
InstallationType string `xml:"INSTALLATIONTYPE"`
ProductType string `xml:"PRODUCTTYPE"`
Languages []string `xml:"LANGUAGES>LANGUAGE"`
DefaultLanguage string `xml:"LANGUAGES>DEFAULT"`
Version Version `xml:"VERSION"`
SystemRoot string `xml:"SYSTEMROOT"`
}
// Version represents a Windows build version.
type Version struct {
Major int `xml:"MAJOR"`
Minor int `xml:"MINOR"`
Build int `xml:"BUILD"`
SPBuild int `xml:"SPBUILD"`
SPLevel int `xml:"SPLEVEL"`
}
// ParseError is returned when the WIM cannot be parsed.
type ParseError struct {
Oper string
Path string
Err error
}
func (e *ParseError) Error() string {
if e.Path == "" {
return "WIM parse error at " + e.Oper + ": " + e.Err.Error()
}
return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error())
}
func (e *ParseError) Unwrap() error { return e.Err }
// Reader provides functions to read a WIM file.
type Reader struct {
hdr wimHeader
r io.ReaderAt
fileData map[SHA1Hash]resourceDescriptor
XMLInfo string // The XML information about the WIM.
Image []*Image // The WIM's images.
}
// Image represents an image within a WIM file.
type Image struct {
wim *Reader
offset resourceDescriptor
sds [][]byte
rootOffset int64
r io.ReadCloser
curOffset int64
m sync.Mutex
ImageInfo
}
// StreamHeader contains alternate data stream metadata.
type StreamHeader struct {
Name string
Hash SHA1Hash
Size int64
}
// Stream represents an alternate data stream or reparse point data stream.
type Stream struct {
StreamHeader
wim *Reader
offset resourceDescriptor
}
// FileHeader contains file metadata.
type FileHeader struct {
Name string
ShortName string
Attributes uint32
SecurityDescriptor []byte
CreationTime Filetime
LastAccessTime Filetime
LastWriteTime Filetime
Hash SHA1Hash
Size int64
LinkID int64
ReparseTag uint32
ReparseReserved uint32
}
// File represents a file or directory in a WIM image.
type File struct {
FileHeader
Streams []*Stream
offset resourceDescriptor
img *Image
subdirOffset int64
}
// NewReader returns a Reader that can be used to read WIM file data.
func NewReader(f io.ReaderAt) (*Reader, error) {
r := &Reader{r: f}
section := io.NewSectionReader(f, 0, 0xffff)
err := binary.Read(section, binary.LittleEndian, &r.hdr)
if err != nil {
return nil, err
}
if r.hdr.ImageTag != wimImageTag {
return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")}
}
if r.hdr.Flags&^supportedHdrFlags != 0 {
return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags)
}
if r.hdr.CompressionSize != 0x8000 {
return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize)
}
if r.hdr.TotalParts != 1 {
return nil, errors.New("multi-part WIM not supported")
}
fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable)
if err != nil {
return nil, err
}
xmlinfo, err := r.readXML()
if err != nil {
return nil, err
}
var inf info
err = xml.Unmarshal([]byte(xmlinfo), &inf)
if err != nil {
return nil, &ParseError{Oper: "XML info", Err: err}
}
for i, img := range images {
for _, imgInfo := range inf.Image {
if imgInfo.Index == i+1 {
img.ImageInfo = imgInfo
break
}
}
}
r.fileData = fileData
r.Image = images
r.XMLInfo = xmlinfo
return r, nil
}
// Close releases resources associated with the Reader.
func (r *Reader) Close() error {
for _, img := range r.Image {
img.reset()
}
return nil
}
func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) {
return r.resourceReaderWithOffset(hdr, 0)
}
func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) {
var sr io.ReadCloser
section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize())
if hdr.Flags()&resFlagCompressed == 0 {
_, _ = section.Seek(offset, 0)
sr = io.NopCloser(section)
} else {
cr, err := newCompressedReader(section, hdr.OriginalSize, offset)
if err != nil {
return nil, err
}
sr = cr
}
return sr, nil
}
func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) {
rsrc, err := r.resourceReader(hdr)
if err != nil {
return nil, err
}
defer rsrc.Close()
return io.ReadAll(rsrc)
}
func (r *Reader) readXML() (string, error) {
if r.hdr.XMLData.CompressedSize() == 0 {
return "", nil
}
rsrc, err := r.resourceReader(&r.hdr.XMLData)
if err != nil {
return "", err
}
defer rsrc.Close()
xmlData := make([]uint16, r.hdr.XMLData.OriginalSize/2)
err = binary.Read(rsrc, binary.LittleEndian, xmlData)
if err != nil {
return "", &ParseError{Oper: "XML data", Err: err}
}
// The BOM will always indicate little-endian UTF-16.
if xmlData[0] != 0xfeff {
return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")}
}
return string(utf16.Decode(xmlData[1:])), nil
}
func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) {
fileData := make(map[SHA1Hash]resourceDescriptor)
var images []*Image
offsetTable, err := r.readResource(res)
if err != nil {
return nil, nil, &ParseError{Oper: "offset table", Err: err}
}
br := bytes.NewReader(offsetTable)
for i := 0; ; i++ {
var res streamDescriptor
err := binary.Read(br, binary.LittleEndian, &res)
if err == io.EOF { //nolint:errorlint
break
}
if err != nil {
return nil, nil, &ParseError{Oper: "offset table", Err: err}
}
if res.Flags()&^supportedResFlags != 0 {
return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")}
}
// Validation for ad-hoc testing
if validate {
sec, err := r.resourceReader(&res.resourceDescriptor)
if err != nil {
panic(fmt.Sprint(i, err))
}
hash := sha1.New() //nolint:gosec // not used for secure application
_, err = io.Copy(hash, sec)
sec.Close()
if err != nil {
panic(fmt.Sprint(i, err))
}
var cmphash SHA1Hash
copy(cmphash[:], hash.Sum(nil))
if cmphash != res.Hash {
panic(fmt.Sprint(i, "hash mismatch"))
}
}
if res.Flags()&resFlagMetadata != 0 {
image := &Image{
wim: r,
offset: res.resourceDescriptor,
}
images = append(images, image)
} else {
fileData[res.Hash] = res.resourceDescriptor
}
}
if len(images) != int(r.hdr.ImageCount) {
return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")}
}
return fileData, images, nil
}
func (*Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) {
var secBlock securityblockDisk
err = binary.Read(rsrc, binary.LittleEndian, &secBlock)
if err != nil {
return sds, 0, &ParseError{Oper: "security table", Err: err}
}
n += securityblockDiskSize
secSizes := make([]int64, secBlock.NumEntries)
err = binary.Read(rsrc, binary.LittleEndian, &secSizes)
if err != nil {
return sds, n, &ParseError{Oper: "security table sizes", Err: err}
}
n += int64(secBlock.NumEntries * 8)
sds = make([][]byte, secBlock.NumEntries)
for i, size := range secSizes {
sd := make([]byte, size&0xffffffff)
_, err = io.ReadFull(rsrc, sd)
if err != nil {
return sds, n, &ParseError{Oper: "security descriptor", Err: err}
}
n += int64(len(sd))
sds[i] = sd
}
secsize := int64((secBlock.TotalLength + 7) &^ 7)
if n > secsize {
return sds, n, &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")}
}
_, err = io.CopyN(io.Discard, rsrc, secsize-n)
if err != nil {
return sds, n, err
}
n = secsize
return sds, n, nil
}
// Open parses the image and returns the root directory.
func (img *Image) Open() (*File, error) {
if img.sds == nil {
rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset)
if err != nil {
return nil, err
}
sds, n, err := img.wim.readSecurityDescriptors(rsrc)
if err != nil {
rsrc.Close()
return nil, err
}
img.sds = sds
img.r = rsrc
img.rootOffset = n
img.curOffset = n
}
f, err := img.readdir(img.rootOffset)
if err != nil {
return nil, err
}
if len(f) != 1 {
return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")}
}
return f[0], err
}
func (img *Image) reset() {
if img.r != nil {
img.r.Close()
img.r = nil
}
img.curOffset = -1
}
func (img *Image) readdir(offset int64) ([]*File, error) {
img.m.Lock()
defer img.m.Unlock()
if offset < img.curOffset || offset > img.curOffset+chunkSize {
// Reset to seek backward or to seek forward very far.
img.reset()
}
if img.r == nil {
rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset)
if err != nil {
return nil, err
}
img.r = rsrc
img.curOffset = offset
}
if offset > img.curOffset {
_, err := io.CopyN(io.Discard, img.r, offset-img.curOffset)
if err != nil {
img.reset()
if err == io.EOF { //nolint:errorlint
err = io.ErrUnexpectedEOF
}
return nil, err
}
}
var entries []*File
for {
e, n, err := img.readNextEntry(img.r)
img.curOffset += n
if err == io.EOF { //nolint:errorlint
break
}
if err != nil {
img.reset()
return nil, err
}
entries = append(entries, e)
}
return entries, nil
}
func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) {
var length int64
err := binary.Read(r, binary.LittleEndian, &length)
if err != nil {
return nil, 0, &ParseError{Oper: "directory length check", Err: err}
}
if length == 0 {
return nil, 8, io.EOF
}
left := length
if left < direntrySize {
return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")}
}
var dentry direntry
err = binary.Read(r, binary.LittleEndian, &dentry)
if err != nil {
return nil, 0, &ParseError{Oper: "directory entry", Err: err}
}
left -= direntrySize
namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength)
if left < namesLen {
return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")}
}
names := make([]uint16, namesLen/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, 0, &ParseError{Oper: "file name", Err: err}
}
left -= namesLen
var name, shortName string
if dentry.FileNameLength > 0 {
name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
}
if dentry.ShortNameLength > 0 {
shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
}
var offset resourceDescriptor
zerohash := SHA1Hash{}
if dentry.Hash != zerohash {
var ok bool
offset, ok = img.wim.fileData[dentry.Hash]
if !ok {
return nil, 0, &ParseError{
Oper: "directory entry",
Path: name,
Err: fmt.Errorf("could not find file data matching hash %#v", dentry),
}
}
}
f := &File{
FileHeader: FileHeader{
Attributes: dentry.Attributes,
CreationTime: dentry.CreationTime,
LastAccessTime: dentry.LastAccessTime,
LastWriteTime: dentry.LastWriteTime,
Hash: dentry.Hash,
Size: offset.OriginalSize,
Name: name,
ShortName: shortName,
},
offset: offset,
img: img,
subdirOffset: dentry.SubdirOffset,
}
isDir := false
if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 {
f.LinkID = dentry.ReparseHardLink
if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
isDir = true
}
} else {
f.ReparseTag = uint32(dentry.ReparseHardLink)
f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32)
}
if isDir && f.subdirOffset == 0 {
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")}
} else if !isDir && f.subdirOffset != 0 {
return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")}
}
if dentry.SecurityID != 0xffffffff {
f.SecurityDescriptor = img.sds[dentry.SecurityID]
}
_, err = io.CopyN(io.Discard, r, left)
if err != nil {
if err == io.EOF { //nolint:errorlint
err = io.ErrUnexpectedEOF
}
return nil, 0, err
}
if dentry.StreamCount > 0 {
var streams []*Stream
for i := uint16(0); i < dentry.StreamCount; i++ {
s, n, err := img.readNextStream(r)
length += n
if err != nil {
return nil, 0, err
}
// The first unnamed stream should be treated as the file stream.
if i == 0 && s.Name == "" {
f.Hash = s.Hash
f.Size = s.Size
f.offset = s.offset
} else if s.Name != "" {
streams = append(streams, s)
}
}
f.Streams = streams
}
if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 {
return nil, 0, &ParseError{
Oper: "directory entry",
Path: name,
Err: errors.New("reparse point is missing reparse stream"),
}
}
return f, length, nil
}
func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) {
var length int64
err := binary.Read(r, binary.LittleEndian, &length)
if err != nil {
if err == io.EOF { //nolint:errorlint
err = io.ErrUnexpectedEOF
}
return nil, 0, &ParseError{Oper: "stream length check", Err: err}
}
left := length
if left < streamentrySize {
return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")}
}
var sentry streamentry
err = binary.Read(r, binary.LittleEndian, &sentry)
if err != nil {
return nil, 0, &ParseError{Oper: "stream entry", Err: err}
}
left -= streamentrySize
if left < int64(sentry.NameLength) {
return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")}
}
names := make([]uint16, sentry.NameLength/2)
err = binary.Read(r, binary.LittleEndian, names)
if err != nil {
return nil, 0, &ParseError{Oper: "file name", Err: err}
}
left -= int64(sentry.NameLength)
name := string(utf16.Decode(names))
var offset resourceDescriptor
if sentry.Hash != (SHA1Hash{}) {
var ok bool
offset, ok = img.wim.fileData[sentry.Hash]
if !ok {
return nil, 0, &ParseError{
Oper: "stream entry",
Path: name,
Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash),
}
}
}
s := &Stream{
StreamHeader: StreamHeader{
Hash: sentry.Hash,
Size: offset.OriginalSize,
Name: name,
},
wim: img.wim,
offset: offset,
}
_, err = io.CopyN(io.Discard, r, left)
if err != nil {
if err == io.EOF { //nolint:errorlint
err = io.ErrUnexpectedEOF
}
return nil, 0, err
}
return s, length, nil
}
// Open returns an io.ReadCloser that can be used to read the stream's contents.
func (s *Stream) Open() (io.ReadCloser, error) {
return s.wim.resourceReader(&s.offset)
}
// Open returns an io.ReadCloser that can be used to read the file's contents.
func (f *File) Open() (io.ReadCloser, error) {
return f.img.wim.resourceReader(&f.offset)
}
// Readdir reads the directory entries.
func (f *File) Readdir() ([]*File, error) {
if !f.IsDir() {
return nil, errors.New("not a directory")
}
return f.img.readdir(f.subdirOffset)
}
// IsDir returns whether the given file is a directory. It returns false when it
// is a directory reparse point.
func (f *FileHeader) IsDir() bool {
return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY
}
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) 2019-2020, Kamil Domański and contributors
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+161
View File
@@ -0,0 +1,161 @@
## iso9660
[![Go Reference](https://pkg.go.dev/badge/github.com/kdomanski/iso9660.svg)](https://pkg.go.dev/github.com/kdomanski/iso9660)
[![codecov](https://codecov.io/gh/kdomanski/iso9660/branch/master/graph/badge.svg?token=14MJSZYZ24)](https://codecov.io/gh/kdomanski/iso9660)
[![Go Report Card](https://goreportcard.com/badge/github.com/kdomanski/iso9660)](https://goreportcard.com/report/github.com/kdomanski/iso9660)
A package for reading and creating ISO9660
Joliet extension is **NOT** supported.
Experimental support for reading Rock Ridge extension is currently in the works.
If you are experiencing issues, please use the v0.3 release, which ignores Rock Ridge.
## References for the format:
- [ECMA-119 1st edition (December 1986)](https://www.ecma-international.org/wp-content/uploads/ECMA-119_1st_edition_december_1986.pdf) ([Web Archive link](http://web.archive.org/web/20210122025258/https://www.ecma-international.org/wp-content/uploads/ECMA-119_1st_edition_december_1986.pdf))
- [ECMA-119 2nd edition (December 1987)](https://www.ecma-international.org/wp-content/uploads/ECMA-119_2nd_edition_december_1987.pdf) ([Web Archive link](http://web.archive.org/web/20210418211711/https://www.ecma-international.org/wp-content/uploads/ECMA-119_2nd_edition_december_1987.pdf))
- [ECMA-119 3rd edition (December 2017)](https://www.ecma-international.org/wp-content/uploads/ECMA-119_3rd_edition_december_2017.pdf) ([Web Archive link](http://web.archive.org/web/20210527165925/https://www.ecma-international.org/wp-content/uploads/ECMA-119_3rd_edition_december_2017.pdf))
- [ECMA-119 4th edition (June 2019)](https://www.ecma-international.org/wp-content/uploads/ECMA-119_4th_edition_june_2019.pdf) ([Web Archive link](https://www.ecma-international.org/wp-content/uploads/ECMA-119_4th_edition_june_2019.pdf))
- [Rock Ridge Interchange Protocol](http://www.nextcomputers.org/NeXTfiles/Projects/CD-ROM/Rock_Ridge_Interchange_Protocol.pdf) ([Web Archive link](http://web.archive.org/web/20071017082049/http://www.nextcomputers.org/NeXTfiles/Projects/CD-ROM/Rock_Ridge_Interchange_Protocol.pdf))
- [System Use Sharing Protocol v1.12](http://aminet.net/package/docs/misc/RRIP)
## Examples
### Extracting an ISO
```go
package main
import (
"log"
"github.com/kdomanski/iso9660/util"
)
func main() {
f, err := os.Open("/home/user/myImage.iso")
if err != nil {
log.Fatalf("failed to open file: %s", err)
}
defer f.Close()
if err = util.ExtractImageToDirectory(f, "/home/user/target_dir"); err != nil {
log.Fatalf("failed to extract image: %s", err)
}
}
```
### Creating an ISO
```go
package main
import (
"log"
"os"
"github.com/kdomanski/iso9660"
)
func main() {
writer, err := iso9660.NewWriter()
if err != nil {
log.Fatalf("failed to create writer: %s", err)
}
defer writer.Cleanup()
f, err := os.Open("/home/user/myFile.txt")
if err != nil {
log.Fatalf("failed to open file: %s", err)
}
defer f.Close()
err = writer.AddFile(f, "folder/MYFILE.TXT")
if err != nil {
log.Fatalf("failed to add file: %s", err)
}
outputFile, err := os.OpenFile("/home/user/output.iso", os.O_WRONLY | os.O_TRUNC | os.O_CREATE, 0644)
if err != nil {
log.Fatalf("failed to create file: %s", err)
}
err = writer.WriteTo(outputFile, "testvol")
if err != nil {
log.Fatalf("failed to write ISO image: %s", err)
}
err = outputFile.Close()
if err != nil {
log.Fatalf("failed to close output file: %s", err)
}
}
```
### Recursively create an ISO image from the given directories
```go
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/kdomanski/iso9660"
)
func main() {
writer, err := iso9660.NewWriter()
if err != nil {
log.Fatalf("failed to create writer: %s", err)
}
defer writer.Cleanup()
isoFile, err := os.OpenFile("C:/output.iso", os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
if err != nil {
log.Fatalf("failed to create file: %s", err)
}
defer isoFile.Close()
prefix := "F:\\" // the prefix to remove in the output iso file
sourceFolders := []string{"F:\\test1", "F:\\test2"} // the given directories to create an ISO file from
for _, folderName := range sourceFolders {
folderPath := strings.Join([]string{prefix, folderName}, "/")
walk_err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
log.Fatalf("walk: %s", err)
return err
}
if info.IsDir() {
return nil
}
outputPath := strings.TrimPrefix(path, prefix) // remove the source drive name
fmt.Printf("Adding file: %s\n", outputPath)
fileToAdd, err := os.Open(path)
if err != nil {
log.Fatalf("failed to open file: %s", err)
}
defer fileToAdd.Close()
err = writer.AddFile(fileToAdd, outputPath)
if err != nil {
log.Fatalf("failed to add file: %s", err)
}
return nil
})
if walk_err != nil {
log.Fatalf("%s", walk_err)
}
}
err = writer.WriteTo(isoFile, "Test")
if err != nil {
log.Fatalf("failed to write ISO image: %s", err)
}
}
```
+292
View File
@@ -0,0 +1,292 @@
package iso9660
import (
"fmt"
"io"
"os"
"strings"
"time"
)
// Image is a wrapper around an image file that allows reading its ISO9660 data
type Image struct {
ra io.ReaderAt
volumeDescriptors []volumeDescriptor
}
// OpenImage returns an Image reader reating from a given file
func OpenImage(ra io.ReaderAt) (*Image, error) {
i := &Image{ra: ra}
if err := i.readVolumes(); err != nil {
return nil, err
}
return i, nil
}
func (i *Image) readVolumes() error {
buffer := make([]byte, sectorSize)
// skip the 16 sectors of system area
for sector := 16; ; sector++ {
if _, err := i.ra.ReadAt(buffer, int64(sector)*int64(sectorSize)); err != nil {
return err
}
var vd volumeDescriptor
if err := vd.UnmarshalBinary(buffer); err != nil {
return err
}
// NOTE: the instance of the root Directory Record that appears
// in the Primary Volume Descriptor cannot contain a System Use
// field. See the SUSP standard.
i.volumeDescriptors = append(i.volumeDescriptors, vd)
if vd.Header.Type == volumeTypeTerminator {
break
}
}
return nil
}
// RootDir returns the File structure corresponding to the root directory
// of the first primary volume
func (i *Image) RootDir() (*File, error) {
for _, vd := range i.volumeDescriptors {
if vd.Type() == volumeTypePrimary {
return &File{de: vd.Primary.RootDirectoryEntry, ra: i.ra, children: nil, isRootDir: true}, nil
}
}
return nil, os.ErrNotExist
}
// RootDir returns the label of the first Primary Volume
func (i *Image) Label() (string, error) {
for _, vd := range i.volumeDescriptors {
if vd.Type() == volumeTypePrimary {
return string(vd.Primary.VolumeIdentifier), nil
}
}
return "", os.ErrNotExist
}
// File is a os.FileInfo-compatible wrapper around an ISO9660 directory entry
type File struct {
ra io.ReaderAt
de *DirectoryEntry
children []*File
isRootDir bool
susp *SUSPMetadata
}
var _ os.FileInfo = &File{}
func (f *File) hasRockRidge() bool {
return f.susp != nil && f.susp.HasRockRidge
}
// IsDir returns true if the entry is a directory or false otherwise
func (f *File) IsDir() bool {
if f.hasRockRidge() {
if mode, err := f.de.SystemUseEntries.GetPosixAttr(); err == nil {
return mode&os.ModeDir != 0
}
}
return f.de.FileFlags&dirFlagDir != 0
}
// ModTime returns the entry's recording time
func (f *File) ModTime() time.Time {
return time.Time(f.de.RecordingDateTime)
}
// Mode returns file mode when available.
// Otherwise it returns os.FileMode flag set with the os.ModeDir flag enabled in case of directories.
func (f *File) Mode() os.FileMode {
if f.hasRockRidge() {
if mode, err := f.de.SystemUseEntries.GetPosixAttr(); err == nil {
return mode
}
}
var mode os.FileMode
if f.IsDir() {
mode |= os.ModeDir
}
return mode
}
// Name returns the base name of the given entry
func (f *File) Name() string {
if f.hasRockRidge() {
if name := f.de.SystemUseEntries.GetRockRidgeName(); name != "" {
return name
}
}
if f.IsDir() {
return f.de.Identifier
}
// drop the version part
// assume only one ';'
fileIdentifier := strings.Split(f.de.Identifier, ";")[0]
// split into filename and extension
// assume only only one '.'
splitFileIdentifier := strings.Split(fileIdentifier, ".")
// there's no dot in the name, thus no extension
if len(splitFileIdentifier) == 1 {
return splitFileIdentifier[0]
}
// extension is empty, return just the name without a dot
if len(splitFileIdentifier[1]) == 0 {
return splitFileIdentifier[0]
}
// return file with extension
return fileIdentifier
}
// Size returns the size in bytes of the extent occupied by the file or directory
func (f *File) Size() int64 {
return int64(f.de.ExtentLength)
}
// Sys returns nil
func (f *File) Sys() interface{} {
return nil
}
// GetAllChildren returns the children entries in case of a directory
// or an error in case of a file. It includes the "." and ".." entries.
func (f *File) GetAllChildren() ([]*File, error) {
if !f.IsDir() {
return nil, fmt.Errorf("%s is not a directory", f.Name())
}
if f.children != nil {
return f.children, nil
}
baseOffset := uint32(f.de.ExtentLocation) * sectorSize
buffer := make([]byte, sectorSize)
for bytesProcessed := uint32(0); bytesProcessed < uint32(f.de.ExtentLength); bytesProcessed += sectorSize {
if _, err := f.ra.ReadAt(buffer, int64(baseOffset+bytesProcessed)); err != nil {
return nil, nil
}
for i := uint32(0); i < sectorSize; {
entryLength := uint32(buffer[i])
if entryLength == 0 {
break
}
if i+entryLength > sectorSize {
return nil, fmt.Errorf("reading directory entries: DE outside of sector boundries")
}
newDE := &DirectoryEntry{}
if err := newDE.UnmarshalBinary(buffer[i : i+entryLength]); err != nil {
return nil, err
}
// Is this a root directory '.' record?
if f.isRootDir && newDE.Identifier == string([]byte{0}) {
newDE.SystemUseEntries, _ = splitSystemUseEntries(newDE.SystemUse, f.ra)
// get the SP record
if len(newDE.SystemUseEntries) > 0 && newDE.SystemUseEntries[0].Type() == "SP" {
sprecord, err := SPRecordDecode(newDE.SystemUseEntries[0])
if err != nil {
return nil, fmt.Errorf("invalid SP record: %w", err)
}
hasRockRidge, err := suspHasRockRidge(newDE.SystemUseEntries)
if err != nil {
return nil, fmt.Errorf("failed to check for Rock Ridge extension: %w", err)
}
// save SUSP offset from the SP record
f.susp = &SUSPMetadata{
Offset: sprecord.BytesSkipped,
HasRockRidge: hasRockRidge,
}
}
} else {
// are we on a volume with SUSP?
if f.susp != nil {
// Ignore error if some of the SUSP data is malformed. Just take the valid part.
offsetSystemUse := newDE.SystemUse[f.susp.Offset:]
newDE.SystemUseEntries, _ = splitSystemUseEntries(offsetSystemUse, f.ra)
}
}
i += entryLength
newFile := &File{ra: f.ra,
de: newDE,
children: nil,
susp: f.susp.Clone(),
}
f.children = append(f.children, newFile)
}
}
return f.children, nil
}
// GetChildren returns the children entries in case of a directory
// or an error in case of a file. It does NOT include the "." and ".." entries.
func (f *File) GetChildren() ([]*File, error) {
children, err := f.GetAllChildren()
if err != nil {
return nil, err
}
filteredChildren := make([]*File, 0, len(children)-2)
for _, child := range children {
if child.de.Identifier == string([]byte{0}) || child.de.Identifier == string([]byte{1}) {
continue
}
filteredChildren = append(filteredChildren, child)
}
return filteredChildren, nil
}
// GetDotEntry returns the "." entry of a directory
// or an error in case of a file.
func (f *File) GetDotEntry() (*File, error) {
children, err := f.GetAllChildren()
if err != nil {
return nil, err
}
for _, child := range children {
if child.de.Identifier == string([]byte{0}) {
return child, nil
}
}
return nil, nil
}
// Reader returns a reader that allows to read the file's data.
// If File is a directory, it returns nil.
func (f *File) Reader() io.Reader {
if f.IsDir() {
return nil
}
baseOffset := int64(f.de.ExtentLocation) * int64(sectorSize)
return io.NewSectionReader(f.ra, baseOffset, int64(f.de.ExtentLength))
}
+658
View File
@@ -0,0 +1,658 @@
package iso9660
import (
"bytes"
"container/list"
"errors"
"fmt"
"io"
"math"
"os"
"path"
"path/filepath"
"runtime"
"strings"
"sync/atomic"
"time"
)
const (
primaryVolumeDirectoryIdentifierMaxLength = 31 // ECMA-119 7.6.3
primaryVolumeFileIdentifierMaxLength = 30 // ECMA-119 7.5
)
var (
// ErrFileTooLarge is returned when trying to process a file of size greater
// than 4GB, which due to the 32-bit address limitation is not possible
// except with ISO 9660-Level 3
ErrFileTooLarge = errors.New("file is exceeding the maximum file size of 4GB")
)
// ImageWriter is responsible for staging an image's contents
// and writing them to an image.
type ImageWriter struct {
stagingDir string
}
// NewWriter creates a new ImageWrite and initializes its temporary staging dir.
// Cleanup should be called after the ImageWriter is no longer needed.
func NewWriter() (*ImageWriter, error) {
tmp, err := os.MkdirTemp("", "")
if err != nil {
return nil, err
}
return &ImageWriter{stagingDir: tmp}, nil
}
// Cleanup deletes the underlying temporary staging directory of an ImageWriter.
// It can be called multiple times without issues.
func (iw *ImageWriter) Cleanup() error {
if iw.stagingDir == "" {
return nil
}
if err := os.RemoveAll(iw.stagingDir); err != nil {
return err
}
iw.stagingDir = ""
return nil
}
// AddFile adds a file to the ImageWriter's staging area.
// All path components are mangled to match basic ISO9660 filename requirements.
func (iw *ImageWriter) AddFile(data io.Reader, filePath string) error {
directoryPath, fileName := manglePath(filePath)
if err := os.MkdirAll(path.Join(iw.stagingDir, directoryPath), 0755); err != nil {
return err
}
f, err := os.OpenFile(path.Join(iw.stagingDir, directoryPath, fileName), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, data)
return err
}
func failIfSymlink(path string) error {
info, err := os.Lstat(path)
if err != nil {
return err
}
if info.Mode()&os.ModeSymlink != 0 {
return fmt.Errorf("%q is a symlink - these are not yet supported", path)
}
return nil
}
// AddLocalFile adds a file identified by its path to the ImageWriter's staging area.
func (iw *ImageWriter) AddLocalFile(origin, target string) error {
if err := failIfSymlink(origin); err != nil {
return err
}
directoryPath, fileName := manglePath(target)
if err := os.MkdirAll(path.Join(iw.stagingDir, directoryPath), 0755); err != nil {
return err
}
// try to hardlink file to staging area before copying.
stagedFile := path.Join(iw.stagingDir, directoryPath, fileName)
if err := os.Remove(stagedFile); err != nil && !os.IsNotExist(err) {
return err
}
if err := os.Link(origin, stagedFile); err == nil {
return nil
}
f, err := os.Open(origin)
if err != nil {
return err
}
defer f.Close()
return iw.AddFile(f, target)
}
func ensureIsDirectory(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
fileinfo, err := f.Stat()
if err != nil {
return err
}
if !fileinfo.IsDir() {
return fmt.Errorf("%q is not a directory", path)
}
return nil
}
// AddLocalDirectory adds a directory recursively to the ImageWriter's staging area.
func (iw *ImageWriter) AddLocalDirectory(origin, target string) error {
if err := ensureIsDirectory(origin); err != nil {
return err
}
walkfn := func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
relPath := path[len(origin):] // We need the path to be relative to the origin.
return iw.AddLocalFile(path, filepath.Join(target, relPath))
}
return filepath.Walk(origin, walkfn)
}
func manglePath(input string) (string, string) {
input = posixifyPath(input)
nonEmptySegments := splitPath(input)
dirSegments := nonEmptySegments[:len(nonEmptySegments)-1]
name := nonEmptySegments[len(nonEmptySegments)-1]
for i := 0; i < len(dirSegments); i++ {
dirSegments[i] = mangleDirectoryName(dirSegments[i])
}
name = mangleFileName(name)
return path.Join(dirSegments...), name
}
// Converts given path to Posix (replacing \ with /)
//
// @param {string} givenPath Path to convert
//
// @returns {string} Converted filepath
func posixifyPath(path string) string {
if runtime.GOOS == "windows" {
return strings.ReplaceAll(path, "\\", "/")
}
return path
}
func splitPath(input string) []string {
rawSegments := strings.Split(input, "/")
var nonEmptySegments []string
for _, s := range rawSegments {
if len(s) > 0 {
nonEmptySegments = append(nonEmptySegments, s)
}
}
return nonEmptySegments
}
// See ECMA-119 7.5
func mangleFileName(input string) string {
// https://github.com/torvalds/linux/blob/v5.6/fs/isofs/dir.c#L29
input = strings.ToLower(input)
split := strings.Split(input, ".")
version := "1"
var filename, extension string
if len(split) == 1 {
filename = split[0]
} else {
filename = strings.Join(split[:len(split)-1], "_")
extension = split[len(split)-1]
}
// enough characters for the `.ignition` extension
extension = mangleD1String(extension, 8)
maxRemainingFilenameLength := primaryVolumeFileIdentifierMaxLength - (1 + len(version))
if len(extension) > 0 {
maxRemainingFilenameLength -= (1 + len(extension))
}
filename = mangleD1String(filename, maxRemainingFilenameLength)
if len(extension) > 0 {
return filename + "." + extension + ";" + version
}
return filename + ";" + version
}
// See ECMA-119 7.6
func mangleDirectoryName(input string) string {
return mangleD1String(input, primaryVolumeDirectoryIdentifierMaxLength)
}
func mangleD1String(input string, maxCharacters int) string {
// https://github.com/torvalds/linux/blob/v5.6/fs/isofs/dir.c#L29
input = strings.ToLower(input)
var mangledString string
for i := 0; i < len(input) && i < maxCharacters; i++ {
r := rune(input[i])
if strings.ContainsRune(d1Characters, r) {
mangledString += string(r)
} else {
mangledString += "_"
}
}
return mangledString
}
// calculateDirChildrenSectors calculates the total mashalled size of all DirectoryEntries
// within a directory. The size of each entry depends of the length of the filename.
func calculateDirChildrenSectors(path string) (uint32, error) {
contents, err := os.ReadDir(path)
if err != nil {
return 0, err
}
var sectors uint32
var currentSectorOccupied uint32 = 68 // the 0x00 and 0x01 entries
for _, c := range contents {
identifierLen := len(c.Name())
idPaddingLen := (identifierLen + 1) % 2
entryLength := uint32(33 + identifierLen + idPaddingLen)
if currentSectorOccupied+entryLength > sectorSize {
sectors++
currentSectorOccupied = entryLength
} else {
currentSectorOccupied += entryLength
}
}
if currentSectorOccupied > 0 {
sectors++
}
return sectors, nil
}
func fileLengthToSectors(l uint32) uint32 {
if (l % sectorSize) == 0 {
return l / sectorSize
}
return (l / sectorSize) + 1
}
type writeContext struct {
stagingDir string
timestamp RecordingTimestamp
freeSectorPointer uint32
}
func (wc *writeContext) allocateSectors(n uint32) uint32 {
return atomic.AddUint32(&wc.freeSectorPointer, n) - n
}
func (wc *writeContext) createDEForRoot() (*DirectoryEntry, error) {
extentLengthInSectors, err := calculateDirChildrenSectors(wc.stagingDir)
if err != nil {
return nil, err
}
extentLocation := wc.allocateSectors(extentLengthInSectors)
de := &DirectoryEntry{
ExtendedAtributeRecordLength: 0,
ExtentLocation: int32(extentLocation),
ExtentLength: uint32(extentLengthInSectors * sectorSize),
RecordingDateTime: wc.timestamp,
FileFlags: dirFlagDir,
FileUnitSize: 0, // 0 for non-interleaved write
InterleaveGap: 0, // not interleaved
VolumeSequenceNumber: 1, // we only have one volume
Identifier: string([]byte{0}),
SystemUse: []byte{},
}
return de, nil
}
type itemToWrite struct {
isDirectory bool
dirPath string
ownEntry *DirectoryEntry
parentEntery *DirectoryEntry
childrenEntries []*DirectoryEntry
targetSector uint32
}
// scanDirectory reads the directory's contents and adds them to the queue, as well as stores all their DirectoryEntries in the item,
// because we'll need them to write this item's descriptor.
func (wc *writeContext) scanDirectory(item *itemToWrite, dirPath string, ownEntry *DirectoryEntry, parentEntery *DirectoryEntry, targetSector uint32) (*list.List, error) {
contents, err := os.ReadDir(dirPath)
if err != nil {
return nil, err
}
itemsToWrite := list.New()
for _, c := range contents {
var (
fileFlags byte
extentLengthInSectors uint32
extentLength uint32
)
if c.IsDir() {
extentLengthInSectors, err = calculateDirChildrenSectors(path.Join(dirPath, c.Name()))
if err != nil {
return nil, err
}
fileFlags = dirFlagDir
extentLength = extentLengthInSectors * sectorSize
} else {
fileinfo, err := c.Info()
if err != nil {
return nil, err
}
if fileinfo.Size() > int64(math.MaxUint32) {
return nil, ErrFileTooLarge
}
extentLength = uint32(fileinfo.Size())
extentLengthInSectors = fileLengthToSectors(extentLength)
fileFlags = 0
}
extentLocation := wc.allocateSectors(extentLengthInSectors)
de := &DirectoryEntry{
ExtendedAtributeRecordLength: 0,
ExtentLocation: int32(extentLocation),
ExtentLength: uint32(extentLength),
RecordingDateTime: wc.timestamp,
FileFlags: fileFlags,
FileUnitSize: 0, // 0 for non-interleaved write
InterleaveGap: 0, // not interleaved
VolumeSequenceNumber: 1, // we only have one volume
Identifier: c.Name(),
SystemUse: []byte{},
}
// Add this child's descriptor to the currently scanned directory's list of children,
// so that later we can use it for writing the current item.
if item.childrenEntries == nil {
item.childrenEntries = []*DirectoryEntry{de}
} else {
item.childrenEntries = append(item.childrenEntries, de)
}
// queue this child for processing
itemsToWrite.PushBack(itemToWrite{
isDirectory: c.IsDir(),
dirPath: path.Join(dirPath, c.Name()),
ownEntry: de,
parentEntery: ownEntry,
targetSector: uint32(de.ExtentLocation),
})
}
return itemsToWrite, nil
}
// processDirectory writes a given directory item to the destination sectors
func processDirectory(w io.Writer, children []*DirectoryEntry, ownEntry *DirectoryEntry, parentEntry *DirectoryEntry) error {
var currentOffset uint32
currentDE := ownEntry.Clone()
currentDE.Identifier = string([]byte{0})
parentDE := parentEntry.Clone()
parentDE.Identifier = string([]byte{1})
currentDEData, err := currentDE.MarshalBinary()
if err != nil {
return err
}
parentDEData, err := parentDE.MarshalBinary()
if err != nil {
return err
}
n, err := w.Write(currentDEData)
if err != nil {
return err
}
currentOffset += uint32(n)
n, err = w.Write(parentDEData)
if err != nil {
return err
}
currentOffset += uint32(n)
for _, childDescriptor := range children {
data, err := childDescriptor.MarshalBinary()
if err != nil {
return err
}
remainingSectorSpace := sectorSize - (currentOffset % sectorSize)
if remainingSectorSpace < uint32(len(data)) {
// ECMA-119 6.8.1.1 If the body of the next descriptor won't fit into the sector,
// we fill the rest of space with zeros and skip to the next sector.
zeros := bytes.Repeat([]byte{0}, int(remainingSectorSpace))
_, err = w.Write(zeros)
if err != nil {
return err
}
// skip to the next sector
currentOffset = 0
}
n, err = w.Write(data)
if err != nil {
return err
}
currentOffset += uint32(n)
}
// fill with zeros to the end of the sector
remainingSectorSpace := sectorSize - (currentOffset % sectorSize)
if remainingSectorSpace != 0 {
zeros := bytes.Repeat([]byte{0}, int(remainingSectorSpace))
_, err = w.Write(zeros)
if err != nil {
return err
}
}
return nil
}
func processFile(w io.Writer, dirPath string) error {
f, err := os.Open(dirPath)
if err != nil {
return err
}
defer f.Close()
fileinfo, err := f.Stat()
if err != nil {
return err
}
if fileinfo.Size() > int64(math.MaxUint32) {
return ErrFileTooLarge
}
buffer := make([]byte, sectorSize)
for bytesLeft := uint32(fileinfo.Size()); bytesLeft > 0; {
var toRead uint32
if bytesLeft < sectorSize {
toRead = bytesLeft
} else {
toRead = sectorSize
}
if _, err = io.ReadAtLeast(f, buffer, int(toRead)); err != nil {
return err
}
if _, err = w.Write(buffer); err != nil {
return err
}
bytesLeft -= toRead
}
// We already write a whole sector-sized buffer, so there's need to fill with zeroes.
return nil
}
// traverseStagingDir creates a new queue of items to write by traversing the staging directory
func (wc *writeContext) traverseStagingDir(rootItem itemToWrite) (*list.List, error) {
itemsToWrite := list.New()
itemsToWrite.PushBack(rootItem)
for item := itemsToWrite.Front(); item != nil; item = item.Next() {
it := item.Value.(itemToWrite)
if it.isDirectory {
newItems, err := wc.scanDirectory(&it, it.dirPath, it.ownEntry, it.parentEntery, it.targetSector)
if err != nil {
relativePath := it.dirPath[len(wc.stagingDir):]
return nil, fmt.Errorf("processing %s: %s", relativePath, err)
}
itemsToWrite.PushBackList(newItems)
}
item.Value = it
}
return itemsToWrite, nil
}
func writeAll(w io.Writer, itemsToWrite *list.List) error {
for item := itemsToWrite.Front(); item != nil; item = item.Next() {
it := item.Value.(itemToWrite)
var err error
if it.isDirectory {
err = processDirectory(w, it.childrenEntries, it.ownEntry, it.parentEntery)
} else {
err = processFile(w, it.dirPath)
}
if err != nil {
return err
}
}
return nil
}
// WriteTo writes the image to the given WriterAt
func (iw *ImageWriter) WriteTo(w io.Writer, volumeIdentifier string) error {
now := time.Now()
wc := writeContext{
stagingDir: iw.stagingDir,
timestamp: RecordingTimestamp{},
freeSectorPointer: 18, // system area (16) + 2 volume descriptors
}
rootDE, err := wc.createDEForRoot()
if err != nil {
return fmt.Errorf("creating root directory descriptor: %s", err)
}
rootItem := itemToWrite{
isDirectory: true,
dirPath: wc.stagingDir,
ownEntry: rootDE,
parentEntery: rootDE,
targetSector: uint32(rootDE.ExtentLocation),
}
itemsToWrite, err := wc.traverseStagingDir(rootItem)
if err != nil {
return fmt.Errorf("tranversing staging directory: %s", err)
}
pvd := volumeDescriptor{
Header: volumeDescriptorHeader{
Type: volumeTypePrimary,
Identifier: standardIdentifierBytes,
Version: 1,
},
Primary: &PrimaryVolumeDescriptorBody{
SystemIdentifier: runtime.GOOS,
VolumeIdentifier: volumeIdentifier,
VolumeSpaceSize: int32(wc.freeSectorPointer),
VolumeSetSize: 1,
VolumeSequenceNumber: 1,
LogicalBlockSize: int16(sectorSize),
PathTableSize: 0,
TypeLPathTableLoc: 0,
OptTypeLPathTableLoc: 0,
TypeMPathTableLoc: 0,
OptTypeMPathTableLoc: 0,
RootDirectoryEntry: rootDE,
VolumeSetIdentifier: "",
PublisherIdentifier: "",
DataPreparerIdentifier: "",
ApplicationIdentifier: "github.com/kdomanski/iso9660",
CopyrightFileIdentifier: "",
AbstractFileIdentifier: "",
BibliographicFileIdentifier: "",
VolumeCreationDateAndTime: VolumeDescriptorTimestampFromTime(now),
VolumeModificationDateAndTime: VolumeDescriptorTimestampFromTime(now),
VolumeExpirationDateAndTime: VolumeDescriptorTimestamp{},
VolumeEffectiveDateAndTime: VolumeDescriptorTimestampFromTime(now),
FileStructureVersion: 1,
ApplicationUsed: [512]byte{},
},
}
terminator := volumeDescriptor{
Header: volumeDescriptorHeader{
Type: volumeTypeTerminator,
Identifier: standardIdentifierBytes,
Version: 1,
},
}
// write 16 sectors of zeroes
zeroSector := bytes.Repeat([]byte{0}, int(sectorSize))
for i := uint32(0); i < 16; i++ {
if _, err = w.Write(zeroSector); err != nil {
return err
}
}
buffer, err := pvd.MarshalBinary()
if err != nil {
return err
}
if _, err = w.Write(buffer); err != nil {
return err
}
if buffer, err = terminator.MarshalBinary(); err != nil {
return err
}
if _, err = w.Write(buffer); err != nil {
return err
}
if err = writeAll(w, itemsToWrite); err != nil {
return fmt.Errorf("writing files: %s", err)
}
return nil
}
+605
View File
@@ -0,0 +1,605 @@
// Package iso9660 implements reading and creating basic ISO9660 images.
package iso9660
import (
"encoding"
"encoding/binary"
"errors"
"fmt"
"io"
"strconv"
"strings"
"time"
)
// ISO 9660 Overview
// https://archive.fo/xs9ac
const (
sectorSize uint32 = 2048
systemAreaSize = sectorSize * 16
standardIdentifier = "CD001"
udfIdentifier = "BEA01"
volumeTypeBoot byte = 0
volumeTypePrimary byte = 1
volumeTypeSupplementary byte = 2
volumeTypePartition byte = 3
volumeTypeTerminator byte = 255
volumeDescriptorBodySize = sectorSize - 7
aCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_!\"%&'()*+,-./:;<=>?"
dCharacters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"
// ECMA-119 7.4.2.2 defines d1-characters as
// "subject to agreement between the originator and the recipient of the volume".
d1Characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_!\"%&'()*+,-./:;<=>?"
)
const (
dirFlagHidden = 1 << iota
dirFlagDir
dirFlagAssociated
dirFlagRecord
dirFlagProtection
_
_
dirFlagMultiExtent
)
var standardIdentifierBytes = [5]byte{'C', 'D', '0', '0', '1'}
var ErrUDFNotSupported = errors.New("UDF volumes are not supported")
// volumeDescriptorHeader represents the data in bytes 0-6
// of a Volume Descriptor as defined in ECMA-119 8.1
type volumeDescriptorHeader struct {
Type byte
Identifier [5]byte
Version byte
}
var _ encoding.BinaryUnmarshaler = &volumeDescriptorHeader{}
var _ encoding.BinaryMarshaler = &volumeDescriptorHeader{}
// UnmarshalBinary decodes a volumeDescriptorHeader from binary form
func (vdh *volumeDescriptorHeader) UnmarshalBinary(data []byte) error {
if len(data) < 7 {
return io.ErrUnexpectedEOF
}
vdh.Type = data[0]
copy(vdh.Identifier[:], data[1:6])
vdh.Version = data[6]
return nil
}
func (vdh volumeDescriptorHeader) MarshalBinary() ([]byte, error) {
data := make([]byte, 7)
data[0] = vdh.Type
data[6] = vdh.Version
copy(data[1:6], vdh.Identifier[:])
return data, nil
}
// BootVolumeDescriptorBody represents the data in bytes 7-2047
// of a Boot Record as defined in ECMA-119 8.2
type BootVolumeDescriptorBody struct {
BootSystemIdentifier string
BootIdentifier string
BootSystemUse [1977]byte
}
var _ encoding.BinaryUnmarshaler = &BootVolumeDescriptorBody{}
// PrimaryVolumeDescriptorBody represents the data in bytes 7-2047
// of a Primary Volume Descriptor as defined in ECMA-119 8.4
type PrimaryVolumeDescriptorBody struct {
SystemIdentifier string
VolumeIdentifier string
VolumeSpaceSize int32
VolumeSetSize int16
VolumeSequenceNumber int16
LogicalBlockSize int16
PathTableSize int32
TypeLPathTableLoc int32
OptTypeLPathTableLoc int32
TypeMPathTableLoc int32
OptTypeMPathTableLoc int32
RootDirectoryEntry *DirectoryEntry
VolumeSetIdentifier string
PublisherIdentifier string
DataPreparerIdentifier string
ApplicationIdentifier string
CopyrightFileIdentifier string
AbstractFileIdentifier string
BibliographicFileIdentifier string
VolumeCreationDateAndTime VolumeDescriptorTimestamp
VolumeModificationDateAndTime VolumeDescriptorTimestamp
VolumeExpirationDateAndTime VolumeDescriptorTimestamp
VolumeEffectiveDateAndTime VolumeDescriptorTimestamp
FileStructureVersion byte
ApplicationUsed [512]byte
}
var _ encoding.BinaryUnmarshaler = &PrimaryVolumeDescriptorBody{}
var _ encoding.BinaryMarshaler = PrimaryVolumeDescriptorBody{}
// DirectoryEntry contains data from a Directory Descriptor
// as described by ECMA-119 9.1
type DirectoryEntry struct {
ExtendedAtributeRecordLength byte
ExtentLocation int32
ExtentLength uint32
RecordingDateTime RecordingTimestamp
FileFlags byte
FileUnitSize byte
InterleaveGap byte
VolumeSequenceNumber int16
Identifier string
SystemUse []byte
SystemUseEntries SystemUseEntrySlice
}
var _ encoding.BinaryUnmarshaler = &DirectoryEntry{}
var _ encoding.BinaryMarshaler = &DirectoryEntry{}
// UnmarshalBinary decodes a DirectoryEntry from binary form
func (de *DirectoryEntry) UnmarshalBinary(data []byte) error {
length := data[0]
if length == 0 {
return io.EOF
}
var err error
de.ExtendedAtributeRecordLength = data[1]
if de.ExtentLocation, err = UnmarshalInt32LSBMSB(data[2:10]); err != nil {
return err
}
if de.ExtentLength, err = UnmarshalUint32LSBMSB(data[10:18]); err != nil {
return err
}
if err = de.RecordingDateTime.UnmarshalBinary(data[18:25]); err != nil {
return err
}
de.FileFlags = data[25]
de.FileUnitSize = data[26]
de.InterleaveGap = data[27]
if de.VolumeSequenceNumber, err = UnmarshalInt16LSBMSB(data[28:32]); err != nil {
return err
}
identifierLen := data[32]
de.Identifier = string(data[33 : 33+identifierLen])
// add padding if identifier length was even]
idPaddingLen := (identifierLen + 1) % 2
de.SystemUse = data[33+identifierLen+idPaddingLen : length]
return nil
}
// MarshalBinary encodes a DirectoryEntry to binary form
func (de *DirectoryEntry) MarshalBinary() ([]byte, error) {
identifierLen := len(de.Identifier)
idPaddingLen := (identifierLen + 1) % 2
totalLen := 33 + identifierLen + idPaddingLen + len(de.SystemUse)
if totalLen > 255 {
return nil, fmt.Errorf("identifier %q is too long", de.Identifier)
}
data := make([]byte, totalLen)
data[0] = byte(totalLen)
data[1] = de.ExtendedAtributeRecordLength
WriteInt32LSBMSB(data[2:10], de.ExtentLocation)
WriteInt32LSBMSB(data[10:18], int32(de.ExtentLength))
de.RecordingDateTime.MarshalBinary(data[18:25])
data[25] = de.FileFlags
data[26] = de.FileUnitSize
data[27] = de.InterleaveGap
WriteInt16LSBMSB(data[28:32], de.VolumeSequenceNumber)
data[32] = byte(identifierLen)
copy(data[33:33+identifierLen], []byte(de.Identifier))
copy(data[33+identifierLen+idPaddingLen:totalLen], de.SystemUse)
return data, nil
}
// Clone creates a copy of the DirectoryEntry
func (de *DirectoryEntry) Clone() DirectoryEntry {
newDE := DirectoryEntry{
ExtendedAtributeRecordLength: de.ExtendedAtributeRecordLength,
ExtentLocation: de.ExtentLocation,
ExtentLength: de.ExtentLength,
RecordingDateTime: de.RecordingDateTime,
FileFlags: de.FileFlags,
FileUnitSize: de.FileUnitSize,
InterleaveGap: de.InterleaveGap,
VolumeSequenceNumber: de.VolumeSequenceNumber,
Identifier: de.Identifier,
SystemUse: make([]byte, len(de.SystemUse)),
}
copy(newDE.SystemUse, de.SystemUse)
return newDE
}
// UnmarshalBinary decodes a PrimaryVolumeDescriptorBody from binary form as defined in ECMA-119 8.4
func (pvd *PrimaryVolumeDescriptorBody) UnmarshalBinary(data []byte) error {
if len(data) < 2048 {
return io.ErrUnexpectedEOF
}
var err error
pvd.SystemIdentifier = strings.TrimRight(string(data[8:40]), " ")
pvd.VolumeIdentifier = strings.TrimRight(string(data[40:72]), " ")
if pvd.VolumeSpaceSize, err = UnmarshalInt32LSBMSB(data[80:88]); err != nil {
return err
}
if pvd.VolumeSetSize, err = UnmarshalInt16LSBMSB(data[120:124]); err != nil {
return err
}
if pvd.VolumeSequenceNumber, err = UnmarshalInt16LSBMSB(data[124:128]); err != nil {
return err
}
if pvd.LogicalBlockSize, err = UnmarshalInt16LSBMSB(data[128:132]); err != nil {
return err
}
if pvd.PathTableSize, err = UnmarshalInt32LSBMSB(data[132:140]); err != nil {
return err
}
pvd.TypeLPathTableLoc = int32(binary.LittleEndian.Uint32(data[140:144]))
pvd.OptTypeLPathTableLoc = int32(binary.LittleEndian.Uint32(data[144:148]))
pvd.TypeMPathTableLoc = int32(binary.BigEndian.Uint32(data[148:152]))
pvd.OptTypeMPathTableLoc = int32(binary.BigEndian.Uint32(data[152:156]))
if pvd.RootDirectoryEntry == nil {
pvd.RootDirectoryEntry = &DirectoryEntry{}
}
if err = pvd.RootDirectoryEntry.UnmarshalBinary(data[156:190]); err != nil {
return err
}
pvd.VolumeSetIdentifier = strings.TrimRight(string(data[190:318]), " ")
pvd.PublisherIdentifier = strings.TrimRight(string(data[318:446]), " ")
pvd.DataPreparerIdentifier = strings.TrimRight(string(data[446:574]), " ")
pvd.ApplicationIdentifier = strings.TrimRight(string(data[574:702]), " ")
pvd.CopyrightFileIdentifier = strings.TrimRight(string(data[702:740]), " ")
pvd.AbstractFileIdentifier = strings.TrimRight(string(data[740:776]), " ")
pvd.BibliographicFileIdentifier = strings.TrimRight(string(data[776:813]), " ")
if pvd.VolumeCreationDateAndTime.UnmarshalBinary(data[813:830]) != nil {
return err
}
if pvd.VolumeModificationDateAndTime.UnmarshalBinary(data[830:847]) != nil {
return err
}
if pvd.VolumeExpirationDateAndTime.UnmarshalBinary(data[847:864]) != nil {
return err
}
if pvd.VolumeEffectiveDateAndTime.UnmarshalBinary(data[864:881]) != nil {
return err
}
pvd.FileStructureVersion = data[881]
copy(pvd.ApplicationUsed[:], data[883:1395])
return nil
}
// MarshalBinary encodes the PrimaryVolumeDescriptorBody to its binary form
func (pvd PrimaryVolumeDescriptorBody) MarshalBinary() ([]byte, error) {
output := make([]byte, sectorSize)
d := MarshalString(pvd.SystemIdentifier, 32)
copy(output[8:40], d)
d = MarshalString(pvd.VolumeIdentifier, 32)
copy(output[40:72], d)
WriteInt32LSBMSB(output[80:88], pvd.VolumeSpaceSize)
WriteInt16LSBMSB(output[120:124], pvd.VolumeSetSize)
WriteInt16LSBMSB(output[124:128], pvd.VolumeSequenceNumber)
WriteInt16LSBMSB(output[128:132], pvd.LogicalBlockSize)
WriteInt32LSBMSB(output[132:140], pvd.PathTableSize)
binary.LittleEndian.PutUint32(output[140:144], uint32(pvd.TypeLPathTableLoc))
binary.LittleEndian.PutUint32(output[144:148], uint32(pvd.OptTypeLPathTableLoc))
binary.BigEndian.PutUint32(output[148:152], uint32(pvd.TypeMPathTableLoc))
binary.BigEndian.PutUint32(output[152:156], uint32(pvd.OptTypeMPathTableLoc))
binaryRDE, err := pvd.RootDirectoryEntry.MarshalBinary()
if err != nil {
return nil, err
}
copy(output[156:190], binaryRDE)
copy(output[190:318], MarshalString(pvd.VolumeSetIdentifier, 128))
copy(output[318:446], MarshalString(pvd.PublisherIdentifier, 128))
copy(output[446:574], MarshalString(pvd.DataPreparerIdentifier, 128))
copy(output[574:702], MarshalString(pvd.ApplicationIdentifier, 128))
copy(output[702:740], MarshalString(pvd.CopyrightFileIdentifier, 38))
copy(output[740:776], MarshalString(pvd.AbstractFileIdentifier, 36))
copy(output[776:813], MarshalString(pvd.BibliographicFileIdentifier, 37))
d, err = pvd.VolumeCreationDateAndTime.MarshalBinary()
if err != nil {
return nil, err
}
copy(output[813:830], d)
d, err = pvd.VolumeModificationDateAndTime.MarshalBinary()
if err != nil {
return nil, err
}
copy(output[830:847], d)
d, err = pvd.VolumeExpirationDateAndTime.MarshalBinary()
if err != nil {
return nil, err
}
copy(output[847:864], d)
d, err = pvd.VolumeEffectiveDateAndTime.MarshalBinary()
if err != nil {
return nil, err
}
copy(output[864:881], d)
output[881] = pvd.FileStructureVersion
output[882] = 0
copy(output[883:1395], pvd.ApplicationUsed[:])
for i := 1395; i < 2048; i++ {
output[i] = 0
}
return output, nil
}
// UnmarshalBinary decodes a BootVolumeDescriptorBody from binary form
func (bvd *BootVolumeDescriptorBody) UnmarshalBinary(data []byte) error {
bvd.BootSystemIdentifier = strings.TrimRight(string(data[7:39]), " ")
bvd.BootIdentifier = strings.TrimRight(string(data[39:71]), " ")
if n := copy(bvd.BootSystemUse[:], data[71:2048]); n != 1977 {
return fmt.Errorf("BootVolumeDescriptorBody.UnmarshalBinary: copied %d bytes", n)
}
return nil
}
type volumeDescriptor struct {
Header volumeDescriptorHeader
Boot *BootVolumeDescriptorBody
Primary *PrimaryVolumeDescriptorBody
}
var _ encoding.BinaryUnmarshaler = &volumeDescriptor{}
var _ encoding.BinaryMarshaler = &volumeDescriptor{}
func (vd volumeDescriptor) Type() byte {
return vd.Header.Type
}
// UnmarshalBinary decodes a volumeDescriptor from binary form
func (vd *volumeDescriptor) UnmarshalBinary(data []byte) error {
if uint32(len(data)) < sectorSize {
return io.ErrUnexpectedEOF
}
if err := vd.Header.UnmarshalBinary(data); err != nil {
// this should never fail, since volumeDescriptorHeader.UnmarshalBinary( ) only checks data size too
return err
}
id := string(vd.Header.Identifier[:])
if id != standardIdentifier {
if id == udfIdentifier {
return ErrUDFNotSupported
}
return fmt.Errorf("volume descriptor %q != %q", id, standardIdentifier)
}
switch vd.Header.Type {
case volumeTypeBoot:
vd.Boot = &BootVolumeDescriptorBody{}
return vd.Boot.UnmarshalBinary(data)
case volumeTypePartition:
return errors.New("partition volumes are not yet supported")
case volumeTypePrimary, volumeTypeSupplementary:
vd.Primary = &PrimaryVolumeDescriptorBody{}
return vd.Primary.UnmarshalBinary(data)
case volumeTypeTerminator:
return nil
}
return fmt.Errorf("unknown volume type 0x%X", vd.Header.Type)
}
// UnmarshalBinary decodes a volumeDescriptor from binary form
func (vd volumeDescriptor) MarshalBinary() ([]byte, error) {
var output []byte
var err error
switch vd.Header.Type {
case volumeTypeBoot:
return nil, errors.New("boot volumes are not yet supported")
case volumeTypePartition:
return nil, errors.New("partition volumes are not yet supported")
case volumeTypePrimary, volumeTypeSupplementary:
if output, err = vd.Primary.MarshalBinary(); err != nil {
return nil, err
}
case volumeTypeTerminator:
output = make([]byte, sectorSize)
}
data, err := vd.Header.MarshalBinary()
if err != nil {
return nil, err
}
copy(output[0:7], data)
return output, nil
}
// VolumeDescriptorTimestamp represents a time and date format
// that can be encoded according to ECMA-119 8.4.26.1
type VolumeDescriptorTimestamp struct {
Year int
Month int
Day int
Hour int
Minute int
Second int
Hundredth int
Offset int
}
var _ encoding.BinaryMarshaler = &VolumeDescriptorTimestamp{}
var _ encoding.BinaryUnmarshaler = &VolumeDescriptorTimestamp{}
// MarshalBinary encodes the timestamp into a binary form
func (ts *VolumeDescriptorTimestamp) MarshalBinary() ([]byte, error) {
formatted := fmt.Sprintf("%04d%02d%02d%02d%02d%02d%02d", ts.Year, ts.Month, ts.Day, ts.Hour, ts.Minute, ts.Second, ts.Hundredth)
formattedBytes := append([]byte(formatted), byte(ts.Offset))
if len(formattedBytes) != 17 {
return nil, fmt.Errorf("VolumeDescriptorTimestamp.MarshalBinary: the formatted timestamp is %d bytes long", len(formatted))
}
return formattedBytes, nil
}
// UnmarshalBinary decodes a VolumeDescriptorTimestamp from binary form
func (ts *VolumeDescriptorTimestamp) UnmarshalBinary(data []byte) error {
if len(data) < 17 {
return io.ErrUnexpectedEOF
}
year, err := strconv.Atoi(strings.TrimSpace(string(data[0:4])))
if err != nil {
return err
}
month, err := strconv.Atoi(strings.TrimSpace(string(data[4:6])))
if err != nil {
return err
}
day, err := strconv.Atoi(strings.TrimSpace(string(data[6:8])))
if err != nil {
return err
}
hour, err := strconv.Atoi(strings.TrimSpace(string(data[8:10])))
if err != nil {
return err
}
min, err := strconv.Atoi(strings.TrimSpace(string(data[10:12])))
if err != nil {
return err
}
sec, err := strconv.Atoi(strings.TrimSpace(string(data[12:14])))
if err != nil {
return err
}
hundredth, err := strconv.Atoi(strings.TrimSpace(string(data[14:16])))
if err != nil {
return err
}
*ts = VolumeDescriptorTimestamp{
Year: year,
Month: month,
Day: day,
Hour: hour,
Minute: min,
Second: sec,
Hundredth: hundredth,
Offset: int(data[16]),
}
return nil
}
// RecordingTimestamp represents a time and date format
// that can be encoded according to ECMA-119 9.1.5
type RecordingTimestamp time.Time
var _ encoding.BinaryUnmarshaler = &RecordingTimestamp{}
// UnmarshalBinary decodes a RecordingTimestamp from binary form
func (ts *RecordingTimestamp) UnmarshalBinary(data []byte) error {
if len(data) < 7 {
return io.ErrUnexpectedEOF
}
year := 1900 + int(data[0])
month := int(data[1])
day := int(data[2])
hour := int(data[3])
min := int(data[4])
sec := int(data[5])
tzOffset := int(data[6])
secondsInAQuarter := 60 * 15
tz := time.FixedZone("", tzOffset*secondsInAQuarter)
*ts = RecordingTimestamp(time.Date(year, time.Month(month), day, hour, min, sec, 0, tz))
return nil
}
// MarshalBinary encodes the RecordingTimestamp in its binary form to a buffer
// of the length of 7 or more bytes
func (ts RecordingTimestamp) MarshalBinary(dst []byte) {
_ = dst[6] // early bounds check to guarantee safety of writes below
t := time.Time(ts)
year, month, day := t.Date()
hour, min, sec := t.Clock()
_, secOffset := t.Zone()
secondsInAQuarter := 60 * 15
offsetInQuarters := secOffset / secondsInAQuarter
dst[0] = byte(year - 1900)
dst[1] = byte(month)
dst[2] = byte(day)
dst[3] = byte(hour)
dst[4] = byte(min)
dst[5] = byte(sec)
dst[6] = byte(offsetInQuarters)
}
// VolumeDescriptorTimestampFromTime converts time.Time to VolumeDescriptorTimestamp
func VolumeDescriptorTimestampFromTime(t time.Time) VolumeDescriptorTimestamp {
t = t.UTC()
year, month, day := t.Date()
hour, minute, second := t.Clock()
hundredth := t.Nanosecond() / 10000000
return VolumeDescriptorTimestamp{
Year: year,
Month: int(month),
Day: day,
Hour: hour,
Minute: minute,
Second: second,
Hundredth: hundredth,
Offset: 0, // we converted to UTC
}
}
+70
View File
@@ -0,0 +1,70 @@
package iso9660
import (
"encoding/binary"
"fmt"
"io"
"strings"
)
// MarshalString encodes the given string as a byte array padded to the given length
func MarshalString(s string, padToLength int) []byte {
if len(s) > padToLength {
s = s[:padToLength]
}
missingPadding := padToLength - len(s)
s = s + strings.Repeat(" ", missingPadding)
return []byte(s)
}
// UnmarshalInt32LSBMSB decodes a 32-bit integer in both byte orders, as defined in ECMA-119 7.3.3
func UnmarshalInt32LSBMSB(data []byte) (int32, error) {
if len(data) < 8 {
return 0, io.ErrUnexpectedEOF
}
lsb := int32(binary.LittleEndian.Uint32(data[0:4]))
msb := int32(binary.BigEndian.Uint32(data[4:8]))
if lsb != msb {
return 0, fmt.Errorf("little-endian and big-endian value mismatch: %d != %d", lsb, msb)
}
return lsb, nil
}
// UnmarshalUint32LSBMSB is the same as UnmarshalInt32LSBMSB but returns an unsigned integer
func UnmarshalUint32LSBMSB(data []byte) (uint32, error) {
n, err := UnmarshalInt32LSBMSB(data)
return uint32(n), err
}
// UnmarshalInt16LSBMSB decodes a 16-bit integer in both byte orders, as defined in ECMA-119 7.3.3
func UnmarshalInt16LSBMSB(data []byte) (int16, error) {
if len(data) < 4 {
return 0, io.ErrUnexpectedEOF
}
lsb := int16(binary.LittleEndian.Uint16(data[0:2]))
msb := int16(binary.BigEndian.Uint16(data[2:4]))
if lsb != msb {
return 0, fmt.Errorf("little-endian and big-endian value mismatch: %d != %d", lsb, msb)
}
return lsb, nil
}
// WriteInt32LSBMSB writes a 32-bit integer in both byte orders, as defined in ECMA-119 7.3.3
func WriteInt32LSBMSB(dst []byte, value int32) {
_ = dst[7] // early bounds check to guarantee safety of writes below
binary.LittleEndian.PutUint32(dst[0:4], uint32(value))
binary.BigEndian.PutUint32(dst[4:8], uint32(value))
}
// WriteInt16LSBMSB writes a 16-bit integer in both byte orders, as defined in ECMA-119 7.2.3
func WriteInt16LSBMSB(dst []byte, value int16) {
_ = dst[3] // early bounds check to guarantee safety of writes below
binary.LittleEndian.PutUint16(dst[0:2], uint16(value))
binary.BigEndian.PutUint16(dst[2:4], uint16(value))
}
+99
View File
@@ -0,0 +1,99 @@
package iso9660
import (
"fmt"
"io/fs"
"os"
)
/* The following types of Rock Ridge records are being handled in some way:
* - [X] PX (RR 4.1.1: POSIX file attributes)
* - [ ] PN (RR 4.1.2: POSIX device number)
* - [ ] SL (RR 4.1.3: symbolic link)
* - [x] NM (RR 4.1.4: alternate name)
* - [ ] CL (RR 4.1.5.1: child link)
* - [ ] PL (RR 4.1.5.2: parent link)
* - [ ] RE (RR 4.1.5.3: relocated directory)
* - [ ] TF (RR 4.1.6: time stamp(s) for a file)
* - [ ] SF (RR 4.1.7: file data in sparse file format)
*/
const (
RockRidgeIdentifier = "RRIP_1991A"
RockRidgeVersion = 1
)
type RockRidgeNameEntry struct {
Flags byte
Name string
}
func suspHasRockRidge(se SystemUseEntrySlice) (bool, error) {
extensions, err := se.GetExtensionRecords()
if err != nil {
return false, err
}
for _, entry := range extensions {
if entry.Identifier == RockRidgeIdentifier && entry.Version == RockRidgeVersion {
return true, nil
}
}
return false, nil
}
func (s SystemUseEntrySlice) GetRockRidgeName() string {
var name string
for _, entry := range s {
// There is a continuation flag in the record, but we determine continuation
// by simply reading all NM entries.
if entry.Type() == "NM" {
nm := umarshalRockRidgeNameEntry(entry)
name += nm.Name
}
}
return name
}
func (s SystemUseEntrySlice) GetPosixAttr() (fs.FileMode, error) {
for _, entry := range s {
if entry.Type() == "PX" {
// BUG(kdomanski): If there are multiple RR PX entries (which is forbidden by the spec), the reader will use the first one.
return umarshalRockRidgeAttrEntry(entry)
}
}
return 0, fmt.Errorf("mandatory entry PX not found")
}
func umarshalRockRidgeAttrEntry(e SystemUseEntry) (fs.FileMode, error) {
rrMode, err := UnmarshalUint32LSBMSB(e.Data()[0:8])
if err != nil {
return 0, fmt.Errorf("unmarshall RR PX entry: %w", err)
}
S_IFLNK := (rrMode & 0170000) == 0120000
S_IFDIR := (rrMode & 0170000) == 0040000
mode := rrMode & uint32(fs.ModePerm) // UNIX permissions
if S_IFLNK {
mode |= uint32(os.ModeSymlink)
}
if S_IFDIR {
mode |= uint32(os.ModeDir)
}
return fs.FileMode(mode), nil
}
func umarshalRockRidgeNameEntry(e SystemUseEntry) *RockRidgeNameEntry {
return &RockRidgeNameEntry{
Flags: e.Data()[0],
Name: string(e.Data()[1:]),
}
}
+211
View File
@@ -0,0 +1,211 @@
package iso9660
import (
"fmt"
"io"
)
/* The following types of core SUSP records are being handled in some way:
* - [x] CE (SUSP 5.1: continuation)
* - [ ] PD (SUSP 5.2: padding)
* - [x] SP (SUSP 5.3: offset)
* - [ ] ST (SUSP 5.4)
* - [x] ER (SUSP 5.5: extension record)
* - [ ] ES (SUSP 5.6)
*/
// SUSP-112 4.1
type SystemUseEntry []byte
func (e SystemUseEntry) Length() int {
return int(e[2])
}
func (e SystemUseEntry) Data() []byte {
return e[4:]
}
func (e SystemUseEntry) Type() string {
return string(e[:2])
}
type ExtensionRecord struct {
Version int
Identifier string
Descriptor string
Source string
}
// See SUSP-112 5.5
func ExtensionRecordDecode(e SystemUseEntry) (*ExtensionRecord, error) {
if e.Type() != "ER" {
return nil, fmt.Errorf("wrong type of record, expected ER")
}
if e.Length() < 8 {
return nil, io.ErrUnexpectedEOF
}
identifierLen := int(e[4])
if e.Length() < 8+identifierLen {
return nil, io.ErrUnexpectedEOF
}
descriptorLen := int(e[5])
if e.Length() < 8+identifierLen+descriptorLen {
return nil, io.ErrUnexpectedEOF
}
sourceLen := int(e[6])
if e.Length() < 8+identifierLen+descriptorLen+sourceLen {
return nil, io.ErrUnexpectedEOF
}
return &ExtensionRecord{
Version: int(e[7]),
Identifier: string(e[8 : 8+identifierLen]),
Descriptor: string(e[8+identifierLen : 8+identifierLen+descriptorLen]),
Source: string(e[8+identifierLen+descriptorLen : 8+identifierLen+descriptorLen+sourceLen]),
}, nil
}
// See SUSP-112 5.3
func SPRecordDecode(e SystemUseEntry) (*SPRecord, error) {
if e.Type() != "SP" {
return nil, fmt.Errorf("wrong type of record, expected SP")
}
if e.Length() < 7 {
return nil, io.ErrUnexpectedEOF
}
if beByte := e[4]; beByte != 0xBE {
return nil, fmt.Errorf("invalid control byte, %x != 0xBE", beByte)
}
if efByte := e[5]; efByte != 0xEF {
return nil, fmt.Errorf("invalid control byte, %x != 0xEF", efByte)
}
return &SPRecord{
BytesSkipped: e[6],
}, nil
}
type SPRecord struct {
BytesSkipped uint8
}
type SystemUseEntrySlice []SystemUseEntry
func (s SystemUseEntrySlice) GetExtensionRecords() ([]*ExtensionRecord, error) {
results := make([]*ExtensionRecord, 0)
for _, entry := range s {
if entry.Type() == "ER" {
er, err := ExtensionRecordDecode(entry)
if err != nil {
return nil, err
}
results = append(results, er)
}
}
return results, nil
}
// SUSP-112 5.1
type ContinuationEntry struct {
blockLocation uint32
offset uint32
lengthOfArea uint32
}
func umarshalContinuationEntry(e SystemUseEntry) (*ContinuationEntry, error) {
if e.Length() != 28 {
return nil, fmt.Errorf("invalid ContinuationArea record with length %d instead of 28", e.Length())
}
location, err := UnmarshalUint32LSBMSB(e.Data()[0:8])
if err != nil {
return nil, fmt.Errorf("block location: %w", err)
}
offset, err := UnmarshalUint32LSBMSB(e.Data()[8:16])
if err != nil {
return nil, fmt.Errorf("offset: %w", err)
}
length, err := UnmarshalUint32LSBMSB(e.Data()[16:24])
if err != nil {
return nil, fmt.Errorf("length: %w", err)
}
return &ContinuationEntry{
blockLocation: location,
offset: offset,
lengthOfArea: length,
}, nil
}
const (
SUEType_ContinuationArea = "CE"
SUEType_PaddingField = "PD"
SUEType_SharingProtocolIndicator = "SP"
SUEType_SharingProtocolTerminator = "ST"
SUEType_ExtensionsReference = "ER"
SUEType_ExtensionSelector = "ES"
)
func splitSystemUseEntries(data []byte, ra io.ReaderAt) ([]SystemUseEntry, error) {
output := make([]SystemUseEntry, 0)
for len(data) > 0 {
if len(data) < 4 {
// SUSP-112 4
// If the remaining allocated space /.../ is less than four bytes long /.../ shall be ignored.
break
}
entryLen := int(data[2])
if len(data) < entryLen {
return nil, fmt.Errorf("splitting System Use entries: %w, expected %d bytes but have only %d", io.ErrUnexpectedEOF, entryLen, len(data))
}
entry := SystemUseEntry(data[:entryLen])
if entry.Type() == SUEType_ContinuationArea {
ce, err := umarshalContinuationEntry(entry)
if err != nil {
return output, fmt.Errorf("unmarshaling ContinuationEntry: %w", err)
}
continuation := make([]byte, ce.lengthOfArea)
finalOffset := (ce.blockLocation * sectorSize) + ce.offset
if _, err := ra.ReadAt(continuation, int64(finalOffset)); err != nil {
return output, fmt.Errorf("reading Continuation Area: %w", err)
}
continuedEntries, err := splitSystemUseEntries(continuation, ra)
if err != nil {
return output, fmt.Errorf("splitting Continuation Area: %w", err)
}
output = append(output, continuedEntries...)
} else {
output = append(output, entry)
}
data = data[entryLen:]
}
return output, nil
}
type SUSPMetadata struct {
Offset uint8
HasRockRidge bool
}
func (sm *SUSPMetadata) Clone() *SUSPMetadata {
if sm == nil {
return nil
}
return &SUSPMetadata{
Offset: sm.Offset,
HasRockRidge: sm.HasRockRidge,
}
}
+14
View File
@@ -0,0 +1,14 @@
# Binaries for programs and plugins
*.exe
*.dll
*.so
*.dylib
# Test binary, build with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
.glide/
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2017, Vladimir Jigulin
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+37
View File
@@ -0,0 +1,37 @@
## Udf filesystem golang library
- Non-optimized
- Some functioal is broken
- `recovery()` style error handling interface
- Work only with certain iso's
It's all because I has reached requried functional for me.
## Example
```go
package main
import (
"fmt"
"os"
"github.com/mogaika/udf"
)
func main() {
r, _ := os.Open("example.iso")
u := udf.NewUdfFromReader(r)
for _, f := range u.ReadDir(nil) {
fmt.Printf("%s %-10d %-20s %v\n", f.Mode().String(), f.Size(), f.Name(), f.ModTime())
}
}
```
Output:
```
-r-xr-xr-x 57 system.cnf 2006-02-11 00:00:00 +0000 UTC
-r-xr-xr-x 1911580 SCUS_973.99 2006-03-15 00:00:00 +0000 UTC
-r-xr-xr-x 278305 ioprp300.img 2005-11-14 00:00:00 +0000 UTC
-r-xr-xr-x 6641 sio2man.irx 2005-10-18 00:00:00 +0000 UTC
-r-xr-xr-x 15653 dbcman.irx 2005-10-18 00:00:00 +0000 UTC
```
+99
View File
@@ -0,0 +1,99 @@
package udf
import (
"encoding/binary"
"time"
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
func r_u8(b []byte) uint8 {
return b[0]
}
func r_i8(b []byte) int8 {
return int8(r_u8(b))
}
var rl_u64 = binary.LittleEndian.Uint64
func rl_u48(b []byte) uint64 {
var buf [8]byte
copy(buf[:], b[:6])
return rl_u64(buf[:])
}
var rl_u32 = binary.LittleEndian.Uint32
var rl_u16 = binary.LittleEndian.Uint16
func rl_i64(b []byte) int64 {
return int64(rl_u64(b))
}
func rl_i32(b []byte) int32 {
return int32(rl_u32(b))
}
func rl_i16(b []byte) int16 {
return int16(rl_u16(b))
}
var rb_u64 = binary.BigEndian.Uint64
var rb_u32 = binary.BigEndian.Uint32
var rb_u16 = binary.BigEndian.Uint16
func rb_u8(b []byte) uint8 {
return b[0]
}
func rb_i64(b []byte) int64 {
return int64(rb_u64(b))
}
func rb_i32(b []byte) int32 {
return int32(rb_u32(b))
}
func rb_i16(b []byte) int16 {
return int16(rb_u16(b))
}
func r_dstring(b []byte, fieldlen int) string {
if fieldlen == 0 {
return ""
}
return string(b[:b[fieldlen-1]])
}
func r_dcharacters(b []byte) string {
if len(b) == 0 {
return ""
}
switch b[0] {
case 8:
s, _, err := transform.Bytes(charmap.Windows1252.NewDecoder(), b[1:])
if err != nil {
panic(err)
}
return string(s)
case 16:
s, _, err := transform.Bytes(unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewDecoder(), b[1:])
if err != nil {
panic(err)
}
return string(s)
default:
return ""
}
}
func r_timestamp(b []byte) time.Time {
var t time.Time
t = t.AddDate(int(rl_u16(b[2:])), int(b[4]), int(b[5]))
t.Add(time.Duration(b[6])*time.Hour +
time.Duration(b[7])*time.Minute +
time.Duration(b[8])*time.Second)
return t
}
+368
View File
@@ -0,0 +1,368 @@
package udf
import (
"time"
)
const (
DESCRIPTOR_PRIMARY_VOLUME = 0x1
DESCRIPTOR_ANCHOR_VOLUME_POINTER = 0x2
DESCRIPTOR_VOLUME_POINTER = 0x3
DESCRIPTOR_IMPLEMENTATION_USE_VOLUME = 0x4
DESCRIPTOR_PARTITION = 0x5
DESCRIPTOR_LOGICAL_VOLUME = 0x6
DESCRIPTOR_UNALLOCATED = 0x7
DESCRIPTOR_TERMINATING = 0x8
DESCRIPTOR_FILE_SET = 0x100
DESCRIPTOR_IDENTIFIER = 0x101
DESCRIPTOR_ALLOCATION_EXTENT = 0x102
DESCRIPTOR_INDIRECT_ENTRY = 0x103
DESCRIPTOR_TERMINAL_ENTRY = 0x104
DESCRIPTOR_FILE_ENTRY = 0x105
)
type Descriptor struct {
TagIdentifier uint16
DescriptorVersion uint16
TagChecksum uint8
TagSerialNumber uint16
DescriptorCRC uint16
DescriptorCRCLength uint16
TagLocation uint32
data []byte
}
func (d *Descriptor) Data() []byte {
buf := make([]byte, len(d.data))
copy(buf, d.data[16:])
return buf
}
func (d *Descriptor) FromBytes(b []byte) *Descriptor {
d.TagIdentifier = rl_u16(b[0:])
d.DescriptorVersion = rl_u16(b[2:])
d.TagChecksum = r_u8(b[3:])
d.TagSerialNumber = rl_u16(b[6:])
d.DescriptorCRC = rl_u16(b[8:])
d.DescriptorCRCLength = rl_u16(b[10:])
d.TagLocation = rl_u32(b[12:])
d.data = b[:]
return d
}
func NewDescriptor(b []byte) *Descriptor {
return new(Descriptor).FromBytes(b)
}
type AnchorVolumeDescriptorPointer struct {
Descriptor Descriptor
MainVolumeDescriptorSeq Extent
ReserveVolumeDescriptorSeq Extent
}
func (ad *AnchorVolumeDescriptorPointer) FromBytes(b []byte) *AnchorVolumeDescriptorPointer {
ad.Descriptor.FromBytes(b)
ad.MainVolumeDescriptorSeq = NewExtent(b[16:])
ad.ReserveVolumeDescriptorSeq = NewExtent(b[24:])
return ad
}
func NewAnchorVolumeDescriptorPointer(b []byte) *AnchorVolumeDescriptorPointer {
return new(AnchorVolumeDescriptorPointer).FromBytes(b)
}
func (d *Descriptor) AnchorVolumeDescriptorPointer() *AnchorVolumeDescriptorPointer {
return NewAnchorVolumeDescriptorPointer(d.data)
}
type PrimaryVolumeDescriptor struct {
Descriptor Descriptor
VolumeDescriptorSequenceNumber uint32
PrimaryVolumeDescriptorNumber uint32
VolumeIdentifier string
VolumeSequenceNumber uint16
MaximumVolumeSequenceNumber uint16
InterchangeLevel uint16
MaximumInterchangeLevel uint16
CharacterSetList uint32
MaximumCharacterSetList uint32
VolumeSetIdentifier string
VolumeAbstract Extent
VolumeCopyrightNoticeExtent Extent
ApplicationIdentifier EntityID
RecordingDateTime time.Time
ImplementationIdentifier EntityID
ImplementationUse []byte
PredecessorVolumeDescriptorSequenceLocation uint32
Flags uint16
}
func (pvd *PrimaryVolumeDescriptor) FromBytes(b []byte) *PrimaryVolumeDescriptor {
pvd.Descriptor.FromBytes(b)
pvd.VolumeDescriptorSequenceNumber = rl_u32(b[16:])
pvd.PrimaryVolumeDescriptorNumber = rl_u32(b[20:])
pvd.VolumeIdentifier = r_dstring(b[24:], 32)
pvd.VolumeSequenceNumber = rl_u16(b[56:])
pvd.MaximumVolumeSequenceNumber = rl_u16(b[58:])
pvd.InterchangeLevel = rl_u16(b[60:])
pvd.MaximumInterchangeLevel = rl_u16(b[62:])
pvd.CharacterSetList = rl_u32(b[64:])
pvd.MaximumCharacterSetList = rl_u32(b[68:])
pvd.VolumeSetIdentifier = r_dstring(b[72:], 128)
pvd.VolumeAbstract = NewExtent(b[328:])
pvd.VolumeCopyrightNoticeExtent = NewExtent(b[336:])
pvd.ApplicationIdentifier = NewEntityID(b[344:])
pvd.RecordingDateTime = r_timestamp(b[376:])
pvd.ImplementationIdentifier = NewEntityID(b[388:])
pvd.ImplementationUse = b[420:484]
pvd.PredecessorVolumeDescriptorSequenceLocation = rl_u32(b[484:])
pvd.Flags = rl_u16(b[488:])
return pvd
}
func NewPrimaryVolumeDescriptor(b []byte) *PrimaryVolumeDescriptor {
return new(PrimaryVolumeDescriptor).FromBytes(b)
}
func (d *Descriptor) PrimaryVolumeDescriptor() *PrimaryVolumeDescriptor {
return NewPrimaryVolumeDescriptor(d.data)
}
type PartitionDescriptor struct {
Descriptor Descriptor
VolumeDescriptorSequenceNumber uint32
PartitionFlags uint16
PartitionNumber uint16
PartitionContents EntityID
PartitionContentsUse []byte
AccessType uint32
PartitionStartingLocation uint32
PartitionLength uint32
ImplementationIdentifier EntityID
ImplementationUse []byte
}
func (pd *PartitionDescriptor) FromBytes(b []byte) *PartitionDescriptor {
pd.Descriptor.FromBytes(b)
pd.VolumeDescriptorSequenceNumber = rl_u32(b[16:])
pd.PartitionFlags = rl_u16(b[20:])
pd.PartitionNumber = rl_u16(b[22:])
pd.PartitionContents = NewEntityID(b[24:])
pd.PartitionContentsUse = b[56:184]
pd.AccessType = rl_u32(b[184:])
pd.PartitionStartingLocation = rl_u32(b[188:])
pd.PartitionLength = rl_u32(b[192:])
pd.ImplementationIdentifier = NewEntityID(b[196:])
pd.ImplementationUse = b[228:356]
return pd
}
func NewPartitionDescriptor(b []byte) *PartitionDescriptor {
return new(PartitionDescriptor).FromBytes(b)
}
func (d *Descriptor) PartitionDescriptor() *PartitionDescriptor {
return NewPartitionDescriptor(d.data)
}
type PartitionMap struct {
PartitionMapType uint8
PartitionMapLength uint8
VolumeSequenceNumber uint16
PartitionNumber uint16
}
func (pm *PartitionMap) FromBytes(b []byte) *PartitionMap {
pm.PartitionMapType = rb_u8(b[0:])
pm.PartitionMapLength = rb_u8(b[1:])
pm.VolumeSequenceNumber = rb_u16(b[2:])
pm.PartitionNumber = rb_u16(b[4:])
return pm
}
type LogicalVolumeDescriptor struct {
Descriptor Descriptor
VolumeDescriptorSequenceNumber uint32
LogicalVolumeIdentifier string
LogicalBlockSize uint32
DomainIdentifier EntityID
LogicalVolumeContentsUse ExtentLong
MapTableLength uint32
NumberOfPartitionMaps uint32
ImplementationIdentifier EntityID
ImplementationUse []byte
IntegritySequenceExtent Extent
PartitionMaps []PartitionMap
}
func (lvd *LogicalVolumeDescriptor) FromBytes(b []byte) *LogicalVolumeDescriptor {
lvd.Descriptor.FromBytes(b)
lvd.VolumeDescriptorSequenceNumber = rl_u32(b[16:])
lvd.LogicalVolumeIdentifier = r_dstring(b[84:], 128)
lvd.LogicalBlockSize = rl_u32(b[212:])
lvd.DomainIdentifier = NewEntityID(b[216:])
lvd.LogicalVolumeContentsUse = NewExtentLong(b[248:])
lvd.MapTableLength = rl_u32(b[264:])
lvd.NumberOfPartitionMaps = rl_u32(b[268:])
lvd.ImplementationIdentifier = NewEntityID(b[272:])
lvd.ImplementationUse = b[304:432]
lvd.IntegritySequenceExtent = NewExtent(b[432:])
lvd.PartitionMaps = make([]PartitionMap, lvd.NumberOfPartitionMaps)
for i := range lvd.PartitionMaps {
lvd.PartitionMaps[i].FromBytes(b[440+i*6:])
}
return lvd
}
func NewLogicalVolumeDescriptor(b []byte) *LogicalVolumeDescriptor {
return new(LogicalVolumeDescriptor).FromBytes(b)
}
func (d *Descriptor) LogicalVolumeDescriptor() *LogicalVolumeDescriptor {
return NewLogicalVolumeDescriptor(d.data)
}
type FileSetDescriptor struct {
Descriptor Descriptor
RecordingDateTime time.Time
InterchangeLevel uint16
MaximumInterchangeLevel uint16
CharacterSetList uint32
MaximumCharacterSetList uint32
FileSetNumber uint32
FileSetDescriptorNumber uint32
LogicalVolumeIdentifier string
FileSetIdentifier string
CopyrightFileIdentifier string
AbstractFileIdentifier string
RootDirectoryICB ExtentLong
DomainIdentifier EntityID
NexExtent ExtentLong
}
func (fsd *FileSetDescriptor) FromBytes(b []byte) *FileSetDescriptor {
fsd.Descriptor.FromBytes(b)
fsd.RecordingDateTime = r_timestamp(b[16:])
fsd.InterchangeLevel = rl_u16(b[28:])
fsd.MaximumInterchangeLevel = rl_u16(b[30:])
fsd.CharacterSetList = rl_u32(b[32:])
fsd.MaximumCharacterSetList = rl_u32(b[36:])
fsd.FileSetNumber = rl_u32(b[40:])
fsd.FileSetDescriptorNumber = rl_u32(b[44:])
fsd.LogicalVolumeIdentifier = r_dstring(b[112:], 128)
fsd.FileSetIdentifier = r_dstring(b[304:], 32)
fsd.CopyrightFileIdentifier = r_dstring(b[336:], 32)
fsd.AbstractFileIdentifier = r_dstring(b[368:], 32)
fsd.RootDirectoryICB = NewExtentLong(b[400:])
fsd.DomainIdentifier = NewEntityID(b[416:])
fsd.NexExtent = NewExtentLong(b[448:])
return fsd
}
func NewFileSetDescriptor(b []byte) *FileSetDescriptor {
return new(FileSetDescriptor).FromBytes(b)
}
func (d *Descriptor) FileSetDescriptor() *FileSetDescriptor {
return NewFileSetDescriptor(d.data)
}
type FileIdentifierDescriptor struct {
Descriptor Descriptor
FileVersionNumber uint16
FileCharacteristics uint8
LengthOfFileIdentifier uint8
ICB ExtentLong
LengthOfImplementationUse uint16
ImplementationUse EntityID
FileIdentifier string
}
func (fid *FileIdentifierDescriptor) Len() uint64 {
l := 38 + uint64(fid.LengthOfImplementationUse) + uint64(fid.LengthOfFileIdentifier)
return 4 * ((l + 3) / 4) // padding = 4
}
func (fid *FileIdentifierDescriptor) FromBytes(b []byte) *FileIdentifierDescriptor {
fid.Descriptor.FromBytes(b)
fid.FileVersionNumber = rl_u16(b[16:])
fid.FileCharacteristics = r_u8(b[18:])
fid.LengthOfFileIdentifier = r_u8(b[19:])
fid.ICB = NewExtentLong(b[20:])
fid.LengthOfImplementationUse = rl_u16(b[36:])
fid.ImplementationUse = NewEntityID(b[38:])
identStart := 38 + fid.LengthOfImplementationUse
fid.FileIdentifier = r_dcharacters(b[identStart : fid.LengthOfFileIdentifier+uint8(identStart)])
return fid
}
func NewFileIdentifierDescriptor(b []byte) *FileIdentifierDescriptor {
return new(FileIdentifierDescriptor).FromBytes(b)
}
func (d *Descriptor) FileIdentifierDescriptor() *FileIdentifierDescriptor {
return NewFileIdentifierDescriptor(d.data)
}
type FileEntry struct {
Descriptor Descriptor
ICBTag *ICBTag
Uid uint32
Gid uint32
Permissions uint32
FileLinkCount uint16
RecordFormat uint8
RecordDisplayAttributes uint8
RecordLength uint32
InformationLength uint64
LogicalBlocksRecorded uint64
AccessTime time.Time
ModificationTime time.Time
AttributeTime time.Time
Checkpoint uint32
ExtendedAttributeICB ExtentLong
ImplementationIdentifier EntityID
UniqueId uint64
LengthOfExtendedAttributes uint32
LengthOfAllocationDescriptors uint32
ExtendedAttributes []byte
AllocationDescriptors []Extent
}
func (fe *FileEntry) FromBytes(b []byte) *FileEntry {
fe.Descriptor.FromBytes(b)
fe.ICBTag = NewICBTag(b[16:])
fe.Uid = rl_u32(b[36:])
fe.Gid = rl_u32(b[40:])
fe.Permissions = rl_u32(b[44:])
fe.FileLinkCount = rl_u16(b[48:])
fe.RecordFormat = r_u8(b[50:])
fe.RecordDisplayAttributes = r_u8(b[51:])
fe.RecordLength = rl_u32(b[52:])
fe.InformationLength = rl_u64(b[56:])
fe.LogicalBlocksRecorded = rl_u64(b[64:])
fe.AccessTime = r_timestamp(b[72:])
fe.ModificationTime = r_timestamp(b[84:])
fe.AttributeTime = r_timestamp(b[96:])
fe.Checkpoint = rl_u32(b[108:])
fe.ExtendedAttributeICB = NewExtentLong(b[112:])
fe.ImplementationIdentifier = NewEntityID(b[128:])
fe.UniqueId = rl_u64(b[160:])
fe.LengthOfExtendedAttributes = rl_u32(b[168:])
fe.LengthOfAllocationDescriptors = rl_u32(b[172:])
allocDescStart := 176 + fe.LengthOfExtendedAttributes
fe.ExtendedAttributes = b[176:allocDescStart]
fe.AllocationDescriptors = make([]Extent, fe.LengthOfAllocationDescriptors/8)
for i := range fe.AllocationDescriptors {
fe.AllocationDescriptors[i] = NewExtent(b[allocDescStart+uint32(i)*8:])
}
return fe
}
func NewFileEntry(b []byte) *FileEntry {
return new(FileEntry).FromBytes(b)
}
func (d *Descriptor) FileEntry() *FileEntry {
return NewFileEntry(d.data)
}
+14
View File
@@ -0,0 +1,14 @@
package udf
type EntityID struct {
Flags uint8
Identifier [23]byte
IdentifierSuffix [8]byte
}
func NewEntityID(b []byte) EntityID {
e := EntityID{Flags: b[0]}
copy(e.Identifier[:], b[1:24])
copy(e.IdentifierSuffix[:], b[24:32])
return e
}
+37
View File
@@ -0,0 +1,37 @@
package udf
type Extent struct {
Length uint32
Location uint32
}
func NewExtent(b []byte) Extent {
return Extent{
Length: rl_u32(b[0:]),
Location: rl_u32(b[4:]),
}
}
type ExtentSmall struct {
Length uint16
Location uint64
}
func NewExtentSmall(b []byte) ExtentSmall {
return ExtentSmall{
Length: rl_u16(b[0:]),
Location: rl_u48(b[2:]),
}
}
type ExtentLong struct {
Length uint32
Location uint64
}
func NewExtentLong(b []byte) ExtentLong {
return ExtentLong{
Length: rl_u32(b[0:]),
Location: rl_u48(b[4:]),
}
}
+74
View File
@@ -0,0 +1,74 @@
package udf
import (
"io"
"os"
"time"
)
type File struct {
Udf *Udf
Fid *FileIdentifierDescriptor
fe *FileEntry
fileEntryPosition uint64
}
func (f *File) GetFileEntryPosition() int64 {
return int64(f.fileEntryPosition)
}
func (f *File) GetFileOffset() int64 {
return SECTOR_SIZE * (int64(f.FileEntry().AllocationDescriptors[0].Location) + int64(f.Udf.PartitionStart()))
}
func (f *File) FileEntry() *FileEntry {
if f.fe == nil {
f.fileEntryPosition = f.Fid.ICB.Location
f.fe = NewFileEntry(f.Udf.ReadSector(f.Udf.PartitionStart() + f.fileEntryPosition))
}
return f.fe
}
func (f *File) NewReader() *io.SectionReader {
return io.NewSectionReader(f.Udf.r, f.GetFileOffset(), f.Size())
}
func (f *File) Name() string {
return f.Fid.FileIdentifier
}
func (f *File) Mode() os.FileMode {
var mode os.FileMode
perms := os.FileMode(f.FileEntry().Permissions)
mode |= ((perms >> 0) & 7) << 0
mode |= ((perms >> 5) & 7) << 3
mode |= ((perms >> 10) & 7) << 6
if f.IsDir() {
mode |= os.ModeDir
}
return mode
}
func (f *File) Size() int64 {
return int64(f.FileEntry().InformationLength)
}
func (f *File) ModTime() time.Time {
return f.FileEntry().ModificationTime
}
func (f *File) IsDir() bool {
// TODO :Fix! This field always 0 :(
return f.FileEntry().ICBTag.FileType == 4
}
func (f *File) Sys() interface{} {
return f.Fid
}
func (f *File) ReadDir() []File {
return f.Udf.ReadDir(f.FileEntry())
}
+26
View File
@@ -0,0 +1,26 @@
package udf
type ICBTag struct {
PriorRecordedNumberOfDirectEntries uint32
StrategyType uint16
StrategyParameter uint16
MaximumNumberOfEntries uint16
FileType uint8
ParentICBLocation uint64
Flags uint16
}
func (itag *ICBTag) FromBytes(b []byte) *ICBTag {
itag.PriorRecordedNumberOfDirectEntries = rl_u32(b[0:])
itag.StrategyType = rl_u16(b[4:])
itag.StrategyParameter = rl_u16(b[4:])
itag.MaximumNumberOfEntries = rl_u16(b[8:])
itag.FileType = r_u8(b[1:])
itag.ParentICBLocation = rl_u48(b[12:])
itag.Flags = rl_u16(b[18:])
return itag
}
func NewICBTag(b []byte) *ICBTag {
return new(ICBTag).FromBytes(b)
}
+118
View File
@@ -0,0 +1,118 @@
package udf
import (
"io"
)
const SECTOR_SIZE = 2048
type Udf struct {
r io.ReaderAt
isInited bool
pvd *PrimaryVolumeDescriptor
pd *PartitionDescriptor
lvd *LogicalVolumeDescriptor
fsd *FileSetDescriptor
root_fe *FileEntry
}
func (udf *Udf) PartitionStart() uint64 {
if udf.pd == nil {
panic(udf)
} else {
return uint64(udf.pd.PartitionStartingLocation)
}
}
func (udf *Udf) GetReader() io.ReaderAt {
return udf.r
}
func (udf *Udf) ReadSectors(sectorNumber uint64, sectorsCount uint64) []byte {
buf := make([]byte, SECTOR_SIZE*sectorsCount)
readed, err := udf.r.ReadAt(buf[:], int64(SECTOR_SIZE*sectorNumber))
if err != nil {
panic(err)
}
if readed != int(SECTOR_SIZE*sectorsCount) {
panic(readed)
}
return buf[:]
}
func (udf *Udf) ReadSector(sectorNumber uint64) []byte {
return udf.ReadSectors(sectorNumber, 1)
}
func (udf *Udf) init() {
if udf.isInited {
return
}
anchorDesc := NewAnchorVolumeDescriptorPointer(udf.ReadSector(256))
if anchorDesc.Descriptor.TagIdentifier != DESCRIPTOR_ANCHOR_VOLUME_POINTER {
panic(anchorDesc.Descriptor.TagIdentifier)
}
for sector := uint64(anchorDesc.MainVolumeDescriptorSeq.Location); ; sector++ {
desc := NewDescriptor(udf.ReadSector(sector))
if desc.TagIdentifier == DESCRIPTOR_TERMINATING {
break
}
switch desc.TagIdentifier {
case DESCRIPTOR_PRIMARY_VOLUME:
udf.pvd = desc.PrimaryVolumeDescriptor()
case DESCRIPTOR_PARTITION:
udf.pd = desc.PartitionDescriptor()
case DESCRIPTOR_LOGICAL_VOLUME:
udf.lvd = desc.LogicalVolumeDescriptor()
}
}
partitionStart := udf.PartitionStart()
udf.fsd = NewFileSetDescriptor(udf.ReadSector(partitionStart + udf.lvd.LogicalVolumeContentsUse.Location))
udf.root_fe = NewFileEntry(udf.ReadSector(partitionStart + udf.fsd.RootDirectoryICB.Location))
udf.isInited = true
}
func (udf *Udf) ReadDir(fe *FileEntry) []File {
udf.init()
if fe == nil {
fe = udf.root_fe
}
ps := udf.PartitionStart()
adPos := fe.AllocationDescriptors[0]
fdLen := uint64(adPos.Length)
fdBuf := udf.ReadSectors(ps+uint64(adPos.Location), (fdLen+SECTOR_SIZE-1)/SECTOR_SIZE)
fdOff := uint64(0)
result := make([]File, 0)
for uint32(fdOff) < adPos.Length {
fid := NewFileIdentifierDescriptor(fdBuf[fdOff:])
if fid.FileIdentifier != "" {
result = append(result, File{
Udf: udf,
Fid: fid,
})
}
fdOff += fid.Len()
}
return result
}
func NewUdfFromReader(r io.ReaderAt) *Udf {
udf := &Udf{
r: r,
isInited: false,
}
return udf
}
+2 -2
View File
@@ -1,4 +1,4 @@
Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright 2009 The Go Authors.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
@@ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer.
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
* Neither the name of Google LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
+2761 -213
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -4,7 +4,7 @@
// Package bcrypt implements Provos and Mazières's bcrypt adaptive hashing
// algorithm. See http://www.usenix.org/event/usenix99/provos/provos.pdf
package bcrypt // import "golang.org/x/crypto/bcrypt"
package bcrypt
// The code is a port of Provos and Mazières's C implementation.
import (
@@ -50,7 +50,7 @@ func (ih InvalidHashPrefixError) Error() string {
type InvalidCostError int
func (ic InvalidCostError) Error() string {
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed range (%d,%d)", int(ic), MinCost, MaxCost)
return fmt.Sprintf("crypto/bcrypt: cost %d is outside allowed inclusive range %d..%d", int(ic), MinCost, MaxCost)
}
const (
+4492 -677
View File
File diff suppressed because it is too large Load Diff
+1427 -264
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -12,6 +12,8 @@ import (
// XOF defines the interface to hash functions that
// support arbitrary-length output.
//
// New callers should prefer the standard library [hash.XOF].
type XOF interface {
// Write absorbs more data into the hash's state. It panics if called
// after Read.
@@ -47,6 +49,8 @@ const maxOutputLength = (1 << 32) * 64
//
// A non-nil key turns the hash into a MAC. The key must between
// zero and 32 bytes long.
//
// The result can be safely interface-upgraded to [hash.XOF].
func NewXOF(size uint32, key []byte) (XOF, error) {
if len(key) > Size {
return nil, errKeySize
@@ -93,6 +97,10 @@ func (x *xof) Clone() XOF {
return &clone
}
func (x *xof) BlockSize() int {
return x.d.BlockSize()
}
func (x *xof) Reset() {
x.cfg[0] = byte(Size)
binary.LittleEndian.PutUint32(x.cfg[4:], uint32(Size)) // leaf length
+11
View File
@@ -0,0 +1,11 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.25
package blake2b
import "hash"
var _ hash.XOF = (*xof)(nil)
+9 -1
View File
@@ -16,9 +16,10 @@
//
// BLAKE2X is a construction to compute hash values larger than 32 bytes. It
// can produce hash values between 0 and 65535 bytes.
package blake2s // import "golang.org/x/crypto/blake2s"
package blake2s
import (
"crypto"
"encoding/binary"
"errors"
"hash"
@@ -55,6 +56,13 @@ func Sum256(data []byte) [Size]byte {
// and BinaryUnmarshaler for state (de)serialization as documented by hash.Hash.
func New256(key []byte) (hash.Hash, error) { return newDigest(Size, key) }
func init() {
crypto.RegisterHash(crypto.BLAKE2s_256, func() hash.Hash {
h, _ := New256(nil)
return h
})
}
// New128 returns a new hash.Hash computing the BLAKE2s-128 checksum given a
// non-empty key. Note that a 128-bit digest is too small to be secure as a
// cryptographic hash and should only be used as a MAC, thus the key argument
+2160 -419
View File
File diff suppressed because it is too large Load Diff
-21
View File
@@ -1,21 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.9
package blake2s
import (
"crypto"
"hash"
)
func init() {
newHash256 := func() hash.Hash {
h, _ := New256(nil)
return h
}
crypto.RegisterHash(crypto.BLAKE2s_256, newHash256)
}
+1 -1
View File
@@ -11,7 +11,7 @@
// Deprecated: any new system should use AES (from crypto/aes, if necessary in
// an AEAD mode like crypto/cipher.NewGCM) or XChaCha20-Poly1305 (from
// golang.org/x/crypto/chacha20poly1305).
package blowfish // import "golang.org/x/crypto/blowfish"
package blowfish
// The code is a port of Bruce Schneier's C implementation.
// See https://www.schneier.com/blowfish.html.
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (!arm64 && !s390x && !ppc64le) || !gc || purego
//go:build (!arm64 && !s390x && !ppc64 && !ppc64le) || !gc || purego
package chacha20
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
//go:build gc && !purego && (ppc64 || ppc64le)
package chacha20
@@ -19,7 +19,7 @@
// The differences in this and the original implementation are
// due to the calling conventions and initialization of constants.
//go:build gc && !purego
//go:build gc && !purego && (ppc64 || ppc64le)
#include "textflag.h"
@@ -33,27 +33,70 @@
#define CONSTBASE R16
#define BLOCKS R17
DATA consts<>+0x00(SB)/8, $0x3320646e61707865
DATA consts<>+0x08(SB)/8, $0x6b20657479622d32
DATA consts<>+0x10(SB)/8, $0x0000000000000001
DATA consts<>+0x18(SB)/8, $0x0000000000000000
DATA consts<>+0x20(SB)/8, $0x0000000000000004
DATA consts<>+0x28(SB)/8, $0x0000000000000000
DATA consts<>+0x30(SB)/8, $0x0a0b08090e0f0c0d
DATA consts<>+0x38(SB)/8, $0x0203000106070405
DATA consts<>+0x40(SB)/8, $0x090a0b080d0e0f0c
DATA consts<>+0x48(SB)/8, $0x0102030005060704
DATA consts<>+0x50(SB)/8, $0x6170786561707865
DATA consts<>+0x58(SB)/8, $0x6170786561707865
DATA consts<>+0x60(SB)/8, $0x3320646e3320646e
DATA consts<>+0x68(SB)/8, $0x3320646e3320646e
DATA consts<>+0x70(SB)/8, $0x79622d3279622d32
DATA consts<>+0x78(SB)/8, $0x79622d3279622d32
DATA consts<>+0x80(SB)/8, $0x6b2065746b206574
DATA consts<>+0x88(SB)/8, $0x6b2065746b206574
DATA consts<>+0x90(SB)/8, $0x0000000100000000
DATA consts<>+0x98(SB)/8, $0x0000000300000002
GLOBL consts<>(SB), RODATA, $0xa0
// for VPERMXOR
#define MASK R18
DATA consts<>+0x00(SB)/4, $0x61707865
DATA consts<>+0x04(SB)/4, $0x3320646e
DATA consts<>+0x08(SB)/4, $0x79622d32
DATA consts<>+0x0c(SB)/4, $0x6b206574
DATA consts<>+0x10(SB)/4, $0x00000001
DATA consts<>+0x14(SB)/4, $0x00000000
DATA consts<>+0x18(SB)/4, $0x00000000
DATA consts<>+0x1c(SB)/4, $0x00000000
DATA consts<>+0x20(SB)/4, $0x00000004
DATA consts<>+0x24(SB)/4, $0x00000000
DATA consts<>+0x28(SB)/4, $0x00000000
DATA consts<>+0x2c(SB)/4, $0x00000000
DATA consts<>+0x30(SB)/4, $0x0e0f0c0d
DATA consts<>+0x34(SB)/4, $0x0a0b0809
DATA consts<>+0x38(SB)/4, $0x06070405
DATA consts<>+0x3c(SB)/4, $0x02030001
DATA consts<>+0x40(SB)/4, $0x0d0e0f0c
DATA consts<>+0x44(SB)/4, $0x090a0b08
DATA consts<>+0x48(SB)/4, $0x05060704
DATA consts<>+0x4c(SB)/4, $0x01020300
DATA consts<>+0x50(SB)/4, $0x61707865
DATA consts<>+0x54(SB)/4, $0x61707865
DATA consts<>+0x58(SB)/4, $0x61707865
DATA consts<>+0x5c(SB)/4, $0x61707865
DATA consts<>+0x60(SB)/4, $0x3320646e
DATA consts<>+0x64(SB)/4, $0x3320646e
DATA consts<>+0x68(SB)/4, $0x3320646e
DATA consts<>+0x6c(SB)/4, $0x3320646e
DATA consts<>+0x70(SB)/4, $0x79622d32
DATA consts<>+0x74(SB)/4, $0x79622d32
DATA consts<>+0x78(SB)/4, $0x79622d32
DATA consts<>+0x7c(SB)/4, $0x79622d32
DATA consts<>+0x80(SB)/4, $0x6b206574
DATA consts<>+0x84(SB)/4, $0x6b206574
DATA consts<>+0x88(SB)/4, $0x6b206574
DATA consts<>+0x8c(SB)/4, $0x6b206574
DATA consts<>+0x90(SB)/4, $0x00000000
DATA consts<>+0x94(SB)/4, $0x00000001
DATA consts<>+0x98(SB)/4, $0x00000002
DATA consts<>+0x9c(SB)/4, $0x00000003
DATA consts<>+0xa0(SB)/4, $0x11223300
DATA consts<>+0xa4(SB)/4, $0x55667744
DATA consts<>+0xa8(SB)/4, $0x99aabb88
DATA consts<>+0xac(SB)/4, $0xddeeffcc
DATA consts<>+0xb0(SB)/4, $0x22330011
DATA consts<>+0xb4(SB)/4, $0x66774455
DATA consts<>+0xb8(SB)/4, $0xaabb8899
DATA consts<>+0xbc(SB)/4, $0xeeffccdd
GLOBL consts<>(SB), RODATA, $0xc0
#ifdef GOARCH_ppc64
#define BE_XXBRW_INIT() \
LVSL (R0)(R0), V24 \
VSPLTISB $3, V25 \
VXOR V24, V25, V24 \
#define BE_XXBRW(vr) VPERM vr, vr, V24, vr
#else
#define BE_XXBRW_INIT()
#define BE_XXBRW(vr)
#endif
//func chaCha20_ctr32_vsx(out, inp *byte, len int, key *[8]uint32, counter *uint32)
TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
@@ -70,6 +113,9 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
MOVD $48, R10
MOVD $64, R11
SRD $6, LEN, BLOCKS
// for VPERMXOR
MOVD $consts<>+0xa0(SB), MASK
MOVD $16, R20
// V16
LXVW4X (CONSTBASE)(R0), VS48
ADD $80,CONSTBASE
@@ -84,9 +130,15 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
// Clear V27
VXOR V27, V27, V27
BE_XXBRW_INIT()
// V28
LXVW4X (CONSTBASE)(R11), VS60
// Load mask constants for VPERMXOR
LXVW4X (MASK)(R0), V20
LXVW4X (MASK)(R20), V21
// splat slot from V19 -> V26
VSPLTW $0, V19, V26
@@ -97,7 +149,7 @@ TEXT ·chaCha20_ctr32_vsx(SB),NOSPLIT,$64-40
MOVD $10, R14
MOVD R14, CTR
PCALIGN $16
loop_outer_vsx:
// V0, V1, V2, V3
LXVW4X (R0)(CONSTBASE), VS32
@@ -128,22 +180,17 @@ loop_outer_vsx:
VSPLTISW $12, V28
VSPLTISW $8, V29
VSPLTISW $7, V30
PCALIGN $16
loop_vsx:
VADDUWM V0, V4, V0
VADDUWM V1, V5, V1
VADDUWM V2, V6, V2
VADDUWM V3, V7, V3
VXOR V12, V0, V12
VXOR V13, V1, V13
VXOR V14, V2, V14
VXOR V15, V3, V15
VRLW V12, V27, V12
VRLW V13, V27, V13
VRLW V14, V27, V14
VRLW V15, V27, V15
VPERMXOR V12, V0, V21, V12
VPERMXOR V13, V1, V21, V13
VPERMXOR V14, V2, V21, V14
VPERMXOR V15, V3, V21, V15
VADDUWM V8, V12, V8
VADDUWM V9, V13, V9
@@ -165,15 +212,10 @@ loop_vsx:
VADDUWM V2, V6, V2
VADDUWM V3, V7, V3
VXOR V12, V0, V12
VXOR V13, V1, V13
VXOR V14, V2, V14
VXOR V15, V3, V15
VRLW V12, V29, V12
VRLW V13, V29, V13
VRLW V14, V29, V14
VRLW V15, V29, V15
VPERMXOR V12, V0, V20, V12
VPERMXOR V13, V1, V20, V13
VPERMXOR V14, V2, V20, V14
VPERMXOR V15, V3, V20, V15
VADDUWM V8, V12, V8
VADDUWM V9, V13, V9
@@ -195,15 +237,10 @@ loop_vsx:
VADDUWM V2, V7, V2
VADDUWM V3, V4, V3
VXOR V15, V0, V15
VXOR V12, V1, V12
VXOR V13, V2, V13
VXOR V14, V3, V14
VRLW V15, V27, V15
VRLW V12, V27, V12
VRLW V13, V27, V13
VRLW V14, V27, V14
VPERMXOR V15, V0, V21, V15
VPERMXOR V12, V1, V21, V12
VPERMXOR V13, V2, V21, V13
VPERMXOR V14, V3, V21, V14
VADDUWM V10, V15, V10
VADDUWM V11, V12, V11
@@ -225,15 +262,10 @@ loop_vsx:
VADDUWM V2, V7, V2
VADDUWM V3, V4, V3
VXOR V15, V0, V15
VXOR V12, V1, V12
VXOR V13, V2, V13
VXOR V14, V3, V14
VRLW V15, V29, V15
VRLW V12, V29, V12
VRLW V13, V29, V13
VRLW V14, V29, V14
VPERMXOR V15, V0, V20, V15
VPERMXOR V12, V1, V20, V12
VPERMXOR V13, V2, V20, V13
VPERMXOR V14, V3, V20, V14
VADDUWM V10, V15, V10
VADDUWM V11, V12, V11
@@ -249,48 +281,48 @@ loop_vsx:
VRLW V6, V30, V6
VRLW V7, V30, V7
VRLW V4, V30, V4
BC 16, LT, loop_vsx
BDNZ loop_vsx
VADDUWM V12, V26, V12
WORD $0x13600F8C // VMRGEW V0, V1, V27
WORD $0x13821F8C // VMRGEW V2, V3, V28
VMRGEW V0, V1, V27
VMRGEW V2, V3, V28
WORD $0x10000E8C // VMRGOW V0, V1, V0
WORD $0x10421E8C // VMRGOW V2, V3, V2
VMRGOW V0, V1, V0
VMRGOW V2, V3, V2
WORD $0x13A42F8C // VMRGEW V4, V5, V29
WORD $0x13C63F8C // VMRGEW V6, V7, V30
VMRGEW V4, V5, V29
VMRGEW V6, V7, V30
XXPERMDI VS32, VS34, $0, VS33
XXPERMDI VS32, VS34, $3, VS35
XXPERMDI VS59, VS60, $0, VS32
XXPERMDI VS59, VS60, $3, VS34
WORD $0x10842E8C // VMRGOW V4, V5, V4
WORD $0x10C63E8C // VMRGOW V6, V7, V6
VMRGOW V4, V5, V4
VMRGOW V6, V7, V6
WORD $0x13684F8C // VMRGEW V8, V9, V27
WORD $0x138A5F8C // VMRGEW V10, V11, V28
VMRGEW V8, V9, V27
VMRGEW V10, V11, V28
XXPERMDI VS36, VS38, $0, VS37
XXPERMDI VS36, VS38, $3, VS39
XXPERMDI VS61, VS62, $0, VS36
XXPERMDI VS61, VS62, $3, VS38
WORD $0x11084E8C // VMRGOW V8, V9, V8
WORD $0x114A5E8C // VMRGOW V10, V11, V10
VMRGOW V8, V9, V8
VMRGOW V10, V11, V10
WORD $0x13AC6F8C // VMRGEW V12, V13, V29
WORD $0x13CE7F8C // VMRGEW V14, V15, V30
VMRGEW V12, V13, V29
VMRGEW V14, V15, V30
XXPERMDI VS40, VS42, $0, VS41
XXPERMDI VS40, VS42, $3, VS43
XXPERMDI VS59, VS60, $0, VS40
XXPERMDI VS59, VS60, $3, VS42
WORD $0x118C6E8C // VMRGOW V12, V13, V12
WORD $0x11CE7E8C // VMRGOW V14, V15, V14
VMRGOW V12, V13, V12
VMRGOW V14, V15, V14
VSPLTISW $4, V27
VADDUWM V26, V27, V26
@@ -305,6 +337,11 @@ loop_vsx:
VADDUWM V8, V18, V8
VADDUWM V12, V19, V12
BE_XXBRW(V0)
BE_XXBRW(V4)
BE_XXBRW(V8)
BE_XXBRW(V12)
CMPU LEN, $64
BLT tail_vsx
@@ -333,6 +370,11 @@ loop_vsx:
VADDUWM V9, V18, V8
VADDUWM V13, V19, V12
BE_XXBRW(V0)
BE_XXBRW(V4)
BE_XXBRW(V8)
BE_XXBRW(V12)
CMPU LEN, $64
BLT tail_vsx
@@ -340,8 +382,8 @@ loop_vsx:
LXVW4X (INP)(R8), VS60
LXVW4X (INP)(R9), VS61
LXVW4X (INP)(R10), VS62
VXOR V27, V0, V27
VXOR V27, V0, V27
VXOR V28, V4, V28
VXOR V29, V8, V29
VXOR V30, V12, V30
@@ -360,6 +402,11 @@ loop_vsx:
VADDUWM V10, V18, V8
VADDUWM V14, V19, V12
BE_XXBRW(V0)
BE_XXBRW(V4)
BE_XXBRW(V8)
BE_XXBRW(V12)
CMPU LEN, $64
BLT tail_vsx
@@ -387,6 +434,11 @@ loop_vsx:
VADDUWM V11, V18, V8
VADDUWM V15, V19, V12
BE_XXBRW(V0)
BE_XXBRW(V4)
BE_XXBRW(V8)
BE_XXBRW(V12)
CMPU LEN, $64
BLT tail_vsx
@@ -414,9 +466,9 @@ loop_vsx:
done_vsx:
// Increment counter by number of 64 byte blocks
MOVD (CNT), R14
MOVWZ (CNT), R14
ADD BLOCKS, R14
MOVD R14, (CNT)
MOVWZ R14, (CNT)
RET
tail_vsx:
@@ -431,7 +483,7 @@ tail_vsx:
ADD $-1, R11, R12
ADD $-1, INP
ADD $-1, OUT
PCALIGN $16
looptail_vsx:
// Copying the result to OUT
// in bytes.
@@ -439,7 +491,7 @@ looptail_vsx:
MOVBZU 1(INP), TMP
XOR KEY, TMP, KEY
MOVBU KEY, 1(OUT)
BC 16, LT, looptail_vsx
BDNZ looptail_vsx
// Clear the stack values
STXVW4X VS48, (R11)(R0)
+1 -1
View File
@@ -5,7 +5,7 @@
// Package chacha20poly1305 implements the ChaCha20-Poly1305 AEAD and its
// extended nonce variant XChaCha20-Poly1305, as specified in RFC 8439 and
// draft-irtf-cfrg-xchacha-01.
package chacha20poly1305 // import "golang.org/x/crypto/chacha20poly1305"
package chacha20poly1305
import (
"crypto/cipher"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -234,7 +234,7 @@ func (b *Builder) AddASN1(tag asn1.Tag, f BuilderContinuation) {
// Identifiers with the low five bits set indicate high-tag-number format
// (two or more octets), which we don't support.
if tag&0x1f == 0x1f {
b.err = fmt.Errorf("cryptobyte: high-tag number identifier octects not supported: 0x%x", tag)
b.err = fmt.Errorf("cryptobyte: high-tag number identifier octets not supported: 0x%x", tag)
return
}
b.AddUint8(uint8(tag))
+1 -1
View File
@@ -4,7 +4,7 @@
// Package asn1 contains supporting types for parsing and building ASN.1
// messages with the cryptobyte package.
package asn1 // import "golang.org/x/crypto/cryptobyte/asn1"
package asn1
// Tag represents an ASN.1 identifier octet, consisting of a tag number
// (indicating a type) and class (such as context-specific or constructed).
+1 -1
View File
@@ -15,7 +15,7 @@
//
// See the documentation and examples for the Builder and String types to get
// started.
package cryptobyte // import "golang.org/x/crypto/cryptobyte"
package cryptobyte
// String represents a string of bytes. It provides methods for parsing
// fixed-length and length-prefixed values from it.
+35 -4
View File
@@ -6,9 +6,11 @@
// performs scalar multiplication on the elliptic curve known as Curve25519.
// See RFC 7748.
//
// Starting in Go 1.20, this package is a wrapper for the X25519 implementation
// This package is a wrapper for the X25519 implementation
// in the crypto/ecdh package.
package curve25519 // import "golang.org/x/crypto/curve25519"
package curve25519
import "crypto/ecdh"
// ScalarMult sets dst to the product scalar * point.
//
@@ -16,7 +18,13 @@ package curve25519 // import "golang.org/x/crypto/curve25519"
// zeroes, irrespective of the scalar. Instead, use the X25519 function, which
// will return an error.
func ScalarMult(dst, scalar, point *[32]byte) {
scalarMult(dst, scalar, point)
if _, err := x25519(dst, scalar[:], point[:]); err != nil {
// The only error condition for x25519 when the inputs are 32 bytes long
// is if the output would have been the all-zero value.
for i := range dst {
dst[i] = 0
}
}
}
// ScalarBaseMult sets dst to the product scalar * base where base is the
@@ -25,7 +33,12 @@ func ScalarMult(dst, scalar, point *[32]byte) {
// It is recommended to use the X25519 function with Basepoint instead, as
// copying into fixed size arrays can lead to unexpected bugs.
func ScalarBaseMult(dst, scalar *[32]byte) {
scalarBaseMult(dst, scalar)
curve := ecdh.X25519()
priv, err := curve.NewPrivateKey(scalar[:])
if err != nil {
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
}
copy(dst[:], priv.PublicKey().Bytes())
}
const (
@@ -57,3 +70,21 @@ func X25519(scalar, point []byte) ([]byte, error) {
var dst [32]byte
return x25519(&dst, scalar, point)
}
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
curve := ecdh.X25519()
pub, err := curve.NewPublicKey(point)
if err != nil {
return nil, err
}
priv, err := curve.NewPrivateKey(scalar)
if err != nil {
return nil, err
}
out, err := priv.ECDH(pub)
if err != nil {
return nil, err
}
copy(dst[:], out)
return dst[:], nil
}
-105
View File
@@ -1,105 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !go1.20
package curve25519
import (
"crypto/subtle"
"errors"
"strconv"
"golang.org/x/crypto/curve25519/internal/field"
)
func scalarMult(dst, scalar, point *[32]byte) {
var e [32]byte
copy(e[:], scalar[:])
e[0] &= 248
e[31] &= 127
e[31] |= 64
var x1, x2, z2, x3, z3, tmp0, tmp1 field.Element
x1.SetBytes(point[:])
x2.One()
x3.Set(&x1)
z3.One()
swap := 0
for pos := 254; pos >= 0; pos-- {
b := e[pos/8] >> uint(pos&7)
b &= 1
swap ^= int(b)
x2.Swap(&x3, swap)
z2.Swap(&z3, swap)
swap = int(b)
tmp0.Subtract(&x3, &z3)
tmp1.Subtract(&x2, &z2)
x2.Add(&x2, &z2)
z2.Add(&x3, &z3)
z3.Multiply(&tmp0, &x2)
z2.Multiply(&z2, &tmp1)
tmp0.Square(&tmp1)
tmp1.Square(&x2)
x3.Add(&z3, &z2)
z2.Subtract(&z3, &z2)
x2.Multiply(&tmp1, &tmp0)
tmp1.Subtract(&tmp1, &tmp0)
z2.Square(&z2)
z3.Mult32(&tmp1, 121666)
x3.Square(&x3)
tmp0.Add(&tmp0, &z3)
z3.Multiply(&x1, &z2)
z2.Multiply(&tmp1, &tmp0)
}
x2.Swap(&x3, swap)
z2.Swap(&z3, swap)
z2.Invert(&z2)
x2.Multiply(&x2, &z2)
copy(dst[:], x2.Bytes())
}
func scalarBaseMult(dst, scalar *[32]byte) {
checkBasepoint()
scalarMult(dst, scalar, &basePoint)
}
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
var in [32]byte
if l := len(scalar); l != 32 {
return nil, errors.New("bad scalar length: " + strconv.Itoa(l) + ", expected 32")
}
if l := len(point); l != 32 {
return nil, errors.New("bad point length: " + strconv.Itoa(l) + ", expected 32")
}
copy(in[:], scalar)
if &point[0] == &Basepoint[0] {
scalarBaseMult(dst, &in)
} else {
var base, zero [32]byte
copy(base[:], point)
scalarMult(dst, &in, &base)
if subtle.ConstantTimeCompare(dst[:], zero[:]) == 1 {
return nil, errors.New("bad input point: low order point")
}
}
return dst[:], nil
}
func checkBasepoint() {
if subtle.ConstantTimeCompare(Basepoint, []byte{
0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}) != 1 {
panic("curve25519: global Basepoint value was modified")
}
}
-46
View File
@@ -1,46 +0,0 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.20
package curve25519
import "crypto/ecdh"
func x25519(dst *[32]byte, scalar, point []byte) ([]byte, error) {
curve := ecdh.X25519()
pub, err := curve.NewPublicKey(point)
if err != nil {
return nil, err
}
priv, err := curve.NewPrivateKey(scalar)
if err != nil {
return nil, err
}
out, err := priv.ECDH(pub)
if err != nil {
return nil, err
}
copy(dst[:], out)
return dst[:], nil
}
func scalarMult(dst, scalar, point *[32]byte) {
if _, err := x25519(dst, scalar[:], point[:]); err != nil {
// The only error condition for x25519 when the inputs are 32 bytes long
// is if the output would have been the all-zero value.
for i := range dst {
dst[i] = 0
}
}
}
func scalarBaseMult(dst, scalar *[32]byte) {
curve := ecdh.X25519()
priv, err := curve.NewPrivateKey(scalar[:])
if err != nil {
panic("curve25519: internal error: scalarBaseMult was not 32 bytes")
}
copy(dst[:], priv.PublicKey().Bytes())
}
-7
View File
@@ -1,7 +0,0 @@
This package is kept in sync with crypto/ed25519/internal/edwards25519/field in
the standard library.
If there are any changes in the standard library that need to be synced to this
package, run sync.sh. It will not overwrite any local changes made since the
previous sync, so it's ok to land changes in this package first, and then sync
to the standard library later.
-416
View File
@@ -1,416 +0,0 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package field implements fast arithmetic modulo 2^255-19.
package field
import (
"crypto/subtle"
"encoding/binary"
"math/bits"
)
// Element represents an element of the field GF(2^255-19). Note that this
// is not a cryptographically secure group, and should only be used to interact
// with edwards25519.Point coordinates.
//
// This type works similarly to math/big.Int, and all arguments and receivers
// are allowed to alias.
//
// The zero value is a valid zero element.
type Element struct {
// An element t represents the integer
// t.l0 + t.l1*2^51 + t.l2*2^102 + t.l3*2^153 + t.l4*2^204
//
// Between operations, all limbs are expected to be lower than 2^52.
l0 uint64
l1 uint64
l2 uint64
l3 uint64
l4 uint64
}
const maskLow51Bits uint64 = (1 << 51) - 1
var feZero = &Element{0, 0, 0, 0, 0}
// Zero sets v = 0, and returns v.
func (v *Element) Zero() *Element {
*v = *feZero
return v
}
var feOne = &Element{1, 0, 0, 0, 0}
// One sets v = 1, and returns v.
func (v *Element) One() *Element {
*v = *feOne
return v
}
// reduce reduces v modulo 2^255 - 19 and returns it.
func (v *Element) reduce() *Element {
v.carryPropagate()
// After the light reduction we now have a field element representation
// v < 2^255 + 2^13 * 19, but need v < 2^255 - 19.
// If v >= 2^255 - 19, then v + 19 >= 2^255, which would overflow 2^255 - 1,
// generating a carry. That is, c will be 0 if v < 2^255 - 19, and 1 otherwise.
c := (v.l0 + 19) >> 51
c = (v.l1 + c) >> 51
c = (v.l2 + c) >> 51
c = (v.l3 + c) >> 51
c = (v.l4 + c) >> 51
// If v < 2^255 - 19 and c = 0, this will be a no-op. Otherwise, it's
// effectively applying the reduction identity to the carry.
v.l0 += 19 * c
v.l1 += v.l0 >> 51
v.l0 = v.l0 & maskLow51Bits
v.l2 += v.l1 >> 51
v.l1 = v.l1 & maskLow51Bits
v.l3 += v.l2 >> 51
v.l2 = v.l2 & maskLow51Bits
v.l4 += v.l3 >> 51
v.l3 = v.l3 & maskLow51Bits
// no additional carry
v.l4 = v.l4 & maskLow51Bits
return v
}
// Add sets v = a + b, and returns v.
func (v *Element) Add(a, b *Element) *Element {
v.l0 = a.l0 + b.l0
v.l1 = a.l1 + b.l1
v.l2 = a.l2 + b.l2
v.l3 = a.l3 + b.l3
v.l4 = a.l4 + b.l4
// Using the generic implementation here is actually faster than the
// assembly. Probably because the body of this function is so simple that
// the compiler can figure out better optimizations by inlining the carry
// propagation. TODO
return v.carryPropagateGeneric()
}
// Subtract sets v = a - b, and returns v.
func (v *Element) Subtract(a, b *Element) *Element {
// We first add 2 * p, to guarantee the subtraction won't underflow, and
// then subtract b (which can be up to 2^255 + 2^13 * 19).
v.l0 = (a.l0 + 0xFFFFFFFFFFFDA) - b.l0
v.l1 = (a.l1 + 0xFFFFFFFFFFFFE) - b.l1
v.l2 = (a.l2 + 0xFFFFFFFFFFFFE) - b.l2
v.l3 = (a.l3 + 0xFFFFFFFFFFFFE) - b.l3
v.l4 = (a.l4 + 0xFFFFFFFFFFFFE) - b.l4
return v.carryPropagate()
}
// Negate sets v = -a, and returns v.
func (v *Element) Negate(a *Element) *Element {
return v.Subtract(feZero, a)
}
// Invert sets v = 1/z mod p, and returns v.
//
// If z == 0, Invert returns v = 0.
func (v *Element) Invert(z *Element) *Element {
// Inversion is implemented as exponentiation with exponent p 2. It uses the
// same sequence of 255 squarings and 11 multiplications as [Curve25519].
var z2, z9, z11, z2_5_0, z2_10_0, z2_20_0, z2_50_0, z2_100_0, t Element
z2.Square(z) // 2
t.Square(&z2) // 4
t.Square(&t) // 8
z9.Multiply(&t, z) // 9
z11.Multiply(&z9, &z2) // 11
t.Square(&z11) // 22
z2_5_0.Multiply(&t, &z9) // 31 = 2^5 - 2^0
t.Square(&z2_5_0) // 2^6 - 2^1
for i := 0; i < 4; i++ {
t.Square(&t) // 2^10 - 2^5
}
z2_10_0.Multiply(&t, &z2_5_0) // 2^10 - 2^0
t.Square(&z2_10_0) // 2^11 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^20 - 2^10
}
z2_20_0.Multiply(&t, &z2_10_0) // 2^20 - 2^0
t.Square(&z2_20_0) // 2^21 - 2^1
for i := 0; i < 19; i++ {
t.Square(&t) // 2^40 - 2^20
}
t.Multiply(&t, &z2_20_0) // 2^40 - 2^0
t.Square(&t) // 2^41 - 2^1
for i := 0; i < 9; i++ {
t.Square(&t) // 2^50 - 2^10
}
z2_50_0.Multiply(&t, &z2_10_0) // 2^50 - 2^0
t.Square(&z2_50_0) // 2^51 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^100 - 2^50
}
z2_100_0.Multiply(&t, &z2_50_0) // 2^100 - 2^0
t.Square(&z2_100_0) // 2^101 - 2^1
for i := 0; i < 99; i++ {
t.Square(&t) // 2^200 - 2^100
}
t.Multiply(&t, &z2_100_0) // 2^200 - 2^0
t.Square(&t) // 2^201 - 2^1
for i := 0; i < 49; i++ {
t.Square(&t) // 2^250 - 2^50
}
t.Multiply(&t, &z2_50_0) // 2^250 - 2^0
t.Square(&t) // 2^251 - 2^1
t.Square(&t) // 2^252 - 2^2
t.Square(&t) // 2^253 - 2^3
t.Square(&t) // 2^254 - 2^4
t.Square(&t) // 2^255 - 2^5
return v.Multiply(&t, &z11) // 2^255 - 21
}
// Set sets v = a, and returns v.
func (v *Element) Set(a *Element) *Element {
*v = *a
return v
}
// SetBytes sets v to x, which must be a 32-byte little-endian encoding.
//
// Consistent with RFC 7748, the most significant bit (the high bit of the
// last byte) is ignored, and non-canonical values (2^255-19 through 2^255-1)
// are accepted. Note that this is laxer than specified by RFC 8032.
func (v *Element) SetBytes(x []byte) *Element {
if len(x) != 32 {
panic("edwards25519: invalid field element input size")
}
// Bits 0:51 (bytes 0:8, bits 0:64, shift 0, mask 51).
v.l0 = binary.LittleEndian.Uint64(x[0:8])
v.l0 &= maskLow51Bits
// Bits 51:102 (bytes 6:14, bits 48:112, shift 3, mask 51).
v.l1 = binary.LittleEndian.Uint64(x[6:14]) >> 3
v.l1 &= maskLow51Bits
// Bits 102:153 (bytes 12:20, bits 96:160, shift 6, mask 51).
v.l2 = binary.LittleEndian.Uint64(x[12:20]) >> 6
v.l2 &= maskLow51Bits
// Bits 153:204 (bytes 19:27, bits 152:216, shift 1, mask 51).
v.l3 = binary.LittleEndian.Uint64(x[19:27]) >> 1
v.l3 &= maskLow51Bits
// Bits 204:251 (bytes 24:32, bits 192:256, shift 12, mask 51).
// Note: not bytes 25:33, shift 4, to avoid overread.
v.l4 = binary.LittleEndian.Uint64(x[24:32]) >> 12
v.l4 &= maskLow51Bits
return v
}
// Bytes returns the canonical 32-byte little-endian encoding of v.
func (v *Element) Bytes() []byte {
// This function is outlined to make the allocations inline in the caller
// rather than happen on the heap.
var out [32]byte
return v.bytes(&out)
}
func (v *Element) bytes(out *[32]byte) []byte {
t := *v
t.reduce()
var buf [8]byte
for i, l := range [5]uint64{t.l0, t.l1, t.l2, t.l3, t.l4} {
bitsOffset := i * 51
binary.LittleEndian.PutUint64(buf[:], l<<uint(bitsOffset%8))
for i, bb := range buf {
off := bitsOffset/8 + i
if off >= len(out) {
break
}
out[off] |= bb
}
}
return out[:]
}
// Equal returns 1 if v and u are equal, and 0 otherwise.
func (v *Element) Equal(u *Element) int {
sa, sv := u.Bytes(), v.Bytes()
return subtle.ConstantTimeCompare(sa, sv)
}
// mask64Bits returns 0xffffffff if cond is 1, and 0 otherwise.
func mask64Bits(cond int) uint64 { return ^(uint64(cond) - 1) }
// Select sets v to a if cond == 1, and to b if cond == 0.
func (v *Element) Select(a, b *Element, cond int) *Element {
m := mask64Bits(cond)
v.l0 = (m & a.l0) | (^m & b.l0)
v.l1 = (m & a.l1) | (^m & b.l1)
v.l2 = (m & a.l2) | (^m & b.l2)
v.l3 = (m & a.l3) | (^m & b.l3)
v.l4 = (m & a.l4) | (^m & b.l4)
return v
}
// Swap swaps v and u if cond == 1 or leaves them unchanged if cond == 0, and returns v.
func (v *Element) Swap(u *Element, cond int) {
m := mask64Bits(cond)
t := m & (v.l0 ^ u.l0)
v.l0 ^= t
u.l0 ^= t
t = m & (v.l1 ^ u.l1)
v.l1 ^= t
u.l1 ^= t
t = m & (v.l2 ^ u.l2)
v.l2 ^= t
u.l2 ^= t
t = m & (v.l3 ^ u.l3)
v.l3 ^= t
u.l3 ^= t
t = m & (v.l4 ^ u.l4)
v.l4 ^= t
u.l4 ^= t
}
// IsNegative returns 1 if v is negative, and 0 otherwise.
func (v *Element) IsNegative() int {
return int(v.Bytes()[0] & 1)
}
// Absolute sets v to |u|, and returns v.
func (v *Element) Absolute(u *Element) *Element {
return v.Select(new(Element).Negate(u), u, u.IsNegative())
}
// Multiply sets v = x * y, and returns v.
func (v *Element) Multiply(x, y *Element) *Element {
feMul(v, x, y)
return v
}
// Square sets v = x * x, and returns v.
func (v *Element) Square(x *Element) *Element {
feSquare(v, x)
return v
}
// Mult32 sets v = x * y, and returns v.
func (v *Element) Mult32(x *Element, y uint32) *Element {
x0lo, x0hi := mul51(x.l0, y)
x1lo, x1hi := mul51(x.l1, y)
x2lo, x2hi := mul51(x.l2, y)
x3lo, x3hi := mul51(x.l3, y)
x4lo, x4hi := mul51(x.l4, y)
v.l0 = x0lo + 19*x4hi // carried over per the reduction identity
v.l1 = x1lo + x0hi
v.l2 = x2lo + x1hi
v.l3 = x3lo + x2hi
v.l4 = x4lo + x3hi
// The hi portions are going to be only 32 bits, plus any previous excess,
// so we can skip the carry propagation.
return v
}
// mul51 returns lo + hi * 2⁵¹ = a * b.
func mul51(a uint64, b uint32) (lo uint64, hi uint64) {
mh, ml := bits.Mul64(a, uint64(b))
lo = ml & maskLow51Bits
hi = (mh << 13) | (ml >> 51)
return
}
// Pow22523 set v = x^((p-5)/8), and returns v. (p-5)/8 is 2^252-3.
func (v *Element) Pow22523(x *Element) *Element {
var t0, t1, t2 Element
t0.Square(x) // x^2
t1.Square(&t0) // x^4
t1.Square(&t1) // x^8
t1.Multiply(x, &t1) // x^9
t0.Multiply(&t0, &t1) // x^11
t0.Square(&t0) // x^22
t0.Multiply(&t1, &t0) // x^31
t1.Square(&t0) // x^62
for i := 1; i < 5; i++ { // x^992
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // x^1023 -> 1023 = 2^10 - 1
t1.Square(&t0) // 2^11 - 2
for i := 1; i < 10; i++ { // 2^20 - 2^10
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^20 - 1
t2.Square(&t1) // 2^21 - 2
for i := 1; i < 20; i++ { // 2^40 - 2^20
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^40 - 1
t1.Square(&t1) // 2^41 - 2
for i := 1; i < 10; i++ { // 2^50 - 2^10
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^50 - 1
t1.Square(&t0) // 2^51 - 2
for i := 1; i < 50; i++ { // 2^100 - 2^50
t1.Square(&t1)
}
t1.Multiply(&t1, &t0) // 2^100 - 1
t2.Square(&t1) // 2^101 - 2
for i := 1; i < 100; i++ { // 2^200 - 2^100
t2.Square(&t2)
}
t1.Multiply(&t2, &t1) // 2^200 - 1
t1.Square(&t1) // 2^201 - 2
for i := 1; i < 50; i++ { // 2^250 - 2^50
t1.Square(&t1)
}
t0.Multiply(&t1, &t0) // 2^250 - 1
t0.Square(&t0) // 2^251 - 2
t0.Square(&t0) // 2^252 - 4
return v.Multiply(&t0, x) // 2^252 - 3 -> x^(2^252-3)
}
// sqrtM1 is 2^((p-1)/4), which squared is equal to -1 by Euler's Criterion.
var sqrtM1 = &Element{1718705420411056, 234908883556509,
2233514472574048, 2117202627021982, 765476049583133}
// SqrtRatio sets r to the non-negative square root of the ratio of u and v.
//
// If u/v is square, SqrtRatio returns r and 1. If u/v is not square, SqrtRatio
// sets r according to Section 4.3 of draft-irtf-cfrg-ristretto255-decaf448-00,
// and returns r and 0.
func (r *Element) SqrtRatio(u, v *Element) (rr *Element, wasSquare int) {
var a, b Element
// r = (u * v3) * (u * v7)^((p-5)/8)
v2 := a.Square(v)
uv3 := b.Multiply(u, b.Multiply(v2, v))
uv7 := a.Multiply(uv3, a.Square(v2))
r.Multiply(uv3, r.Pow22523(uv7))
check := a.Multiply(v, a.Square(r)) // check = v * r^2
uNeg := b.Negate(u)
correctSignSqrt := check.Equal(u)
flippedSignSqrt := check.Equal(uNeg)
flippedSignSqrtI := check.Equal(uNeg.Multiply(uNeg, sqrtM1))
rPrime := b.Multiply(r, sqrtM1) // r_prime = SQRT_M1 * r
// r = CT_SELECT(r_prime IF flipped_sign_sqrt | flipped_sign_sqrt_i ELSE r)
r.Select(rPrime, r, flippedSignSqrt|flippedSignSqrtI)
r.Absolute(r) // Choose the nonnegative square root.
return r, correctSignSqrt | flippedSignSqrt
}
-15
View File
@@ -1,15 +0,0 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
//go:build amd64 && gc && !purego
package field
// feMul sets out = a * b. It works like feMulGeneric.
//
//go:noescape
func feMul(out *Element, a *Element, b *Element)
// feSquare sets out = a * a. It works like feSquareGeneric.
//
//go:noescape
func feSquare(out *Element, a *Element)
-378
View File
@@ -1,378 +0,0 @@
// Code generated by command: go run fe_amd64_asm.go -out ../fe_amd64.s -stubs ../fe_amd64.go -pkg field. DO NOT EDIT.
//go:build amd64 && gc && !purego
#include "textflag.h"
// func feMul(out *Element, a *Element, b *Element)
TEXT ·feMul(SB), NOSPLIT, $0-24
MOVQ a+8(FP), CX
MOVQ b+16(FP), BX
// r0 = a0×b0
MOVQ (CX), AX
MULQ (BX)
MOVQ AX, DI
MOVQ DX, SI
// r0 += 19×a1×b4
MOVQ 8(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a2×b3
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a3×b2
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, DI
ADCQ DX, SI
// r0 += 19×a4×b1
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 8(BX)
ADDQ AX, DI
ADCQ DX, SI
// r1 = a0×b1
MOVQ (CX), AX
MULQ 8(BX)
MOVQ AX, R9
MOVQ DX, R8
// r1 += a1×b0
MOVQ 8(CX), AX
MULQ (BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a2×b4
MOVQ 16(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a3×b3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R9
ADCQ DX, R8
// r1 += 19×a4×b2
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 16(BX)
ADDQ AX, R9
ADCQ DX, R8
// r2 = a0×b2
MOVQ (CX), AX
MULQ 16(BX)
MOVQ AX, R11
MOVQ DX, R10
// r2 += a1×b1
MOVQ 8(CX), AX
MULQ 8(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += a2×b0
MOVQ 16(CX), AX
MULQ (BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a3×b4
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R11
ADCQ DX, R10
// r2 += 19×a4×b3
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(BX)
ADDQ AX, R11
ADCQ DX, R10
// r3 = a0×b3
MOVQ (CX), AX
MULQ 24(BX)
MOVQ AX, R13
MOVQ DX, R12
// r3 += a1×b2
MOVQ 8(CX), AX
MULQ 16(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a2×b1
MOVQ 16(CX), AX
MULQ 8(BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += a3×b0
MOVQ 24(CX), AX
MULQ (BX)
ADDQ AX, R13
ADCQ DX, R12
// r3 += 19×a4×b4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(BX)
ADDQ AX, R13
ADCQ DX, R12
// r4 = a0×b4
MOVQ (CX), AX
MULQ 32(BX)
MOVQ AX, R15
MOVQ DX, R14
// r4 += a1×b3
MOVQ 8(CX), AX
MULQ 24(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a2×b2
MOVQ 16(CX), AX
MULQ 16(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a3×b1
MOVQ 24(CX), AX
MULQ 8(BX)
ADDQ AX, R15
ADCQ DX, R14
// r4 += a4×b0
MOVQ 32(CX), AX
MULQ (BX)
ADDQ AX, R15
ADCQ DX, R14
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, DI, SI
SHLQ $0x0d, R9, R8
SHLQ $0x0d, R11, R10
SHLQ $0x0d, R13, R12
SHLQ $0x0d, R15, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Second reduction chain (carryPropagate)
MOVQ DI, SI
SHRQ $0x33, SI
MOVQ R9, R8
SHRQ $0x33, R8
MOVQ R11, R10
SHRQ $0x33, R10
MOVQ R13, R12
SHRQ $0x33, R12
MOVQ R15, R14
SHRQ $0x33, R14
ANDQ AX, DI
IMUL3Q $0x13, R14, R14
ADDQ R14, DI
ANDQ AX, R9
ADDQ SI, R9
ANDQ AX, R11
ADDQ R8, R11
ANDQ AX, R13
ADDQ R10, R13
ANDQ AX, R15
ADDQ R12, R15
// Store output
MOVQ out+0(FP), AX
MOVQ DI, (AX)
MOVQ R9, 8(AX)
MOVQ R11, 16(AX)
MOVQ R13, 24(AX)
MOVQ R15, 32(AX)
RET
// func feSquare(out *Element, a *Element)
TEXT ·feSquare(SB), NOSPLIT, $0-16
MOVQ a+8(FP), CX
// r0 = l0×l0
MOVQ (CX), AX
MULQ (CX)
MOVQ AX, SI
MOVQ DX, BX
// r0 += 38×l1×l4
MOVQ 8(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, SI
ADCQ DX, BX
// r0 += 38×l2×l3
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 24(CX)
ADDQ AX, SI
ADCQ DX, BX
// r1 = 2×l0×l1
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 8(CX)
MOVQ AX, R8
MOVQ DX, DI
// r1 += 38×l2×l4
MOVQ 16(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R8
ADCQ DX, DI
// r1 += 19×l3×l3
MOVQ 24(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 24(CX)
ADDQ AX, R8
ADCQ DX, DI
// r2 = 2×l0×l2
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 16(CX)
MOVQ AX, R10
MOVQ DX, R9
// r2 += l1×l1
MOVQ 8(CX), AX
MULQ 8(CX)
ADDQ AX, R10
ADCQ DX, R9
// r2 += 38×l3×l4
MOVQ 24(CX), AX
IMUL3Q $0x26, AX, AX
MULQ 32(CX)
ADDQ AX, R10
ADCQ DX, R9
// r3 = 2×l0×l3
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 24(CX)
MOVQ AX, R12
MOVQ DX, R11
// r3 += 2×l1×l2
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 16(CX)
ADDQ AX, R12
ADCQ DX, R11
// r3 += 19×l4×l4
MOVQ 32(CX), AX
IMUL3Q $0x13, AX, AX
MULQ 32(CX)
ADDQ AX, R12
ADCQ DX, R11
// r4 = 2×l0×l4
MOVQ (CX), AX
SHLQ $0x01, AX
MULQ 32(CX)
MOVQ AX, R14
MOVQ DX, R13
// r4 += 2×l1×l3
MOVQ 8(CX), AX
IMUL3Q $0x02, AX, AX
MULQ 24(CX)
ADDQ AX, R14
ADCQ DX, R13
// r4 += l2×l2
MOVQ 16(CX), AX
MULQ 16(CX)
ADDQ AX, R14
ADCQ DX, R13
// First reduction chain
MOVQ $0x0007ffffffffffff, AX
SHLQ $0x0d, SI, BX
SHLQ $0x0d, R8, DI
SHLQ $0x0d, R10, R9
SHLQ $0x0d, R12, R11
SHLQ $0x0d, R14, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Second reduction chain (carryPropagate)
MOVQ SI, BX
SHRQ $0x33, BX
MOVQ R8, DI
SHRQ $0x33, DI
MOVQ R10, R9
SHRQ $0x33, R9
MOVQ R12, R11
SHRQ $0x33, R11
MOVQ R14, R13
SHRQ $0x33, R13
ANDQ AX, SI
IMUL3Q $0x13, R13, R13
ADDQ R13, SI
ANDQ AX, R8
ADDQ BX, R8
ANDQ AX, R10
ADDQ DI, R10
ANDQ AX, R12
ADDQ R9, R12
ANDQ AX, R14
ADDQ R11, R14
// Store output
MOVQ out+0(FP), AX
MOVQ SI, (AX)
MOVQ R8, 8(AX)
MOVQ R10, 16(AX)
MOVQ R12, 24(AX)
MOVQ R14, 32(AX)
RET
-11
View File
@@ -1,11 +0,0 @@
// Copyright (c) 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !amd64 || !gc || purego
package field
func feMul(v, x, y *Element) { feMulGeneric(v, x, y) }
func feSquare(v, x *Element) { feSquareGeneric(v, x) }
-15
View File
@@ -1,15 +0,0 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
package field
//go:noescape
func carryPropagate(v *Element)
func (v *Element) carryPropagate() *Element {
carryPropagate(v)
return v
}
-42
View File
@@ -1,42 +0,0 @@
// Copyright (c) 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build arm64 && gc && !purego
#include "textflag.h"
// carryPropagate works exactly like carryPropagateGeneric and uses the
// same AND, ADD, and LSR+MADD instructions emitted by the compiler, but
// avoids loading R0-R4 twice and uses LDP and STP.
//
// See https://golang.org/issues/43145 for the main compiler issue.
//
// func carryPropagate(v *Element)
TEXT ·carryPropagate(SB),NOFRAME|NOSPLIT,$0-8
MOVD v+0(FP), R20
LDP 0(R20), (R0, R1)
LDP 16(R20), (R2, R3)
MOVD 32(R20), R4
AND $0x7ffffffffffff, R0, R10
AND $0x7ffffffffffff, R1, R11
AND $0x7ffffffffffff, R2, R12
AND $0x7ffffffffffff, R3, R13
AND $0x7ffffffffffff, R4, R14
ADD R0>>51, R11, R11
ADD R1>>51, R12, R12
ADD R2>>51, R13, R13
ADD R3>>51, R14, R14
// R4>>51 * 19 + R10 -> R10
LSR $51, R4, R21
MOVD $19, R22
MADD R22, R10, R21, R10
STP (R10, R11), 0(R20)
STP (R12, R13), 16(R20)
MOVD R14, 32(R20)
RET
-11
View File
@@ -1,11 +0,0 @@
// Copyright (c) 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !arm64 || !gc || purego
package field
func (v *Element) carryPropagate() *Element {
return v.carryPropagateGeneric()
}
-264
View File
@@ -1,264 +0,0 @@
// Copyright (c) 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package field
import "math/bits"
// uint128 holds a 128-bit number as two 64-bit limbs, for use with the
// bits.Mul64 and bits.Add64 intrinsics.
type uint128 struct {
lo, hi uint64
}
// mul64 returns a * b.
func mul64(a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
return uint128{lo, hi}
}
// addMul64 returns v + a * b.
func addMul64(v uint128, a, b uint64) uint128 {
hi, lo := bits.Mul64(a, b)
lo, c := bits.Add64(lo, v.lo, 0)
hi, _ = bits.Add64(hi, v.hi, c)
return uint128{lo, hi}
}
// shiftRightBy51 returns a >> 51. a is assumed to be at most 115 bits.
func shiftRightBy51(a uint128) uint64 {
return (a.hi << (64 - 51)) | (a.lo >> 51)
}
func feMulGeneric(v, a, b *Element) {
a0 := a.l0
a1 := a.l1
a2 := a.l2
a3 := a.l3
a4 := a.l4
b0 := b.l0
b1 := b.l1
b2 := b.l2
b3 := b.l3
b4 := b.l4
// Limb multiplication works like pen-and-paper columnar multiplication, but
// with 51-bit limbs instead of digits.
//
// a4 a3 a2 a1 a0 x
// b4 b3 b2 b1 b0 =
// ------------------------
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a4b1 a3b1 a2b1 a1b1 a0b1 +
// a4b2 a3b2 a2b2 a1b2 a0b2 +
// a4b3 a3b3 a2b3 a1b3 a0b3 +
// a4b4 a3b4 a2b4 a1b4 a0b4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// We can then use the reduction identity (a * 2²⁵⁵ + b = a * 19 + b) to
// reduce the limbs that would overflow 255 bits. r5 * 2²⁵⁵ becomes 19 * r5,
// r6 * 2³⁰⁶ becomes 19 * r6 * 2⁵¹, etc.
//
// Reduction can be carried out simultaneously to multiplication. For
// example, we do not compute r5: whenever the result of a multiplication
// belongs to r5, like a1b4, we multiply it by 19 and add the result to r0.
//
// a4b0 a3b0 a2b0 a1b0 a0b0 +
// a3b1 a2b1 a1b1 a0b1 19×a4b1 +
// a2b2 a1b2 a0b2 19×a4b2 19×a3b2 +
// a1b3 a0b3 19×a4b3 19×a3b3 19×a2b3 +
// a0b4 19×a4b4 19×a3b4 19×a2b4 19×a1b4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// Finally we add up the columns into wide, overlapping limbs.
a1_19 := a1 * 19
a2_19 := a2 * 19
a3_19 := a3 * 19
a4_19 := a4 * 19
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
r0 := mul64(a0, b0)
r0 = addMul64(r0, a1_19, b4)
r0 = addMul64(r0, a2_19, b3)
r0 = addMul64(r0, a3_19, b2)
r0 = addMul64(r0, a4_19, b1)
// r1 = a0×b1 + a1×b0 + 19×(a2×b4 + a3×b3 + a4×b2)
r1 := mul64(a0, b1)
r1 = addMul64(r1, a1, b0)
r1 = addMul64(r1, a2_19, b4)
r1 = addMul64(r1, a3_19, b3)
r1 = addMul64(r1, a4_19, b2)
// r2 = a0×b2 + a1×b1 + a2×b0 + 19×(a3×b4 + a4×b3)
r2 := mul64(a0, b2)
r2 = addMul64(r2, a1, b1)
r2 = addMul64(r2, a2, b0)
r2 = addMul64(r2, a3_19, b4)
r2 = addMul64(r2, a4_19, b3)
// r3 = a0×b3 + a1×b2 + a2×b1 + a3×b0 + 19×a4×b4
r3 := mul64(a0, b3)
r3 = addMul64(r3, a1, b2)
r3 = addMul64(r3, a2, b1)
r3 = addMul64(r3, a3, b0)
r3 = addMul64(r3, a4_19, b4)
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
r4 := mul64(a0, b4)
r4 = addMul64(r4, a1, b3)
r4 = addMul64(r4, a2, b2)
r4 = addMul64(r4, a3, b1)
r4 = addMul64(r4, a4, b0)
// After the multiplication, we need to reduce (carry) the five coefficients
// to obtain a result with limbs that are at most slightly larger than 2⁵¹,
// to respect the Element invariant.
//
// Overall, the reduction works the same as carryPropagate, except with
// wider inputs: we take the carry for each coefficient by shifting it right
// by 51, and add it to the limb above it. The top carry is multiplied by 19
// according to the reduction identity and added to the lowest limb.
//
// The largest coefficient (r0) will be at most 111 bits, which guarantees
// that all carries are at most 111 - 51 = 60 bits, which fits in a uint64.
//
// r0 = a0×b0 + 19×(a1×b4 + a2×b3 + a3×b2 + a4×b1)
// r0 < 2⁵²×2⁵² + 19×(2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵² + 2⁵²×2⁵²)
// r0 < (1 + 19 × 4) × 2⁵² × 2⁵²
// r0 < 2⁷ × 2⁵² × 2⁵²
// r0 < 2¹¹¹
//
// Moreover, the top coefficient (r4) is at most 107 bits, so c4 is at most
// 56 bits, and c4 * 19 is at most 61 bits, which again fits in a uint64 and
// allows us to easily apply the reduction identity.
//
// r4 = a0×b4 + a1×b3 + a2×b2 + a3×b1 + a4×b0
// r4 < 5 × 2⁵² × 2⁵²
// r4 < 2¹⁰⁷
//
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
// Now all coefficients fit into 64-bit registers but are still too large to
// be passed around as a Element. We therefore do one last carry chain,
// where the carries will be small enough to fit in the wiggle room above 2⁵¹.
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
func feSquareGeneric(v, a *Element) {
l0 := a.l0
l1 := a.l1
l2 := a.l2
l3 := a.l3
l4 := a.l4
// Squaring works precisely like multiplication above, but thanks to its
// symmetry we get to group a few terms together.
//
// l4 l3 l2 l1 l0 x
// l4 l3 l2 l1 l0 =
// ------------------------
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l4l1 l3l1 l2l1 l1l1 l0l1 +
// l4l2 l3l2 l2l2 l1l2 l0l2 +
// l4l3 l3l3 l2l3 l1l3 l0l3 +
// l4l4 l3l4 l2l4 l1l4 l0l4 =
// ----------------------------------------------
// r8 r7 r6 r5 r4 r3 r2 r1 r0
//
// l4l0 l3l0 l2l0 l1l0 l0l0 +
// l3l1 l2l1 l1l1 l0l1 19×l4l1 +
// l2l2 l1l2 l0l2 19×l4l2 19×l3l2 +
// l1l3 l0l3 19×l4l3 19×l3l3 19×l2l3 +
// l0l4 19×l4l4 19×l3l4 19×l2l4 19×l1l4 =
// --------------------------------------
// r4 r3 r2 r1 r0
//
// With precomputed 2×, 19×, and 2×19× terms, we can compute each limb with
// only three Mul64 and four Add64, instead of five and eight.
l0_2 := l0 * 2
l1_2 := l1 * 2
l1_38 := l1 * 38
l2_38 := l2 * 38
l3_38 := l3 * 38
l3_19 := l3 * 19
l4_19 := l4 * 19
// r0 = l0×l0 + 19×(l1×l4 + l2×l3 + l3×l2 + l4×l1) = l0×l0 + 19×2×(l1×l4 + l2×l3)
r0 := mul64(l0, l0)
r0 = addMul64(r0, l1_38, l4)
r0 = addMul64(r0, l2_38, l3)
// r1 = l0×l1 + l1×l0 + 19×(l2×l4 + l3×l3 + l4×l2) = 2×l0×l1 + 19×2×l2×l4 + 19×l3×l3
r1 := mul64(l0_2, l1)
r1 = addMul64(r1, l2_38, l4)
r1 = addMul64(r1, l3_19, l3)
// r2 = l0×l2 + l1×l1 + l2×l0 + 19×(l3×l4 + l4×l3) = 2×l0×l2 + l1×l1 + 19×2×l3×l4
r2 := mul64(l0_2, l2)
r2 = addMul64(r2, l1, l1)
r2 = addMul64(r2, l3_38, l4)
// r3 = l0×l3 + l1×l2 + l2×l1 + l3×l0 + 19×l4×l4 = 2×l0×l3 + 2×l1×l2 + 19×l4×l4
r3 := mul64(l0_2, l3)
r3 = addMul64(r3, l1_2, l2)
r3 = addMul64(r3, l4_19, l4)
// r4 = l0×l4 + l1×l3 + l2×l2 + l3×l1 + l4×l0 = 2×l0×l4 + 2×l1×l3 + l2×l2
r4 := mul64(l0_2, l4)
r4 = addMul64(r4, l1_2, l3)
r4 = addMul64(r4, l2, l2)
c0 := shiftRightBy51(r0)
c1 := shiftRightBy51(r1)
c2 := shiftRightBy51(r2)
c3 := shiftRightBy51(r3)
c4 := shiftRightBy51(r4)
rr0 := r0.lo&maskLow51Bits + c4*19
rr1 := r1.lo&maskLow51Bits + c0
rr2 := r2.lo&maskLow51Bits + c1
rr3 := r3.lo&maskLow51Bits + c2
rr4 := r4.lo&maskLow51Bits + c3
*v = Element{rr0, rr1, rr2, rr3, rr4}
v.carryPropagate()
}
// carryPropagateGeneric brings the limbs below 52 bits by applying the reduction
// identity (a * 2²⁵⁵ + b = a * 19 + b) to the l4 carry. TODO inline
func (v *Element) carryPropagateGeneric() *Element {
c0 := v.l0 >> 51
c1 := v.l1 >> 51
c2 := v.l2 >> 51
c3 := v.l3 >> 51
c4 := v.l4 >> 51
v.l0 = v.l0&maskLow51Bits + c4*19
v.l1 = v.l1&maskLow51Bits + c0
v.l2 = v.l2&maskLow51Bits + c1
v.l3 = v.l3&maskLow51Bits + c2
v.l4 = v.l4&maskLow51Bits + c3
return v
}
-1
View File
@@ -1 +0,0 @@
b0c49ae9f59d233526f8934262c5bbbe14d4358d
-19
View File
@@ -1,19 +0,0 @@
#! /bin/bash
set -euo pipefail
cd "$(git rev-parse --show-toplevel)"
STD_PATH=src/crypto/ed25519/internal/edwards25519/field
LOCAL_PATH=curve25519/internal/field
LAST_SYNC_REF=$(cat $LOCAL_PATH/sync.checkpoint)
git fetch https://go.googlesource.com/go master
if git diff --quiet $LAST_SYNC_REF:$STD_PATH FETCH_HEAD:$STD_PATH; then
echo "No changes."
else
NEW_REF=$(git rev-parse FETCH_HEAD | tee $LOCAL_PATH/sync.checkpoint)
echo "Applying changes from $LAST_SYNC_REF to $NEW_REF..."
git diff $LAST_SYNC_REF:$STD_PATH FETCH_HEAD:$STD_PATH | \
git apply -3 --directory=$LOCAL_PATH
fi
+1 -3
View File
@@ -11,9 +11,7 @@
// operations with the same key more efficient. This package refers to the RFC
// 8032 private key as the “seed”.
//
// Beginning with Go 1.13, the functionality of this package was moved to the
// standard library as crypto/ed25519. This package only acts as a compatibility
// wrapper.
// This package is a wrapper around the standard library crypto/ed25519 package.
package ed25519
import (
+1 -1
View File
@@ -8,7 +8,7 @@
// HKDF is a cryptographic key derivation function (KDF) with the goal of
// expanding limited input keying material into one or more cryptographically
// strong secret keys.
package hkdf // import "golang.org/x/crypto/hkdf"
package hkdf
import (
"crypto/hmac"
+1 -1
View File
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (!amd64 && !ppc64le && !s390x) || !gc || purego
//go:build (!amd64 && !loong64 && !ppc64le && !ppc64 && !s390x) || !gc || purego
package poly1305
+59 -74
View File
@@ -1,108 +1,93 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Code generated by command: go run sum_amd64_asm.go -out ../sum_amd64.s -pkg poly1305. DO NOT EDIT.
//go:build gc && !purego
#include "textflag.h"
#define POLY1305_ADD(msg, h0, h1, h2) \
ADDQ 0(msg), h0; \
ADCQ 8(msg), h1; \
ADCQ $1, h2; \
LEAQ 16(msg), msg
#define POLY1305_MUL(h0, h1, h2, r0, r1, t0, t1, t2, t3) \
MOVQ r0, AX; \
MULQ h0; \
MOVQ AX, t0; \
MOVQ DX, t1; \
MOVQ r0, AX; \
MULQ h1; \
ADDQ AX, t1; \
ADCQ $0, DX; \
MOVQ r0, t2; \
IMULQ h2, t2; \
ADDQ DX, t2; \
\
MOVQ r1, AX; \
MULQ h0; \
ADDQ AX, t1; \
ADCQ $0, DX; \
MOVQ DX, h0; \
MOVQ r1, t3; \
IMULQ h2, t3; \
MOVQ r1, AX; \
MULQ h1; \
ADDQ AX, t2; \
ADCQ DX, t3; \
ADDQ h0, t2; \
ADCQ $0, t3; \
\
MOVQ t0, h0; \
MOVQ t1, h1; \
MOVQ t2, h2; \
ANDQ $3, h2; \
MOVQ t2, t0; \
ANDQ $0xFFFFFFFFFFFFFFFC, t0; \
ADDQ t0, h0; \
ADCQ t3, h1; \
ADCQ $0, h2; \
SHRQ $2, t3, t2; \
SHRQ $2, t3; \
ADDQ t2, h0; \
ADCQ t3, h1; \
ADCQ $0, h2
// func update(state *[7]uint64, msg []byte)
// func update(state *macState, msg []byte)
TEXT ·update(SB), $0-32
MOVQ state+0(FP), DI
MOVQ msg_base+8(FP), SI
MOVQ msg_len+16(FP), R15
MOVQ 0(DI), R8 // h0
MOVQ 8(DI), R9 // h1
MOVQ 16(DI), R10 // h2
MOVQ 24(DI), R11 // r0
MOVQ 32(DI), R12 // r1
CMPQ R15, $16
MOVQ (DI), R8
MOVQ 8(DI), R9
MOVQ 16(DI), R10
MOVQ 24(DI), R11
MOVQ 32(DI), R12
CMPQ R15, $0x10
JB bytes_between_0_and_15
loop:
POLY1305_ADD(SI, R8, R9, R10)
ADDQ (SI), R8
ADCQ 8(SI), R9
ADCQ $0x01, R10
LEAQ 16(SI), SI
multiply:
POLY1305_MUL(R8, R9, R10, R11, R12, BX, CX, R13, R14)
SUBQ $16, R15
CMPQ R15, $16
JAE loop
MOVQ R11, AX
MULQ R8
MOVQ AX, BX
MOVQ DX, CX
MOVQ R11, AX
MULQ R9
ADDQ AX, CX
ADCQ $0x00, DX
MOVQ R11, R13
IMULQ R10, R13
ADDQ DX, R13
MOVQ R12, AX
MULQ R8
ADDQ AX, CX
ADCQ $0x00, DX
MOVQ DX, R8
MOVQ R12, R14
IMULQ R10, R14
MOVQ R12, AX
MULQ R9
ADDQ AX, R13
ADCQ DX, R14
ADDQ R8, R13
ADCQ $0x00, R14
MOVQ BX, R8
MOVQ CX, R9
MOVQ R13, R10
ANDQ $0x03, R10
MOVQ R13, BX
ANDQ $-4, BX
ADDQ BX, R8
ADCQ R14, R9
ADCQ $0x00, R10
SHRQ $0x02, R14, R13
SHRQ $0x02, R14
ADDQ R13, R8
ADCQ R14, R9
ADCQ $0x00, R10
SUBQ $0x10, R15
CMPQ R15, $0x10
JAE loop
bytes_between_0_and_15:
TESTQ R15, R15
JZ done
MOVQ $1, BX
MOVQ $0x00000001, BX
XORQ CX, CX
XORQ R13, R13
ADDQ R15, SI
flush_buffer:
SHLQ $8, BX, CX
SHLQ $8, BX
SHLQ $0x08, BX, CX
SHLQ $0x08, BX
MOVB -1(SI), R13
XORQ R13, BX
DECQ SI
DECQ R15
JNZ flush_buffer
ADDQ BX, R8
ADCQ CX, R9
ADCQ $0, R10
MOVQ $16, R15
ADCQ $0x00, R10
MOVQ $0x00000010, R15
JMP multiply
done:
MOVQ R8, 0(DI)
MOVQ R8, (DI)
MOVQ R9, 8(DI)
MOVQ R10, 16(DI)
RET
@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
//go:build gc && !purego && (amd64 || loong64 || ppc64 || ppc64le)
package poly1305
+123
View File
@@ -0,0 +1,123 @@
// Copyright 2025 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
// func update(state *macState, msg []byte)
TEXT ·update(SB), $0-32
MOVV state+0(FP), R4
MOVV msg_base+8(FP), R5
MOVV msg_len+16(FP), R6
MOVV $0x10, R7
MOVV (R4), R8 // h0
MOVV 8(R4), R9 // h1
MOVV 16(R4), R10 // h2
MOVV 24(R4), R11 // r0
MOVV 32(R4), R12 // r1
BLT R6, R7, bytes_between_0_and_15
loop:
MOVV (R5), R14 // msg[0:8]
MOVV 8(R5), R16 // msg[8:16]
ADDV R14, R8, R8 // h0 (x1 + y1 = z1', if z1' < x1 then z1' overflow)
ADDV R16, R9, R27
SGTU R14, R8, R24 // h0.carry
SGTU R9, R27, R28
ADDV R27, R24, R9 // h1
SGTU R27, R9, R24
OR R24, R28, R24 // h1.carry
ADDV $0x01, R24, R24
ADDV R10, R24, R10 // h2
ADDV $16, R5, R5 // msg = msg[16:]
multiply:
MULV R8, R11, R14 // h0r0.lo
MULHVU R8, R11, R15 // h0r0.hi
MULV R9, R11, R13 // h1r0.lo
MULHVU R9, R11, R16 // h1r0.hi
ADDV R13, R15, R15
SGTU R13, R15, R24
ADDV R24, R16, R16
MULV R10, R11, R25
ADDV R16, R25, R25
MULV R8, R12, R13 // h0r1.lo
MULHVU R8, R12, R16 // h0r1.hi
ADDV R13, R15, R15
SGTU R13, R15, R24
ADDV R24, R16, R16
MOVV R16, R8
MULV R10, R12, R26 // h2r1
MULV R9, R12, R13 // h1r1.lo
MULHVU R9, R12, R16 // h1r1.hi
ADDV R13, R25, R25
ADDV R16, R26, R27
SGTU R13, R25, R24
ADDV R27, R24, R26
ADDV R8, R25, R25
SGTU R8, R25, R24
ADDV R24, R26, R26
AND $3, R25, R10
AND $-4, R25, R17
ADDV R17, R14, R8
ADDV R26, R15, R27
SGTU R17, R8, R24
SGTU R26, R27, R28
ADDV R27, R24, R9
SGTU R27, R9, R24
OR R24, R28, R24
ADDV R24, R10, R10
SLLV $62, R26, R27
SRLV $2, R25, R28
SRLV $2, R26, R26
OR R27, R28, R25
ADDV R25, R8, R8
ADDV R26, R9, R27
SGTU R25, R8, R24
SGTU R26, R27, R28
ADDV R27, R24, R9
SGTU R27, R9, R24
OR R24, R28, R24
ADDV R24, R10, R10
SUBV $16, R6, R6
BGE R6, R7, loop
bytes_between_0_and_15:
BEQ R6, R0, done
MOVV $1, R14
XOR R15, R15
ADDV R6, R5, R5
flush_buffer:
MOVBU -1(R5), R25
SRLV $56, R14, R24
SLLV $8, R15, R28
SLLV $8, R14, R14
OR R24, R28, R15
XOR R25, R14, R14
SUBV $1, R6, R6
SUBV $1, R5, R5
BNE R6, R0, flush_buffer
ADDV R14, R8, R8
SGTU R14, R8, R24
ADDV R15, R9, R27
SGTU R15, R27, R28
ADDV R27, R24, R9
SGTU R27, R9, R24
OR R24, R28, R24
ADDV R10, R24, R10
MOVV $16, R6
JMP multiply
done:
MOVV R8, (R4)
MOVV R9, 8(R4)
MOVV R10, 16(R4)
RET
-47
View File
@@ -1,47 +0,0 @@
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
package poly1305
//go:noescape
func update(state *macState, msg []byte)
// mac is a wrapper for macGeneric that redirects calls that would have gone to
// updateGeneric to update.
//
// Its Write and Sum methods are otherwise identical to the macGeneric ones, but
// using function pointers would carry a major performance cost.
type mac struct{ macGeneric }
func (h *mac) Write(p []byte) (int, error) {
nn := len(p)
if h.offset > 0 {
n := copy(h.buffer[h.offset:], p)
if h.offset+n < TagSize {
h.offset += n
return nn, nil
}
p = p[n:]
h.offset = 0
update(&h.macState, h.buffer[:])
}
if n := len(p) - (len(p) % TagSize); n > 0 {
update(&h.macState, p[:n])
p = p[n:]
}
if len(p) > 0 {
h.offset += copy(h.buffer[h.offset:], p)
}
return nn, nil
}
func (h *mac) Sum(out *[16]byte) {
state := h.macState
if h.offset > 0 {
update(&state, h.buffer[:h.offset])
}
finalize(out, &state.h, &state.s)
}
@@ -2,15 +2,25 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gc && !purego
//go:build gc && !purego && (ppc64 || ppc64le)
#include "textflag.h"
// This was ported from the amd64 implementation.
#ifdef GOARCH_ppc64le
#define LE_MOVD MOVD
#define LE_MOVWZ MOVWZ
#define LE_MOVHZ MOVHZ
#else
#define LE_MOVD MOVDBR
#define LE_MOVWZ MOVWBR
#define LE_MOVHZ MOVHBR
#endif
#define POLY1305_ADD(msg, h0, h1, h2, t0, t1, t2) \
MOVD (msg), t0; \
MOVD 8(msg), t1; \
LE_MOVD (msg)( R0), t0; \
LE_MOVD (msg)(R24), t1; \
MOVD $1, t2; \
ADDC t0, h0, h0; \
ADDE t1, h1, h1; \
@@ -50,10 +60,6 @@
ADDE t3, h1, h1; \
ADDZE h2
DATA ·poly1305Mask<>+0x00(SB)/8, $0x0FFFFFFC0FFFFFFF
DATA ·poly1305Mask<>+0x08(SB)/8, $0x0FFFFFFC0FFFFFFC
GLOBL ·poly1305Mask<>(SB), RODATA, $16
// func update(state *[7]uint64, msg []byte)
TEXT ·update(SB), $0-32
MOVD state+0(FP), R3
@@ -66,6 +72,8 @@ TEXT ·update(SB), $0-32
MOVD 24(R3), R11 // r0
MOVD 32(R3), R12 // r1
MOVD $8, R24
CMP R5, $16
BLT bytes_between_0_and_15
@@ -94,7 +102,7 @@ flush_buffer:
// Greater than 8 -- load the rightmost remaining bytes in msg
// and put into R17 (h1)
MOVD (R4)(R21), R17
LE_MOVD (R4)(R21), R17
MOVD $16, R22
// Find the offset to those bytes
@@ -118,7 +126,7 @@ just1:
BLT less8
// Exactly 8
MOVD (R4), R16
LE_MOVD (R4), R16
CMP R17, $0
@@ -133,7 +141,7 @@ less8:
MOVD $0, R22 // shift count
CMP R5, $4
BLT less4
MOVWZ (R4), R16
LE_MOVWZ (R4), R16
ADD $4, R4
ADD $-4, R5
MOVD $32, R22
@@ -141,7 +149,7 @@ less8:
less4:
CMP R5, $2
BLT less2
MOVHZ (R4), R21
LE_MOVHZ (R4), R21
SLD R22, R21, R21
OR R16, R21, R16
ADD $16, R22
+2 -2
View File
@@ -4,10 +4,10 @@
// Package md4 implements the MD4 hash algorithm as defined in RFC 1320.
//
// Deprecated: MD4 is cryptographically broken and should should only be used
// Deprecated: MD4 is cryptographically broken and should only be used
// where compatibility with legacy systems, not security, is the goal. Instead,
// use a secure hash like SHA-256 (from crypto/sha256).
package md4 // import "golang.org/x/crypto/md4"
package md4
import (
"crypto"
+1 -1
View File
@@ -26,7 +26,7 @@ type pbeCipher interface {
create(key []byte) (cipher.Block, error)
// deriveKey returns a key derived from the given password and salt.
deriveKey(salt, password []byte, iterations int) []byte
// deriveKey returns an IV derived from the given password and salt.
// deriveIV returns an IV derived from the given password and salt.
deriveIV(salt, password []byte, iterations int) []byte
}
+1 -1
View File
@@ -7,7 +7,7 @@
// Deprecated: RIPEMD-160 is a legacy hash and should not be used for new
// applications. Also, this package does not and will not provide an optimized
// implementation. Instead, use a modern hash like SHA-256 (from crypto/sha256).
package ripemd160 // import "golang.org/x/crypto/ripemd160"
package ripemd160
// RIPEMD-160 is designed by Hans Dobbertin, Antoon Bosselaers, and Bart
// Preneel with specifications available at:
+5 -1
View File
@@ -5,6 +5,10 @@
// Package sha3 implements the SHA-3 fixed-output-length hash functions and
// the SHAKE variable-output-length hash functions defined by FIPS-202.
//
// All types in this package also implement [encoding.BinaryMarshaler],
// [encoding.BinaryAppender] and [encoding.BinaryUnmarshaler] to marshal and
// unmarshal the internal state of the hash.
//
// Both types of hash function use the "sponge" construction and the Keccak
// permutation. For a detailed specification see http://keccak.noekeon.org/
//
@@ -59,4 +63,4 @@
// They produce output of the same length, with the same security strengths
// against all attacks. This means, in particular, that SHA3-256 only has
// 128-bit collision resistance, because its output length is 32 bytes.
package sha3 // import "golang.org/x/crypto/sha3"
package sha3
+49 -18
View File
@@ -9,6 +9,7 @@ package sha3
// bytes.
import (
"crypto"
"hash"
)
@@ -16,53 +17,83 @@ import (
// Its generic security strength is 224 bits against preimage attacks,
// and 112 bits against collision attacks.
func New224() hash.Hash {
if h := new224Asm(); h != nil {
return h
}
return &state{rate: 144, outputLen: 28, dsbyte: 0x06}
return new224()
}
// New256 creates a new SHA3-256 hash.
// Its generic security strength is 256 bits against preimage attacks,
// and 128 bits against collision attacks.
func New256() hash.Hash {
if h := new256Asm(); h != nil {
return h
}
return &state{rate: 136, outputLen: 32, dsbyte: 0x06}
return new256()
}
// New384 creates a new SHA3-384 hash.
// Its generic security strength is 384 bits against preimage attacks,
// and 192 bits against collision attacks.
func New384() hash.Hash {
if h := new384Asm(); h != nil {
return h
}
return &state{rate: 104, outputLen: 48, dsbyte: 0x06}
return new384()
}
// New512 creates a new SHA3-512 hash.
// Its generic security strength is 512 bits against preimage attacks,
// and 256 bits against collision attacks.
func New512() hash.Hash {
if h := new512Asm(); h != nil {
return h
}
return &state{rate: 72, outputLen: 64, dsbyte: 0x06}
return new512()
}
func init() {
crypto.RegisterHash(crypto.SHA3_224, New224)
crypto.RegisterHash(crypto.SHA3_256, New256)
crypto.RegisterHash(crypto.SHA3_384, New384)
crypto.RegisterHash(crypto.SHA3_512, New512)
}
const (
dsbyteSHA3 = 0b00000110
dsbyteKeccak = 0b00000001
dsbyteShake = 0b00011111
dsbyteCShake = 0b00000100
// rateK[c] is the rate in bytes for Keccak[c] where c is the capacity in
// bits. Given the sponge size is 1600 bits, the rate is 1600 - c bits.
rateK256 = (1600 - 256) / 8
rateK448 = (1600 - 448) / 8
rateK512 = (1600 - 512) / 8
rateK768 = (1600 - 768) / 8
rateK1024 = (1600 - 1024) / 8
)
func new224Generic() *state {
return &state{rate: rateK448, outputLen: 28, dsbyte: dsbyteSHA3}
}
func new256Generic() *state {
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteSHA3}
}
func new384Generic() *state {
return &state{rate: rateK768, outputLen: 48, dsbyte: dsbyteSHA3}
}
func new512Generic() *state {
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteSHA3}
}
// NewLegacyKeccak256 creates a new Keccak-256 hash.
//
// Only use this function if you require compatibility with an existing cryptosystem
// that uses non-standard padding. All other users should use New256 instead.
func NewLegacyKeccak256() hash.Hash { return &state{rate: 136, outputLen: 32, dsbyte: 0x01} }
func NewLegacyKeccak256() hash.Hash {
return &state{rate: rateK512, outputLen: 32, dsbyte: dsbyteKeccak}
}
// NewLegacyKeccak512 creates a new Keccak-512 hash.
//
// Only use this function if you require compatibility with an existing cryptosystem
// that uses non-standard padding. All other users should use New512 instead.
func NewLegacyKeccak512() hash.Hash { return &state{rate: 72, outputLen: 64, dsbyte: 0x01} }
func NewLegacyKeccak512() hash.Hash {
return &state{rate: rateK1024, outputLen: 64, dsbyte: dsbyteKeccak}
}
// Sum224 returns the SHA3-224 digest of the data.
func Sum224(data []byte) (digest [28]byte) {
-27
View File
@@ -1,27 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !gc || purego || !s390x
package sha3
import (
"hash"
)
// new224Asm returns an assembly implementation of SHA3-224 if available,
// otherwise it returns nil.
func new224Asm() hash.Hash { return nil }
// new256Asm returns an assembly implementation of SHA3-256 if available,
// otherwise it returns nil.
func new256Asm() hash.Hash { return nil }
// new384Asm returns an assembly implementation of SHA3-384 if available,
// otherwise it returns nil.
func new384Asm() hash.Hash { return nil }
// new512Asm returns an assembly implementation of SHA3-512 if available,
// otherwise it returns nil.
func new512Asm() hash.Hash { return nil }
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !gc || purego || !s390x
package sha3
func new224() *state {
return new224Generic()
}
func new256() *state {
return new256Generic()
}
func new384() *state {
return new384Generic()
}
func new512() *state {
return new512Generic()
}
+5403 -374
View File
File diff suppressed because it is too large Load Diff
-18
View File
@@ -1,18 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.4
package sha3
import (
"crypto"
)
func init() {
crypto.RegisterHash(crypto.SHA3_224, New224)
crypto.RegisterHash(crypto.SHA3_256, New256)
crypto.RegisterHash(crypto.SHA3_384, New384)
crypto.RegisterHash(crypto.SHA3_512, New512)
}
+125 -78
View File
@@ -4,6 +4,15 @@
package sha3
import (
"crypto/subtle"
"encoding/binary"
"errors"
"unsafe"
"golang.org/x/sys/cpu"
)
// spongeDirection indicates the direction bytes are flowing through the sponge.
type spongeDirection int
@@ -14,17 +23,13 @@ const (
spongeSqueezing
)
const (
// maxRate is the maximum size of the internal buffer. SHAKE-256
// currently needs the largest buffer.
maxRate = 168
)
type state struct {
// Generic sponge components.
a [25]uint64 // main state of the hash
buf []byte // points into storage
rate int // the number of bytes of state to use
a [1600 / 8]byte // main state of the hash
// a[n:rate] is the buffer. If absorbing, it's the remaining space to XOR
// into before running the permutation. If squeezing, it's the remaining
// output to produce before running the permutation.
n, rate int
// dsbyte contains the "domain separation" bits and the first bit of
// the padding. Sections 6.1 and 6.2 of [1] separate the outputs of the
@@ -40,9 +45,6 @@ type state struct {
// Extendable-Output Functions (May 2014)"
dsbyte byte
storage storageBuf
// Specific to SHA-3 and SHAKE.
outputLen int // the default output size in bytes
state spongeDirection // whether the sponge is absorbing or squeezing
}
@@ -54,103 +56,77 @@ func (d *state) BlockSize() int { return d.rate }
func (d *state) Size() int { return d.outputLen }
// Reset clears the internal state by zeroing the sponge state and
// the byte buffer, and setting Sponge.state to absorbing.
// the buffer indexes, and setting Sponge.state to absorbing.
func (d *state) Reset() {
// Zero the permutation's state.
for i := range d.a {
d.a[i] = 0
}
d.state = spongeAbsorbing
d.buf = d.storage.asBytes()[:0]
d.n = 0
}
func (d *state) clone() *state {
ret := *d
if ret.state == spongeAbsorbing {
ret.buf = ret.storage.asBytes()[:len(ret.buf)]
} else {
ret.buf = ret.storage.asBytes()[d.rate-cap(d.buf) : d.rate]
}
return &ret
}
// permute applies the KeccakF-1600 permutation. It handles
// any input-output buffering.
// permute applies the KeccakF-1600 permutation.
func (d *state) permute() {
switch d.state {
case spongeAbsorbing:
// If we're absorbing, we need to xor the input into the state
// before applying the permutation.
xorIn(d, d.buf)
d.buf = d.storage.asBytes()[:0]
keccakF1600(&d.a)
case spongeSqueezing:
// If we're squeezing, we need to apply the permutation before
// copying more output.
keccakF1600(&d.a)
d.buf = d.storage.asBytes()[:d.rate]
copyOut(d, d.buf)
var a *[25]uint64
if cpu.IsBigEndian {
a = new([25]uint64)
for i := range a {
a[i] = binary.LittleEndian.Uint64(d.a[i*8:])
}
} else {
a = (*[25]uint64)(unsafe.Pointer(&d.a))
}
keccakF1600(a)
d.n = 0
if cpu.IsBigEndian {
for i := range a {
binary.LittleEndian.PutUint64(d.a[i*8:], a[i])
}
}
}
// pads appends the domain separation bits in dsbyte, applies
// the multi-bitrate 10..1 padding rule, and permutes the state.
func (d *state) padAndPermute(dsbyte byte) {
if d.buf == nil {
d.buf = d.storage.asBytes()[:0]
}
func (d *state) padAndPermute() {
// Pad with this instance's domain-separator bits. We know that there's
// at least one byte of space in d.buf because, if it were full,
// at least one byte of space in the sponge because, if it were full,
// permute would have been called to empty it. dsbyte also contains the
// first one bit for the padding. See the comment in the state struct.
d.buf = append(d.buf, dsbyte)
zerosStart := len(d.buf)
d.buf = d.storage.asBytes()[:d.rate]
for i := zerosStart; i < d.rate; i++ {
d.buf[i] = 0
}
d.a[d.n] ^= d.dsbyte
// This adds the final one bit for the padding. Because of the way that
// bits are numbered from the LSB upwards, the final bit is the MSB of
// the last byte.
d.buf[d.rate-1] ^= 0x80
d.a[d.rate-1] ^= 0x80
// Apply the permutation
d.permute()
d.state = spongeSqueezing
d.buf = d.storage.asBytes()[:d.rate]
copyOut(d, d.buf)
}
// Write absorbs more data into the hash's state. It panics if any
// output has already been read.
func (d *state) Write(p []byte) (written int, err error) {
func (d *state) Write(p []byte) (n int, err error) {
if d.state != spongeAbsorbing {
panic("sha3: Write after Read")
}
if d.buf == nil {
d.buf = d.storage.asBytes()[:0]
}
written = len(p)
n = len(p)
for len(p) > 0 {
if len(d.buf) == 0 && len(p) >= d.rate {
// The fast path; absorb a full "rate" bytes of input and apply the permutation.
xorIn(d, p[:d.rate])
p = p[d.rate:]
keccakF1600(&d.a)
} else {
// The slow path; buffer the input until we can fill the sponge, and then xor it in.
todo := d.rate - len(d.buf)
if todo > len(p) {
todo = len(p)
}
d.buf = append(d.buf, p[:todo]...)
p = p[todo:]
x := subtle.XORBytes(d.a[d.n:d.rate], d.a[d.n:d.rate], p)
d.n += x
p = p[x:]
// If the sponge is full, apply the permutation.
if len(d.buf) == d.rate {
d.permute()
}
// If the sponge is full, apply the permutation.
if d.n == d.rate {
d.permute()
}
}
@@ -161,21 +137,21 @@ func (d *state) Write(p []byte) (written int, err error) {
func (d *state) Read(out []byte) (n int, err error) {
// If we're still absorbing, pad and apply the permutation.
if d.state == spongeAbsorbing {
d.padAndPermute(d.dsbyte)
d.padAndPermute()
}
n = len(out)
// Now, do the squeezing.
for len(out) > 0 {
n := copy(out, d.buf)
d.buf = d.buf[n:]
out = out[n:]
// Apply the permutation if we've squeezed the sponge dry.
if len(d.buf) == 0 {
if d.n == d.rate {
d.permute()
}
x := copy(out, d.a[d.n:d.rate])
d.n += x
out = out[x:]
}
return
@@ -195,3 +171,74 @@ func (d *state) Sum(in []byte) []byte {
dup.Read(hash)
return append(in, hash...)
}
const (
magicSHA3 = "sha\x08"
magicShake = "sha\x09"
magicCShake = "sha\x0a"
magicKeccak = "sha\x0b"
// magic || rate || main state || n || sponge direction
marshaledSize = len(magicSHA3) + 1 + 200 + 1 + 1
)
func (d *state) MarshalBinary() ([]byte, error) {
return d.AppendBinary(make([]byte, 0, marshaledSize))
}
func (d *state) AppendBinary(b []byte) ([]byte, error) {
switch d.dsbyte {
case dsbyteSHA3:
b = append(b, magicSHA3...)
case dsbyteShake:
b = append(b, magicShake...)
case dsbyteCShake:
b = append(b, magicCShake...)
case dsbyteKeccak:
b = append(b, magicKeccak...)
default:
panic("unknown dsbyte")
}
// rate is at most 168, and n is at most rate.
b = append(b, byte(d.rate))
b = append(b, d.a[:]...)
b = append(b, byte(d.n), byte(d.state))
return b, nil
}
func (d *state) UnmarshalBinary(b []byte) error {
if len(b) != marshaledSize {
return errors.New("sha3: invalid hash state")
}
magic := string(b[:len(magicSHA3)])
b = b[len(magicSHA3):]
switch {
case magic == magicSHA3 && d.dsbyte == dsbyteSHA3:
case magic == magicShake && d.dsbyte == dsbyteShake:
case magic == magicCShake && d.dsbyte == dsbyteCShake:
case magic == magicKeccak && d.dsbyte == dsbyteKeccak:
default:
return errors.New("sha3: invalid hash state identifier")
}
rate := int(b[0])
b = b[1:]
if rate != d.rate {
return errors.New("sha3: invalid hash state function")
}
copy(d.a[:], b)
b = b[len(d.a):]
n, state := int(b[0]), spongeDirection(b[1])
if n > d.rate {
return errors.New("sha3: invalid hash state")
}
d.n = n
if state != spongeAbsorbing && state != spongeSqueezing {
return errors.New("sha3: invalid hash state")
}
d.state = state
return nil
}
+41 -26
View File
@@ -143,6 +143,12 @@ func (s *asmState) Write(b []byte) (int, error) {
// Read squeezes an arbitrary number of bytes from the sponge.
func (s *asmState) Read(out []byte) (n int, err error) {
// The 'compute last message digest' instruction only stores the digest
// at the first operand (dst) for SHAKE functions.
if s.function != shake_128 && s.function != shake_256 {
panic("sha3: can only call Read for SHAKE functions")
}
n = len(out)
// need to pad if we were absorbing
@@ -202,8 +208,17 @@ func (s *asmState) Sum(b []byte) []byte {
// Hash the buffer. Note that we don't clear it because we
// aren't updating the state.
klmd(s.function, &a, nil, s.buf)
return append(b, a[:s.outputLen]...)
switch s.function {
case sha3_224, sha3_256, sha3_384, sha3_512:
klmd(s.function, &a, nil, s.buf)
return append(b, a[:s.outputLen]...)
case shake_128, shake_256:
d := make([]byte, s.outputLen, 64)
klmd(s.function, &a, d, s.buf)
return append(b, d[:s.outputLen]...)
default:
panic("sha3: unknown function")
}
}
// Reset resets the Hash to its initial state.
@@ -233,56 +248,56 @@ func (s *asmState) Clone() ShakeHash {
return s.clone()
}
// new224Asm returns an assembly implementation of SHA3-224 if available,
// otherwise it returns nil.
func new224Asm() hash.Hash {
// new224 returns an assembly implementation of SHA3-224 if available,
// otherwise it returns a generic implementation.
func new224() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_224)
}
return nil
return new224Generic()
}
// new256Asm returns an assembly implementation of SHA3-256 if available,
// otherwise it returns nil.
func new256Asm() hash.Hash {
// new256 returns an assembly implementation of SHA3-256 if available,
// otherwise it returns a generic implementation.
func new256() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_256)
}
return nil
return new256Generic()
}
// new384Asm returns an assembly implementation of SHA3-384 if available,
// otherwise it returns nil.
func new384Asm() hash.Hash {
// new384 returns an assembly implementation of SHA3-384 if available,
// otherwise it returns a generic implementation.
func new384() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_384)
}
return nil
return new384Generic()
}
// new512Asm returns an assembly implementation of SHA3-512 if available,
// otherwise it returns nil.
func new512Asm() hash.Hash {
// new512 returns an assembly implementation of SHA3-512 if available,
// otherwise it returns a generic implementation.
func new512() hash.Hash {
if cpu.S390X.HasSHA3 {
return newAsmState(sha3_512)
}
return nil
return new512Generic()
}
// newShake128Asm returns an assembly implementation of SHAKE-128 if available,
// otherwise it returns nil.
func newShake128Asm() ShakeHash {
// newShake128 returns an assembly implementation of SHAKE-128 if available,
// otherwise it returns a generic implementation.
func newShake128() ShakeHash {
if cpu.S390X.HasSHA3 {
return newAsmState(shake_128)
}
return nil
return newShake128Generic()
}
// newShake256Asm returns an assembly implementation of SHAKE-256 if available,
// otherwise it returns nil.
func newShake256Asm() ShakeHash {
// newShake256 returns an assembly implementation of SHAKE-256 if available,
// otherwise it returns a generic implementation.
func newShake256() ShakeHash {
if cpu.S390X.HasSHA3 {
return newAsmState(shake_256)
}
return nil
return newShake256Generic()
}
+61 -40
View File
@@ -16,9 +16,12 @@ package sha3
// [2] https://doi.org/10.6028/NIST.SP.800-185
import (
"bytes"
"encoding/binary"
"errors"
"hash"
"io"
"math/bits"
)
// ShakeHash defines the interface to hash functions that support
@@ -50,44 +53,36 @@ type cshakeState struct {
initBlock []byte
}
// Consts for configuring initial SHA-3 state
const (
dsbyteShake = 0x1f
dsbyteCShake = 0x04
rate128 = 168
rate256 = 136
)
func bytepad(input []byte, w int) []byte {
// leftEncode always returns max 9 bytes
buf := make([]byte, 0, 9+len(input)+w)
buf = append(buf, leftEncode(uint64(w))...)
buf = append(buf, input...)
padlen := w - (len(buf) % w)
return append(buf, make([]byte, padlen)...)
func bytepad(data []byte, rate int) []byte {
out := make([]byte, 0, 9+len(data)+rate-1)
out = append(out, leftEncode(uint64(rate))...)
out = append(out, data...)
if padlen := rate - len(out)%rate; padlen < rate {
out = append(out, make([]byte, padlen)...)
}
return out
}
func leftEncode(value uint64) []byte {
var b [9]byte
binary.BigEndian.PutUint64(b[1:], value)
// Trim all but last leading zero bytes
i := byte(1)
for i < 8 && b[i] == 0 {
i++
func leftEncode(x uint64) []byte {
// Let n be the smallest positive integer for which 2^(8n) > x.
n := (bits.Len64(x) + 7) / 8
if n == 0 {
n = 1
}
// Prepend number of encoded bytes
b[i-1] = 9 - i
return b[i-1:]
// Return n || x with n as a byte and x an n bytes in big-endian order.
b := make([]byte, 9)
binary.BigEndian.PutUint64(b[1:], x)
b = b[9-n-1:]
b[0] = byte(n)
return b
}
func newCShake(N, S []byte, rate, outputLen int, dsbyte byte) ShakeHash {
c := cshakeState{state: &state{rate: rate, outputLen: outputLen, dsbyte: dsbyte}}
// leftEncode returns max 9 bytes
c.initBlock = make([]byte, 0, 9*2+len(N)+len(S))
c.initBlock = append(c.initBlock, leftEncode(uint64(len(N)*8))...)
c.initBlock = make([]byte, 0, 9+len(N)+9+len(S)) // leftEncode returns max 9 bytes
c.initBlock = append(c.initBlock, leftEncode(uint64(len(N))*8)...)
c.initBlock = append(c.initBlock, N...)
c.initBlock = append(c.initBlock, leftEncode(uint64(len(S)*8))...)
c.initBlock = append(c.initBlock, leftEncode(uint64(len(S))*8)...)
c.initBlock = append(c.initBlock, S...)
c.Write(bytepad(c.initBlock, c.rate))
return &c
@@ -111,24 +106,50 @@ func (c *state) Clone() ShakeHash {
return c.clone()
}
func (c *cshakeState) MarshalBinary() ([]byte, error) {
return c.AppendBinary(make([]byte, 0, marshaledSize+len(c.initBlock)))
}
func (c *cshakeState) AppendBinary(b []byte) ([]byte, error) {
b, err := c.state.AppendBinary(b)
if err != nil {
return nil, err
}
b = append(b, c.initBlock...)
return b, nil
}
func (c *cshakeState) UnmarshalBinary(b []byte) error {
if len(b) <= marshaledSize {
return errors.New("sha3: invalid hash state")
}
if err := c.state.UnmarshalBinary(b[:marshaledSize]); err != nil {
return err
}
c.initBlock = bytes.Clone(b[marshaledSize:])
return nil
}
// NewShake128 creates a new SHAKE128 variable-output-length ShakeHash.
// Its generic security strength is 128 bits against all attacks if at
// least 32 bytes of its output are used.
func NewShake128() ShakeHash {
if h := newShake128Asm(); h != nil {
return h
}
return &state{rate: rate128, outputLen: 32, dsbyte: dsbyteShake}
return newShake128()
}
// NewShake256 creates a new SHAKE256 variable-output-length ShakeHash.
// Its generic security strength is 256 bits against all attacks if
// at least 64 bytes of its output are used.
func NewShake256() ShakeHash {
if h := newShake256Asm(); h != nil {
return h
}
return &state{rate: rate256, outputLen: 64, dsbyte: dsbyteShake}
return newShake256()
}
func newShake128Generic() *state {
return &state{rate: rateK256, outputLen: 32, dsbyte: dsbyteShake}
}
func newShake256Generic() *state {
return &state{rate: rateK512, outputLen: 64, dsbyte: dsbyteShake}
}
// NewCShake128 creates a new instance of cSHAKE128 variable-output-length ShakeHash,
@@ -141,7 +162,7 @@ func NewCShake128(N, S []byte) ShakeHash {
if len(N) == 0 && len(S) == 0 {
return NewShake128()
}
return newCShake(N, S, rate128, 32, dsbyteCShake)
return newCShake(N, S, rateK256, 32, dsbyteCShake)
}
// NewCShake256 creates a new instance of cSHAKE256 variable-output-length ShakeHash,
@@ -154,7 +175,7 @@ func NewCShake256(N, S []byte) ShakeHash {
if len(N) == 0 && len(S) == 0 {
return NewShake256()
}
return newCShake(N, S, rate256, 64, dsbyteCShake)
return newCShake(N, S, rateK512, 64, dsbyteCShake)
}
// ShakeSum128 writes an arbitrary-length digest of data into hash.
-19
View File
@@ -1,19 +0,0 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !gc || purego || !s390x
package sha3
// newShake128Asm returns an assembly implementation of SHAKE-128 if available,
// otherwise it returns nil.
func newShake128Asm() ShakeHash {
return nil
}
// newShake256Asm returns an assembly implementation of SHAKE-256 if available,
// otherwise it returns nil.
func newShake256Asm() ShakeHash {
return nil
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !gc || purego || !s390x
package sha3
func newShake128() *state {
return newShake128Generic()
}
func newShake256() *state {
return newShake256Generic()
}
-23
View File
@@ -1,23 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (!amd64 && !386 && !ppc64le) || purego
package sha3
// A storageBuf is an aligned array of maxRate bytes.
type storageBuf [maxRate]byte
func (b *storageBuf) asBytes() *[maxRate]byte {
return (*[maxRate]byte)(b)
}
var (
xorIn = xorInGeneric
copyOut = copyOutGeneric
xorInUnaligned = xorInGeneric
copyOutUnaligned = copyOutGeneric
)
const xorImplementationUnaligned = "generic"
-28
View File
@@ -1,28 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package sha3
import "encoding/binary"
// xorInGeneric xors the bytes in buf into the state; it
// makes no non-portable assumptions about memory layout
// or alignment.
func xorInGeneric(d *state, buf []byte) {
n := len(buf) / 8
for i := 0; i < n; i++ {
a := binary.LittleEndian.Uint64(buf)
d.a[i] ^= a
buf = buf[8:]
}
}
// copyOutGeneric copies uint64s to a byte buffer.
func copyOutGeneric(d *state, b []byte) {
for i := 0; len(b) >= 8; i++ {
binary.LittleEndian.PutUint64(b, d.a[i])
b = b[8:]
}
}
-66
View File
@@ -1,66 +0,0 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (amd64 || 386 || ppc64le) && !purego
package sha3
import "unsafe"
// A storageBuf is an aligned array of maxRate bytes.
type storageBuf [maxRate / 8]uint64
func (b *storageBuf) asBytes() *[maxRate]byte {
return (*[maxRate]byte)(unsafe.Pointer(b))
}
// xorInUnaligned uses unaligned reads and writes to update d.a to contain d.a
// XOR buf.
func xorInUnaligned(d *state, buf []byte) {
n := len(buf)
bw := (*[maxRate / 8]uint64)(unsafe.Pointer(&buf[0]))[: n/8 : n/8]
if n >= 72 {
d.a[0] ^= bw[0]
d.a[1] ^= bw[1]
d.a[2] ^= bw[2]
d.a[3] ^= bw[3]
d.a[4] ^= bw[4]
d.a[5] ^= bw[5]
d.a[6] ^= bw[6]
d.a[7] ^= bw[7]
d.a[8] ^= bw[8]
}
if n >= 104 {
d.a[9] ^= bw[9]
d.a[10] ^= bw[10]
d.a[11] ^= bw[11]
d.a[12] ^= bw[12]
}
if n >= 136 {
d.a[13] ^= bw[13]
d.a[14] ^= bw[14]
d.a[15] ^= bw[15]
d.a[16] ^= bw[16]
}
if n >= 144 {
d.a[17] ^= bw[17]
}
if n >= 168 {
d.a[18] ^= bw[18]
d.a[19] ^= bw[19]
d.a[20] ^= bw[20]
}
}
func copyOutUnaligned(d *state, buf []byte) {
ab := (*[maxRate]uint8)(unsafe.Pointer(&d.a[0]))
copy(buf, ab[:])
}
var (
xorIn = xorInUnaligned
copyOut = copyOutUnaligned
)
const xorImplementationUnaligned = "unaligned"

Some files were not shown because too many files have changed in this diff Show More